From 2ef56a9a3f1e0c7243a3b098703971e7a6ccf4f9 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Tue, 18 Apr 2017 20:19:04 -0700 Subject: [PATCH 1/7] Added a `--device-api-key` option to the `device register` command. Change-Type: minor --- build/actions/device.js | 24 +++++++++++++++--------- doc/cli.markdown | 6 ++++++ lib/actions/device.coffee | 35 +++++++++++++++++++++++++---------- package.json | 2 +- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/build/actions/device.js b/build/actions/device.js index 3df195a4..29d9aab0 100644 --- a/build/actions/device.js +++ b/build/actions/device.js @@ -84,7 +84,7 @@ exports.supported = { exports.register = { signature: 'device register ', description: 'register a device', - help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp', + help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp\n $ resin device register MyApp --uuid \n $ resin device register MyApp --uuid --device-api-key ', permission: 'user', options: [ { @@ -92,19 +92,25 @@ exports.register = { description: 'custom uuid', parameter: 'uuid', alias: 'u' + }, { + signature: 'deviceApiKey', + description: 'custom device key', + parameter: 'device-api-key', + alias: 'k' } ], action: function(params, options, done) { - var Promise, resin; + var Promise, ref, ref1, resin; Promise = require('bluebird'); resin = require('resin-sdk-preconfigured'); - return resin.models.application.get(params.application).then(function(application) { - return Promise["try"](function() { - return options.uuid || resin.models.device.generateUniqueKey(); - }).then(function(uuid) { - console.info("Registering to " + application.app_name + ": " + uuid); - return resin.models.device.register(application.app_name, uuid); - }); + return Promise.join(resin.models.application.get(params.application), (ref = options.uuid) != null ? ref : resin.models.device.generateUniqueKey(), (ref1 = options.deviceApiKey) != null ? ref1 : resin.models.device.generateUniqueKey(), function(application, uuid, deviceApiKey) { + console.info("Registering to " + application.app_name + ": " + uuid); + if (options.deviceApiKey == null) { + console.info("Using generated device api key: " + deviceApiKey); + } else { + console.info('Using provided device api key'); + } + return resin.models.device.register(application.app_name, uuid, deviceApiKey); }).get('uuid').nodeify(done); } }; diff --git a/doc/cli.markdown b/doc/cli.markdown index d1b39ec5..fb95897b 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -334,6 +334,8 @@ Use this command to register a device to an application. Examples: $ resin device register MyApp + $ resin device register MyApp --uuid + $ resin device register MyApp --uuid --device-api-key ### Options @@ -341,6 +343,10 @@ Examples: custom uuid +#### --deviceApiKey, -k <device-api-key> + +custom device key + ## device rm <uuid> Use this command to remove a device from resin.io. diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index 8e4ddb70..6a6ab11b 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -134,25 +134,40 @@ exports.register = Examples: $ resin device register MyApp + $ resin device register MyApp --uuid + $ resin device register MyApp --uuid --device-api-key ''' permission: 'user' options: [ - signature: 'uuid' - description: 'custom uuid' - parameter: 'uuid' - alias: 'u' + { + signature: 'uuid' + description: 'custom uuid' + parameter: 'uuid' + alias: 'u' + } + { + signature: 'deviceApiKey' + description: 'custom device key' + parameter: 'device-api-key' + alias: 'k' + } ] action: (params, options, done) -> Promise = require('bluebird') resin = require('resin-sdk-preconfigured') - resin.models.application.get(params.application).then (application) -> - - Promise.try -> - return options.uuid or resin.models.device.generateUniqueKey() - .then (uuid) -> + Promise.join( + resin.models.application.get(params.application) + options.uuid ? resin.models.device.generateUniqueKey() + options.deviceApiKey ? resin.models.device.generateUniqueKey() + (application, uuid, deviceApiKey) -> console.info("Registering to #{application.app_name}: #{uuid}") - return resin.models.device.register(application.app_name, uuid) + if not options.deviceApiKey? + console.info("Using generated device api key: #{deviceApiKey}") + else + console.info('Using provided device api key') + return resin.models.device.register(application.app_name, uuid, deviceApiKey) + ) .get('uuid') .nodeify(done) diff --git a/package.json b/package.json index 7474a172..07125530 100644 --- a/package.json +++ b/package.json @@ -105,4 +105,4 @@ "optionalDependencies": { "removedrive": "^1.0.0" } -} \ No newline at end of file +} From 97eaf174ecbf0f6bc04a6491149e71316ab4f8c4 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Tue, 18 Apr 2017 20:21:59 -0700 Subject: [PATCH 2/7] Added a `--device-api-key` option to the `config generate` command. Change-Type: minor --- build/actions/config.js | 12 ++++++++++-- doc/cli.markdown | 5 +++++ lib/actions/config.coffee | 11 ++++++++++- package.json | 2 +- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/build/actions/config.js b/build/actions/config.js index d1be1242..6522c801 100644 --- a/build/actions/config.js +++ b/build/actions/config.js @@ -194,9 +194,14 @@ exports.reconfigure = { exports.generate = { signature: 'config generate', description: 'generate a config.json file', - help: 'Use this command to generate a config.json for a device or application\n\nExamples:\n\n $ resin config generate --device 7cf02a6\n $ resin config generate --device 7cf02a6 --output config.json\n $ resin config generate --app MyApp\n $ resin config generate --app MyApp --output config.json', + help: 'Use this command to generate a config.json for a device or application\n\nExamples:\n\n $ resin config generate --device 7cf02a6\n $ resin config generate --device 7cf02a6 --device-api-key \n $ resin config generate --device 7cf02a6 --output config.json\n $ resin config generate --app MyApp\n $ resin config generate --app MyApp --output config.json', options: [ commandOptions.optionalApplication, commandOptions.optionalDevice, { + signature: 'deviceApiKey', + description: 'custom device key', + parameter: 'device-api-key', + alias: 'k' + }, { signature: 'output', description: 'output', parameter: 'output', @@ -223,8 +228,11 @@ exports.generate = { return resin.models.application.get(options.application); }).then(function(resource) { return resin.models.device.getManifestBySlug(resource.device_type).get('options').then(form.run).then(function(answers) { + var ref; if (resource.uuid != null) { - return deviceConfig.getByDevice(resource.uuid, answers); + return Promise.resolve((ref = options.deviceApiKey) != null ? ref : resin.models.device.generateDeviceKey(resource.uuid)).then(function(deviceApiKey) { + return deviceConfig.getByDevice(resource.uuid, deviceApiKey, answers); + }); } return deviceConfig.getByApplication(resource.app_name, answers); }); diff --git a/doc/cli.markdown b/doc/cli.markdown index fb95897b..da66b447 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -1038,6 +1038,7 @@ Use this command to generate a config.json for a device or application Examples: $ resin config generate --device 7cf02a6 + $ resin config generate --device 7cf02a6 --device-api-key $ resin config generate --device 7cf02a6 --output config.json $ resin config generate --app MyApp $ resin config generate --app MyApp --output config.json @@ -1052,6 +1053,10 @@ application name device uuid +#### --deviceApiKey, -k <device-api-key> + +custom device key + #### --output, -o <output> output diff --git a/lib/actions/config.coffee b/lib/actions/config.coffee index 96077646..c7f91f85 100644 --- a/lib/actions/config.coffee +++ b/lib/actions/config.coffee @@ -224,6 +224,7 @@ exports.generate = Examples: $ resin config generate --device 7cf02a6 + $ resin config generate --device 7cf02a6 --device-api-key $ resin config generate --device 7cf02a6 --output config.json $ resin config generate --app MyApp $ resin config generate --app MyApp --output config.json @@ -231,6 +232,12 @@ exports.generate = options: [ commandOptions.optionalApplication commandOptions.optionalDevice + { + signature: 'deviceApiKey' + description: 'custom device key' + parameter: 'device-api-key' + alias: 'k' + } { signature: 'output' description: 'output' @@ -267,7 +274,9 @@ exports.generate = .then(form.run) .then (answers) -> if resource.uuid? - return deviceConfig.getByDevice(resource.uuid, answers) + return Promise.resolve(options.deviceApiKey ? resin.models.device.generateDeviceKey(resource.uuid)) + .then (deviceApiKey) -> + deviceConfig.getByDevice(resource.uuid, deviceApiKey, answers) return deviceConfig.getByApplication(resource.app_name, answers) .then (config) -> if options.output? diff --git a/package.json b/package.json index 07125530..89765ba0 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "resin-cli-visuals": "^1.4.0", "resin-config-json": "^1.0.0", "resin-device-config": "^3.0.0", - "resin-device-init": "^2.2.1", + "resin-device-init": "^3.0.0", "resin-docker-build": "^0.4.0", "resin-doodles": "0.0.1", "resin-image-fs": "^2.3.0", From 3fd4f328ab209d72d1a01e3bf5d414adf338f1e2 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Tue, 18 Apr 2017 20:22:43 -0700 Subject: [PATCH 3/7] Added a device api key parameter to the `os configure` command. Change-Type: minor --- build/actions/os.js | 24 +++++++++++++----------- doc/cli.markdown | 5 +++-- lib/actions/os.coffee | 23 +++++++++++++++-------- package.json | 2 +- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/build/actions/os.js b/build/actions/os.js index 35d207d8..76b4434d 100644 --- a/build/actions/os.js +++ b/build/actions/os.js @@ -188,9 +188,9 @@ exports.buildConfig = { }; exports.configure = { - signature: 'os configure ', + signature: 'os configure [deviceApiKey]', description: 'configure an os image', - help: 'Use this command to configure a previously downloaded operating system image for the specific device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6', + help: 'Use this command to configure a previously downloaded operating system image for the specific device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6\n $ resin os configure ../path/rpi.img 7cf02a6 ', permission: 'user', options: [ commandOptions.advancedConfig, { @@ -200,7 +200,7 @@ exports.configure = { } ], action: function(params, options, done) { - var Promise, fs, helpers, init, readFileAsync, resin; + var Promise, fs, helpers, init, readFileAsync, ref, resin; fs = require('fs'); Promise = require('bluebird'); readFileAsync = Promise.promisify(fs.readFile); @@ -208,14 +208,16 @@ exports.configure = { init = require('resin-device-init'); helpers = require('../utils/helpers'); console.info('Configuring operating system image'); - return resin.models.device.get(params.uuid).then(function(device) { - if (options.config) { - return readFileAsync(options.config, 'utf8').then(JSON.parse); - } - return buildConfig(params.image, device.device_type, options.advanced); - }).then(function(answers) { - return init.configure(params.image, params.uuid, answers).then(helpers.osProgressHandler); - }).nodeify(done); + return Promise.all([ + resin.models.device.get(params.uuid).then(function(device) { + if (options.config) { + return readFileAsync(options.config, 'utf8').then(JSON.parse); + } + return buildConfig(params.image, device.device_type, options.advanced); + }), Promise.resolve((ref = params.deviceApiKey) != null ? ref : resin.models.device.generateDeviceKey(params.uuid)) + ]).spread(function(answers, deviceApiKey) { + return init.configure(params.image, params.uuid, deviceApiKey, answers); + }).then(helpers.osProgressHandler).nodeify(done); } }; diff --git a/doc/cli.markdown b/doc/cli.markdown index da66b447..664fbc8a 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -106,7 +106,7 @@ environment variable (in the same standard URL format). - [os versions <type>](#os-versions-60-type-62-) - [os download <type>](#os-download-60-type-62-) - [os build-config <image> <device-type>](#os-build-config-60-image-62-60-device-type-62-) - - [os configure <image> <uuid>](#os-configure-60-image-62-60-uuid-62-) + - [os configure <image> <uuid> [deviceApiKey]](#os-configure-60-image-62-60-uuid-62-deviceapikey-) - [os initialize <image>](#os-initialize-60-image-62-) - Config @@ -904,13 +904,14 @@ show advanced configuration options the path to the output JSON file -## os configure <image> <uuid> +## os configure <image> <uuid> [deviceApiKey] Use this command to configure a previously downloaded operating system image for the specific device. Examples: $ resin os configure ../path/rpi.img 7cf02a6 + $ resin os configure ../path/rpi.img 7cf02a6 ### Options diff --git a/lib/actions/os.coffee b/lib/actions/os.coffee index 9c57332e..94bd50ab 100644 --- a/lib/actions/os.coffee +++ b/lib/actions/os.coffee @@ -196,7 +196,7 @@ exports.buildConfig = .nodeify(done) exports.configure = - signature: 'os configure ' + signature: 'os configure [deviceApiKey]' description: 'configure an os image' help: ''' Use this command to configure a previously downloaded operating system image for the specific device. @@ -204,6 +204,7 @@ exports.configure = Examples: $ resin os configure ../path/rpi.img 7cf02a6 + $ resin os configure ../path/rpi.img 7cf02a6 ''' permission: 'user' options: [ @@ -223,13 +224,19 @@ exports.configure = helpers = require('../utils/helpers') console.info('Configuring operating system image') - resin.models.device.get(params.uuid).then (device) -> - if options.config - return readFileAsync(options.config, 'utf8') - .then(JSON.parse) - return buildConfig(params.image, device.device_type, options.advanced) - .then (answers) -> - init.configure(params.image, params.uuid, answers).then(helpers.osProgressHandler) + Promise.all [ + resin.models.device.get(params.uuid) + .then (device) -> + if options.config + return readFileAsync(options.config, 'utf8') + .then(JSON.parse) + return buildConfig(params.image, device.device_type, options.advanced) + , + Promise.resolve(params.deviceApiKey ? resin.models.device.generateDeviceKey(params.uuid)) + ] + .spread (answers, deviceApiKey) -> + init.configure(params.image, params.uuid, deviceApiKey, answers) + .then(helpers.osProgressHandler) .nodeify(done) INIT_WARNING_MESSAGE = ''' diff --git a/package.json b/package.json index 89765ba0..14b34112 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "resin-cli-form": "^1.4.1", "resin-cli-visuals": "^1.4.0", "resin-config-json": "^1.0.0", - "resin-device-config": "^3.0.0", + "resin-device-config": "^4.0.0", "resin-device-init": "^3.0.0", "resin-docker-build": "^0.4.0", "resin-doodles": "0.0.1", From 8e95757f4775e6d4ddeffb013912a2f1db0e02a6 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 11 Oct 2017 19:52:03 +0200 Subject: [PATCH 4/7] Make `resin config generate` safe for all ResinOS versions --- build/actions/config.js | 96 +++++++++++++++++++++++++++++++++++---- doc/cli.markdown | 2 +- lib/actions/config.coffee | 92 +++++++++++++++++++++++++++++++++---- 3 files changed, 172 insertions(+), 18 deletions(-) diff --git a/build/actions/config.js b/build/actions/config.js index 6522c801..c909c73e 100644 --- a/build/actions/config.js +++ b/build/actions/config.js @@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -var commandOptions; +var authenticateWithApplicationKey, authenticateWithDeviceKey, commandOptions, generateApplicationConfig, generateBaseConfig, generateDeviceConfig; commandOptions = require('./command-options'); @@ -198,7 +198,7 @@ exports.generate = { options: [ commandOptions.optionalApplication, commandOptions.optionalDevice, { signature: 'deviceApiKey', - description: 'custom device key', + description: 'custom device key - note that this is only supported on ResinOS 2.0.3+', parameter: 'device-api-key', alias: 'k' }, { @@ -213,10 +213,10 @@ exports.generate = { var Promise, _, deviceConfig, form, prettyjson, resin, writeFileAsync; Promise = require('bluebird'); writeFileAsync = Promise.promisify(require('fs').writeFile); + deviceConfig = require('resin-device-config'); resin = require('resin-sdk-preconfigured'); _ = require('lodash'); form = require('resin-cli-form'); - deviceConfig = require('resin-device-config'); prettyjson = require('prettyjson'); if ((options.device == null) && (options.application == null)) { throw new Error('You have to pass either a device or an application.\n\nSee the help page for examples:\n\n $ resin help config generate'); @@ -228,15 +228,14 @@ exports.generate = { return resin.models.application.get(options.application); }).then(function(resource) { return resin.models.device.getManifestBySlug(resource.device_type).get('options').then(form.run).then(function(answers) { - var ref; if (resource.uuid != null) { - return Promise.resolve((ref = options.deviceApiKey) != null ? ref : resin.models.device.generateDeviceKey(resource.uuid)).then(function(deviceApiKey) { - return deviceConfig.getByDevice(resource.uuid, deviceApiKey, answers); - }); + return generateDeviceConfig(resource, options.deviceApiKey, answers); + } else { + return generateApplicationConfig(resource, answers); } - return deviceConfig.getByApplication(resource.app_name, answers); }); }).then(function(config) { + deviceConfig.validate(config); if (options.output != null) { return writeFileAsync(options.output, JSON.stringify(config)); } @@ -244,3 +243,84 @@ exports.generate = { }).nodeify(done); } }; + +generateBaseConfig = function(application, options) { + var Promise, deviceConfig, resin; + Promise = require('bluebird'); + deviceConfig = require('resin-device-config'); + resin = require('resin-sdk-preconfigured'); + return Promise.props({ + userId: resin.auth.getUserId(), + username: resin.auth.whoami(), + apiUrl: resin.settings.get('apiUrl'), + vpnUrl: resin.settings.get('vpnUrl'), + registryUrl: resin.settings.get('registryUrl'), + deltaUrl: resin.settings.get('deltaUrl'), + pubNubKeys: resin.models.config.getPubNubKeys(), + mixpanelToken: resin.models.config.getMixpanelToken() + }).then(function(results) { + return deviceConfig.generate({ + application: application, + user: { + id: results.userId, + username: results.username + }, + endpoints: { + api: results.apiUrl, + vpn: results.vpnUrl, + registry: results.registryUrl, + delta: results.deltaUrl + }, + pubnub: results.pubNubKeys, + mixpanel: { + token: results.mixpanelToken + } + }); + }); +}; + +generateApplicationConfig = function(application, options) { + return generateBaseConfig(application).tap(function(config) { + return authenticateWithApplicationKey(config, application.id); + }); +}; + +generateDeviceConfig = function(device, deviceApiKey, options) { + var resin; + resin = require('resin-sdk-preconfigured'); + return resin.models.application.get(device.application_name).then(function(application) { + return generateBaseConfig(application, options).tap(function(config) { + if (deviceApiKey != null) { + return authenticateWithDeviceKey(config, device.uuid, deviceApiKey); + } else { + return authenticateWithApplicationKey(config, application.id); + } + }); + }).then(function(config) { + config.registered_at = Math.floor(Date.now() / 1000); + config.deviceId = device.id; + return config; + }); +}; + +authenticateWithApplicationKey = function(config, applicationNameOrId) { + var resin; + resin = require('resin-sdk-preconfigured'); + return resin.models.application.generateApiKey(applicationNameOrId).then(function(apiKey) { + config.apiKey = apiKey; + return config; + }); +}; + +authenticateWithDeviceKey = function(config, uuid, customDeviceApiKey) { + var Promise, resin; + Promise = require('bluebird'); + resin = require('resin-sdk-preconfigured'); + return Promise["try"](function() { + return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); + }).then(function(deviceApiKey) { + config.uuid = uuid; + config.deviceApiKey = deviceApiKey; + return config; + }); +}; diff --git a/doc/cli.markdown b/doc/cli.markdown index 664fbc8a..65d6207c 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -1056,7 +1056,7 @@ device uuid #### --deviceApiKey, -k <device-api-key> -custom device key +custom device key - note that this is only supported on ResinOS 2.0.3+ #### --output, -o <output> diff --git a/lib/actions/config.coffee b/lib/actions/config.coffee index c7f91f85..2bef8ebe 100644 --- a/lib/actions/config.coffee +++ b/lib/actions/config.coffee @@ -234,7 +234,7 @@ exports.generate = commandOptions.optionalDevice { signature: 'deviceApiKey' - description: 'custom device key' + description: 'custom device key - note that this is only supported on ResinOS 2.0.3+' parameter: 'device-api-key' alias: 'k' } @@ -270,17 +270,91 @@ exports.generate = return resin.models.application.get(options.application) .then (resource) -> resin.models.device.getManifestBySlug(resource.device_type) - .get('options') - .then(form.run) - .then (answers) -> - if resource.uuid? - return Promise.resolve(options.deviceApiKey ? resin.models.device.generateDeviceKey(resource.uuid)) - .then (deviceApiKey) -> - deviceConfig.getByDevice(resource.uuid, deviceApiKey, answers) - return deviceConfig.getByApplication(resource.app_name, answers) + .get('options') + .then(form.run) + .then (answers) -> + if resource.uuid? + generateDeviceConfig(resource, options.deviceApiKey, answers) + else + generateApplicationConfig(resource, answers) .then (config) -> + deviceConfig.validate(config) if options.output? return writeFileAsync(options.output, JSON.stringify(config)) console.log(prettyjson.render(config)) .nodeify(done) + +generateBaseConfig = (application, options) -> + Promise = require('bluebird') + deviceConfig = require('resin-device-config') + resin = require('resin-sdk-preconfigured') + + Promise.props + userId: resin.auth.getUserId() + username: resin.auth.whoami() + apiUrl: resin.settings.get('apiUrl') + vpnUrl: resin.settings.get('vpnUrl') + registryUrl: resin.settings.get('registryUrl') + deltaUrl: resin.settings.get('deltaUrl') + pubNubKeys: resin.models.config.getPubNubKeys() + mixpanelToken: resin.models.config.getMixpanelToken() + .then (results) -> + deviceConfig.generate + application: application + user: + id: results.userId + username: results.username + endpoints: + api: results.apiUrl + vpn: results.vpnUrl + registry: results.registryUrl + delta: results.deltaUrl + pubnub: results.pubNubKeys + mixpanel: + token: results.mixpanelToken + +generateApplicationConfig = (application, options) -> + generateBaseConfig(application) + .tap (config) -> + authenticateWithApplicationKey(config, application.id) + +generateDeviceConfig = (device, deviceApiKey, options) -> + resin = require('resin-sdk-preconfigured') + + resin.models.application.get(device.application_name) + .then (application) -> + generateBaseConfig(application, options) + .tap (config) -> + # Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain + # the expected version for this config and generate one when we know it's safe, + # but instead for now we fall back to app keys unsless the user has explicitly opted in. + if deviceApiKey? + authenticateWithDeviceKey(config, device.uuid, deviceApiKey) + else + authenticateWithApplicationKey(config, application.id) + .then (config) -> + # Associate a device, to prevent the supervisor + # from creating another one on its own. + config.registered_at = Math.floor(Date.now() / 1000) + config.deviceId = device.id + + return config + +authenticateWithApplicationKey = (config, applicationNameOrId) -> + resin = require('resin-sdk-preconfigured') + resin.models.application.generateApiKey(applicationNameOrId) + .then (apiKey) -> + config.apiKey = apiKey + return config + +authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) -> + Promise = require('bluebird') + resin = require('resin-sdk-preconfigured') + + Promise.try -> + customDeviceApiKey || resin.models.device.generateDeviceKey(uuid) + .then (deviceApiKey) -> + config.uuid = uuid + config.deviceApiKey = deviceApiKey + return config From a8f1d16b26d8dfac144f571da0f404cdb602f918 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Fri, 13 Oct 2017 20:23:19 +0200 Subject: [PATCH 5/7] Make `resin os configure` safe with device keys for all ResinOS versions --- build/actions/config.js | 88 ++-------------------------------- build/actions/os.js | 15 +++--- build/utils/config.js | 99 +++++++++++++++++++++++++++++++++++++++ lib/actions/config.coffee | 75 +---------------------------- lib/actions/os.coffee | 16 +++---- lib/utils/config.coffee | 89 +++++++++++++++++++++++++++++++++++ package.json | 2 +- 7 files changed, 211 insertions(+), 173 deletions(-) create mode 100644 build/utils/config.js create mode 100644 lib/utils/config.coffee diff --git a/build/actions/config.js b/build/actions/config.js index c909c73e..5c34ad4d 100644 --- a/build/actions/config.js +++ b/build/actions/config.js @@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -var authenticateWithApplicationKey, authenticateWithDeviceKey, commandOptions, generateApplicationConfig, generateBaseConfig, generateDeviceConfig; +var commandOptions; commandOptions = require('./command-options'); @@ -210,14 +210,15 @@ exports.generate = { ], permission: 'user', action: function(params, options, done) { - var Promise, _, deviceConfig, form, prettyjson, resin, writeFileAsync; + var Promise, _, deviceConfig, form, generateApplicationConfig, generateDeviceConfig, prettyjson, ref, resin, writeFileAsync; Promise = require('bluebird'); writeFileAsync = Promise.promisify(require('fs').writeFile); - deviceConfig = require('resin-device-config'); resin = require('resin-sdk-preconfigured'); _ = require('lodash'); form = require('resin-cli-form'); + deviceConfig = require('resin-device-config'); prettyjson = require('prettyjson'); + ref = require('../utils/config'), generateDeviceConfig = ref.generateDeviceConfig, generateApplicationConfig = ref.generateApplicationConfig; if ((options.device == null) && (options.application == null)) { throw new Error('You have to pass either a device or an application.\n\nSee the help page for examples:\n\n $ resin help config generate'); } @@ -243,84 +244,3 @@ exports.generate = { }).nodeify(done); } }; - -generateBaseConfig = function(application, options) { - var Promise, deviceConfig, resin; - Promise = require('bluebird'); - deviceConfig = require('resin-device-config'); - resin = require('resin-sdk-preconfigured'); - return Promise.props({ - userId: resin.auth.getUserId(), - username: resin.auth.whoami(), - apiUrl: resin.settings.get('apiUrl'), - vpnUrl: resin.settings.get('vpnUrl'), - registryUrl: resin.settings.get('registryUrl'), - deltaUrl: resin.settings.get('deltaUrl'), - pubNubKeys: resin.models.config.getPubNubKeys(), - mixpanelToken: resin.models.config.getMixpanelToken() - }).then(function(results) { - return deviceConfig.generate({ - application: application, - user: { - id: results.userId, - username: results.username - }, - endpoints: { - api: results.apiUrl, - vpn: results.vpnUrl, - registry: results.registryUrl, - delta: results.deltaUrl - }, - pubnub: results.pubNubKeys, - mixpanel: { - token: results.mixpanelToken - } - }); - }); -}; - -generateApplicationConfig = function(application, options) { - return generateBaseConfig(application).tap(function(config) { - return authenticateWithApplicationKey(config, application.id); - }); -}; - -generateDeviceConfig = function(device, deviceApiKey, options) { - var resin; - resin = require('resin-sdk-preconfigured'); - return resin.models.application.get(device.application_name).then(function(application) { - return generateBaseConfig(application, options).tap(function(config) { - if (deviceApiKey != null) { - return authenticateWithDeviceKey(config, device.uuid, deviceApiKey); - } else { - return authenticateWithApplicationKey(config, application.id); - } - }); - }).then(function(config) { - config.registered_at = Math.floor(Date.now() / 1000); - config.deviceId = device.id; - return config; - }); -}; - -authenticateWithApplicationKey = function(config, applicationNameOrId) { - var resin; - resin = require('resin-sdk-preconfigured'); - return resin.models.application.generateApiKey(applicationNameOrId).then(function(apiKey) { - config.apiKey = apiKey; - return config; - }); -}; - -authenticateWithDeviceKey = function(config, uuid, customDeviceApiKey) { - var Promise, resin; - Promise = require('bluebird'); - resin = require('resin-sdk-preconfigured'); - return Promise["try"](function() { - return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); - }).then(function(deviceApiKey) { - config.uuid = uuid; - config.deviceApiKey = deviceApiKey; - return config; - }); -}; diff --git a/build/actions/os.js b/build/actions/os.js index 76b4434d..418f47f8 100644 --- a/build/actions/os.js +++ b/build/actions/os.js @@ -200,23 +200,26 @@ exports.configure = { } ], action: function(params, options, done) { - var Promise, fs, helpers, init, readFileAsync, ref, resin; + var Promise, fs, generateDeviceConfig, helpers, init, readFileAsync, resin; fs = require('fs'); Promise = require('bluebird'); readFileAsync = Promise.promisify(fs.readFile); resin = require('resin-sdk-preconfigured'); init = require('resin-device-init'); helpers = require('../utils/helpers'); + generateDeviceConfig = require('../utils/config').generateDeviceConfig; console.info('Configuring operating system image'); - return Promise.all([ - resin.models.device.get(params.uuid).then(function(device) { + return resin.models.device.get(params.uuid).then(function(device) { + return Promise["try"](function() { if (options.config) { return readFileAsync(options.config, 'utf8').then(JSON.parse); } return buildConfig(params.image, device.device_type, options.advanced); - }), Promise.resolve((ref = params.deviceApiKey) != null ? ref : resin.models.device.generateDeviceKey(params.uuid)) - ]).spread(function(answers, deviceApiKey) { - return init.configure(params.image, params.uuid, deviceApiKey, answers); + }).then(function(answers) { + return generateDeviceConfig(device, params.deviceApiKey, answers).then(function(config) { + return init.configure(params.image, device.device_type, config, answers); + }); + }); }).then(helpers.osProgressHandler).nodeify(done); } }; diff --git a/build/utils/config.js b/build/utils/config.js new file mode 100644 index 00000000..bf0db123 --- /dev/null +++ b/build/utils/config.js @@ -0,0 +1,99 @@ +// Generated by CoffeeScript 1.12.7 + +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ +var authenticateWithApplicationKey, authenticateWithDeviceKey; + +exports.generateBaseConfig = function(application, options) { + var Promise, deviceConfig, resin; + Promise = require('bluebird'); + deviceConfig = require('resin-device-config'); + resin = require('resin-sdk-preconfigured'); + return Promise.props({ + userId: resin.auth.getUserId(), + username: resin.auth.whoami(), + apiUrl: resin.settings.get('apiUrl'), + vpnUrl: resin.settings.get('vpnUrl'), + registryUrl: resin.settings.get('registryUrl'), + deltaUrl: resin.settings.get('deltaUrl'), + pubNubKeys: resin.models.config.getPubNubKeys(), + mixpanelToken: resin.models.config.getMixpanelToken() + }).then(function(results) { + return deviceConfig.generate({ + application: application, + user: { + id: results.userId, + username: results.username + }, + endpoints: { + api: results.apiUrl, + vpn: results.vpnUrl, + registry: results.registryUrl, + delta: results.deltaUrl + }, + pubnub: results.pubNubKeys, + mixpanel: { + token: results.mixpanelToken + } + }); + }); +}; + +exports.generateApplicationConfig = function(application, options) { + return exports.generateBaseConfig(application).tap(function(config) { + return authenticateWithApplicationKey(config, application.id); + }); +}; + +exports.generateDeviceConfig = function(device, deviceApiKey, options) { + var resin; + resin = require('resin-sdk-preconfigured'); + return resin.models.application.get(device.application_name).then(function(application) { + return exports.generateBaseConfig(application, options).tap(function(config) { + if (deviceApiKey != null) { + return authenticateWithDeviceKey(config, device.uuid, deviceApiKey); + } else { + return authenticateWithApplicationKey(config, application.id); + } + }); + }).then(function(config) { + config.registered_at = Math.floor(Date.now() / 1000); + config.deviceId = device.id; + config.uuid = device.uuid; + return config; + }); +}; + +authenticateWithApplicationKey = function(config, applicationNameOrId) { + var resin; + resin = require('resin-sdk-preconfigured'); + return resin.models.application.generateApiKey(applicationNameOrId).then(function(apiKey) { + config.apiKey = apiKey; + return config; + }); +}; + +authenticateWithDeviceKey = function(config, uuid, customDeviceApiKey) { + var Promise, resin; + Promise = require('bluebird'); + resin = require('resin-sdk-preconfigured'); + return Promise["try"](function() { + return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); + }).then(function(deviceApiKey) { + config.deviceApiKey = deviceApiKey; + return config; + }); +}; diff --git a/lib/actions/config.coffee b/lib/actions/config.coffee index 2bef8ebe..f112c37c 100644 --- a/lib/actions/config.coffee +++ b/lib/actions/config.coffee @@ -254,6 +254,7 @@ exports.generate = form = require('resin-cli-form') deviceConfig = require('resin-device-config') prettyjson = require('prettyjson') + { generateDeviceConfig, generateApplicationConfig } = require('../utils/config') if not options.device? and not options.application? throw new Error ''' @@ -284,77 +285,3 @@ exports.generate = console.log(prettyjson.render(config)) .nodeify(done) - -generateBaseConfig = (application, options) -> - Promise = require('bluebird') - deviceConfig = require('resin-device-config') - resin = require('resin-sdk-preconfigured') - - Promise.props - userId: resin.auth.getUserId() - username: resin.auth.whoami() - apiUrl: resin.settings.get('apiUrl') - vpnUrl: resin.settings.get('vpnUrl') - registryUrl: resin.settings.get('registryUrl') - deltaUrl: resin.settings.get('deltaUrl') - pubNubKeys: resin.models.config.getPubNubKeys() - mixpanelToken: resin.models.config.getMixpanelToken() - .then (results) -> - deviceConfig.generate - application: application - user: - id: results.userId - username: results.username - endpoints: - api: results.apiUrl - vpn: results.vpnUrl - registry: results.registryUrl - delta: results.deltaUrl - pubnub: results.pubNubKeys - mixpanel: - token: results.mixpanelToken - -generateApplicationConfig = (application, options) -> - generateBaseConfig(application) - .tap (config) -> - authenticateWithApplicationKey(config, application.id) - -generateDeviceConfig = (device, deviceApiKey, options) -> - resin = require('resin-sdk-preconfigured') - - resin.models.application.get(device.application_name) - .then (application) -> - generateBaseConfig(application, options) - .tap (config) -> - # Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain - # the expected version for this config and generate one when we know it's safe, - # but instead for now we fall back to app keys unsless the user has explicitly opted in. - if deviceApiKey? - authenticateWithDeviceKey(config, device.uuid, deviceApiKey) - else - authenticateWithApplicationKey(config, application.id) - .then (config) -> - # Associate a device, to prevent the supervisor - # from creating another one on its own. - config.registered_at = Math.floor(Date.now() / 1000) - config.deviceId = device.id - - return config - -authenticateWithApplicationKey = (config, applicationNameOrId) -> - resin = require('resin-sdk-preconfigured') - resin.models.application.generateApiKey(applicationNameOrId) - .then (apiKey) -> - config.apiKey = apiKey - return config - -authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) -> - Promise = require('bluebird') - resin = require('resin-sdk-preconfigured') - - Promise.try -> - customDeviceApiKey || resin.models.device.generateDeviceKey(uuid) - .then (deviceApiKey) -> - config.uuid = uuid - config.deviceApiKey = deviceApiKey - return config diff --git a/lib/actions/os.coffee b/lib/actions/os.coffee index 94bd50ab..7dba0722 100644 --- a/lib/actions/os.coffee +++ b/lib/actions/os.coffee @@ -222,20 +222,20 @@ exports.configure = resin = require('resin-sdk-preconfigured') init = require('resin-device-init') helpers = require('../utils/helpers') + { generateDeviceConfig } = require('../utils/config') console.info('Configuring operating system image') - Promise.all [ - resin.models.device.get(params.uuid) - .then (device) -> + resin.models.device.get(params.uuid) + .then (device) -> + Promise.try -> if options.config return readFileAsync(options.config, 'utf8') .then(JSON.parse) return buildConfig(params.image, device.device_type, options.advanced) - , - Promise.resolve(params.deviceApiKey ? resin.models.device.generateDeviceKey(params.uuid)) - ] - .spread (answers, deviceApiKey) -> - init.configure(params.image, params.uuid, deviceApiKey, answers) + .then (answers) -> + generateDeviceConfig(device, params.deviceApiKey, answers) + .then (config) -> + init.configure(params.image, device.device_type, config, answers) .then(helpers.osProgressHandler) .nodeify(done) diff --git a/lib/utils/config.coffee b/lib/utils/config.coffee new file mode 100644 index 00000000..cf802d46 --- /dev/null +++ b/lib/utils/config.coffee @@ -0,0 +1,89 @@ +### +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +### + +exports.generateBaseConfig = (application, options) -> + Promise = require('bluebird') + deviceConfig = require('resin-device-config') + resin = require('resin-sdk-preconfigured') + + Promise.props + userId: resin.auth.getUserId() + username: resin.auth.whoami() + apiUrl: resin.settings.get('apiUrl') + vpnUrl: resin.settings.get('vpnUrl') + registryUrl: resin.settings.get('registryUrl') + deltaUrl: resin.settings.get('deltaUrl') + pubNubKeys: resin.models.config.getPubNubKeys() + mixpanelToken: resin.models.config.getMixpanelToken() + .then (results) -> + deviceConfig.generate + application: application + user: + id: results.userId + username: results.username + endpoints: + api: results.apiUrl + vpn: results.vpnUrl + registry: results.registryUrl + delta: results.deltaUrl + pubnub: results.pubNubKeys + mixpanel: + token: results.mixpanelToken + +exports.generateApplicationConfig = (application, options) -> + exports.generateBaseConfig(application) + .tap (config) -> + authenticateWithApplicationKey(config, application.id) + +exports.generateDeviceConfig = (device, deviceApiKey, options) -> + resin = require('resin-sdk-preconfigured') + + resin.models.application.get(device.application_name) + .then (application) -> + exports.generateBaseConfig(application, options) + .tap (config) -> + # Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain + # the expected version for this config and generate one when we know it's safe, + # but instead for now we fall back to app keys unless the user has explicitly opted in. + if deviceApiKey? + authenticateWithDeviceKey(config, device.uuid, deviceApiKey) + else + authenticateWithApplicationKey(config, application.id) + .then (config) -> + # Associate a device, to prevent the supervisor + # from creating another one on its own. + config.registered_at = Math.floor(Date.now() / 1000) + config.deviceId = device.id + config.uuid = device.uuid + + return config + +authenticateWithApplicationKey = (config, applicationNameOrId) -> + resin = require('resin-sdk-preconfigured') + resin.models.application.generateApiKey(applicationNameOrId) + .then (apiKey) -> + config.apiKey = apiKey + return config + +authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) -> + Promise = require('bluebird') + resin = require('resin-sdk-preconfigured') + + Promise.try -> + customDeviceApiKey || resin.models.device.generateDeviceKey(uuid) + .then (deviceApiKey) -> + config.deviceApiKey = deviceApiKey + return config diff --git a/package.json b/package.json index 14b34112..9d268957 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "resin-cli-visuals": "^1.4.0", "resin-config-json": "^1.0.0", "resin-device-config": "^4.0.0", - "resin-device-init": "^3.0.0", + "resin-device-init": "^4.0.0", "resin-docker-build": "^0.4.0", "resin-doodles": "0.0.1", "resin-image-fs": "^2.3.0", From 2d43e47610dfb71ae9b82fda36c5646cb389d79e Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Fri, 13 Oct 2017 20:29:47 +0200 Subject: [PATCH 6/7] Add device api keys warning on device register and os configure --- build/actions/device.js | 2 +- build/actions/os.js | 2 +- doc/cli.markdown | 4 ++++ lib/actions/device.coffee | 2 ++ lib/actions/os.coffee | 2 ++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/build/actions/device.js b/build/actions/device.js index 29d9aab0..2ae14456 100644 --- a/build/actions/device.js +++ b/build/actions/device.js @@ -84,7 +84,7 @@ exports.supported = { exports.register = { signature: 'device register ', description: 'register a device', - help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp\n $ resin device register MyApp --uuid \n $ resin device register MyApp --uuid --device-api-key ', + help: 'Use this command to register a device to an application.\n\nNote that device api keys are only supported on ResinOS 2.0.3+\n\nExamples:\n\n $ resin device register MyApp\n $ resin device register MyApp --uuid \n $ resin device register MyApp --uuid --device-api-key ', permission: 'user', options: [ { diff --git a/build/actions/os.js b/build/actions/os.js index 418f47f8..13198d99 100644 --- a/build/actions/os.js +++ b/build/actions/os.js @@ -190,7 +190,7 @@ exports.buildConfig = { exports.configure = { signature: 'os configure [deviceApiKey]', description: 'configure an os image', - help: 'Use this command to configure a previously downloaded operating system image for the specific device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6\n $ resin os configure ../path/rpi.img 7cf02a6 ', + help: 'Use this command to configure a previously downloaded operating system image for the specific device.\n\nNote that device api keys are only supported on ResinOS 2.0.3+\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6\n $ resin os configure ../path/rpi.img 7cf02a6 ', permission: 'user', options: [ commandOptions.advancedConfig, { diff --git a/doc/cli.markdown b/doc/cli.markdown index 65d6207c..d07eeb10 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -331,6 +331,8 @@ Examples: Use this command to register a device to an application. +Note that device api keys are only supported on ResinOS 2.0.3+ + Examples: $ resin device register MyApp @@ -908,6 +910,8 @@ the path to the output JSON file Use this command to configure a previously downloaded operating system image for the specific device. +Note that device api keys are only supported on ResinOS 2.0.3+ + Examples: $ resin os configure ../path/rpi.img 7cf02a6 diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index 6a6ab11b..30842740 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -131,6 +131,8 @@ exports.register = help: ''' Use this command to register a device to an application. + Note that device api keys are only supported on ResinOS 2.0.3+ + Examples: $ resin device register MyApp diff --git a/lib/actions/os.coffee b/lib/actions/os.coffee index 7dba0722..b055d965 100644 --- a/lib/actions/os.coffee +++ b/lib/actions/os.coffee @@ -201,6 +201,8 @@ exports.configure = help: ''' Use this command to configure a previously downloaded operating system image for the specific device. + Note that device api keys are only supported on ResinOS 2.0.3+ + Examples: $ resin os configure ../path/rpi.img 7cf02a6 From 3f692ecbb06119b28fbf0037c514eb705ffbfabd Mon Sep 17 00:00:00 2001 From: "resin-io-versionbot[bot]" Date: Wed, 18 Oct 2017 18:18:01 +0000 Subject: [PATCH 7/7] v6.7.0 --- CHANGELOG.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5225a540..7b5a9587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! This project adheres to [Semantic Versioning](http://semver.org/). +## v6.7.0 - 2017-10-18 + +* Added a device api key parameter to the `os configure` command. #487 [Pagan Gazzard] +* Added a `--device-api-key` option to the `config generate` command. #487 [Pagan Gazzard] +* Added a `--device-api-key` option to the `device register` command. #487 [Pagan Gazzard] + ## v6.6.13 - 2017-10-18 * Fix issue where `os download` would always download prod images #689 [Tim Perry] diff --git a/package.json b/package.json index 9d268957..fd1658b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resin-cli", - "version": "6.6.13", + "version": "6.7.0", "description": "The official resin.io CLI tool", "main": "./build/actions/index.js", "homepage": "https://github.com/resin-io/resin-cli", @@ -105,4 +105,4 @@ "optionalDependencies": { "removedrive": "^1.0.0" } -} +} \ No newline at end of file