Auto-merge for PR #463 via VersionBot

Allow registering the deviceApiKey in a non-compatible OS by making the apiKey equal the deviceApiKey, and add an fsync to all config.json writes
This commit is contained in:
resin-io-versionbot[bot] 2017-07-01 03:27:15 +00:00 committed by GitHub
commit a6409f0e12
3 changed files with 63 additions and 23 deletions

View File

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## v5.0.1 - 2017-07-01
* Allow registering the deviceApiKey in a non-compatible OS by making the apiKey equal the deviceApiKey, and add an fsync to all config.json writes [Pablo Carranza Velez]
## v5.0.0 - 2017-06-26 ## v5.0.0 - 2017-06-26
* Remove the undocumented and unused sideload and compose APIs [Pablo Carranza Velez] * Remove the undocumented and unused sideload and compose APIs [Pablo Carranza Velez]

View File

@ -1,7 +1,7 @@
{ {
"name": "resin-supervisor", "name": "resin-supervisor",
"description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.", "description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.",
"version": "5.0.0", "version": "5.0.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -21,6 +21,15 @@ exports.ExchangeKeyError = class ExchangeKeyError extends TypedError
bootstrapper = {} bootstrapper = {}
writeAndSyncFile = (path, data) ->
fs.openAsync(path, 'w')
.then (fd) ->
fs.writeAsync(fd, data, 0, 'utf8')
.then ->
fs.fsyncAsync(fd)
.then ->
fs.closeAsync(fd)
loadPreloadedApps = -> loadPreloadedApps = ->
devConfig = {} devConfig = {}
knex('app').truncate() knex('app').truncate()
@ -64,12 +73,16 @@ exchangeKey = ->
.then (device) -> .then (device) ->
if not device? if not device?
throw new ExchangeKeyError("Couldn't fetch device with provisioning key") throw new ExchangeKeyError("Couldn't fetch device with provisioning key")
# We found the device, we can try to generate a working device key for it # We found the device, we can try to register a working device key for it
request.postAsync("#{config.apiEndpoint}/api-key/device/#{device.id}/device-key?apikey=#{userConfig.apiKey}") userConfig.deviceApiKey ?= deviceRegister.generateUniqueKey()
request.postAsync("#{config.apiEndpoint}/api-key/device/#{device.id}/device-key?apikey=#{userConfig.apiKey}", {
json: true
body:
apiKey: userConfig.deviceApiKey
})
.spread (res, body) -> .spread (res, body) ->
if res.status != 200 if res.statusCode != 200
throw new ExchangeKeyError("Couldn't generate device key with provisioning key") throw new ExchangeKeyError("Couldn't register device key with provisioning key")
userConfig.deviceApiKey = body
.return(device) .return(device)
bootstrap = -> bootstrap = ->
@ -100,9 +113,15 @@ bootstrap = ->
.then ({ id }) -> .then ({ id }) ->
userConfig.registered_at = Date.now() userConfig.registered_at = Date.now()
userConfig.deviceId = id userConfig.deviceId = id
# Delete the provisioning key now. osRelease.getOSVersion(config.hostOSVersionPath)
delete userConfig.apiKey .then (osVersion) ->
fs.writeFileAsync(configPath, JSON.stringify(userConfig)) # Delete the provisioning key now, only if the OS supports it
hasSupport = hasDeviceApiKeySupport(osVersion)
if hasSupport
delete userConfig.apiKey
else
userConfig.apiKey = userConfig.deviceApiKey
writeAndSyncFile(configPath, JSON.stringify(userConfig))
.return(userConfig) .return(userConfig)
.then (userConfig) -> .then (userConfig) ->
console.log('Finishing bootstrapping') console.log('Finishing bootstrapping')
@ -132,7 +151,7 @@ generateRegistration = (forceReregister = false) ->
else else
userConfig.uuid ?= deviceRegister.generateUniqueKey() userConfig.uuid ?= deviceRegister.generateUniqueKey()
userConfig.deviceApiKey ?= deviceRegister.generateUniqueKey() userConfig.deviceApiKey ?= deviceRegister.generateUniqueKey()
fs.writeFileAsync(configPath, JSON.stringify(userConfig)) writeAndSyncFile(configPath, JSON.stringify(userConfig))
.return(userConfig.uuid) .return(userConfig.uuid)
.catch (err) -> .catch (err) ->
console.log('Error generating and saving UUID: ', err) console.log('Error generating and saving UUID: ', err)
@ -148,26 +167,43 @@ bootstrapOrRetry = ->
utils.mixpanelTrack('Device bootstrap failed, retrying', { error: err, delay: config.bootstrapRetryDelay }) utils.mixpanelTrack('Device bootstrap failed, retrying', { error: err, delay: config.bootstrapRetryDelay })
setTimeout(bootstrapOrRetry, config.bootstrapRetryDelay) setTimeout(bootstrapOrRetry, config.bootstrapRetryDelay)
hasDeviceApiKeySupport = (osVersion) ->
try
!/^Resin OS /.test(osVersion) or semver.gte(semverRegex.test(osVersion), '2.0.2')
catch err
console.error('Unable to determine if device has deviceApiKey support', err, err.stack)
false
bootstrapper.done = new Promise (resolve) -> bootstrapper.done = new Promise (resolve) ->
bootstrapper.doneBootstrapping = -> bootstrapper.doneBootstrapping = ->
bootstrapper.bootstrapped = true bootstrapper.bootstrapped = true
resolve(userConfig) resolve(userConfig)
# If we're still using an old api key we can try to exchange it for a valid device key # If we're still using an old api key we can try to exchange it for a valid device key
# This will only be the case when the supervisor/OS has been updated.
if userConfig.apiKey? if userConfig.apiKey?
Promise.join( # Only do a key exchange and delete the provisioning key if we're on a Resin OS version
osRelease.getOSVersion(config.hostOSVersionPath) # that supports using the deviceApiKey (2.0.2 and above)
exchangeKey() # or if we're in a non-Resin OS (which is assumed to be updated enough).
(osVersion) -> # Otherwise VPN and other host services that use an API key will break.
# Only delete the provisioning key if we're on a Resin OS version that supports using the deviceApiKey (2.0.2 and above) #
# or if we're in a non-Resin OS (which is assumed to be updated enough). # In other cases, we make the apiKey equal the deviceApiKey instead.
# Otherwise VPN and other host services that use an API key will break. osRelease.getOSVersion(config.hostOSVersionPath)
hasDeviceApiKeySupport = !/^Resin OS /.test(osVersion) or semver.gte(semverRegex.test(osVersion), '2.0.2') .then (osVersion) ->
delete userConfig.apiKey if hasDeviceApiKeySupport hasSupport = hasDeviceApiKeySupport(osVersion)
utils.setConfig('apiKey', userConfig.deviceApiKey) if hasSupport or userConfig.apiKey != userConfig.deviceApiKey
console.log('Attempting key exchange')
exchangeKey()
.then -> .then ->
fs.writeFileAsync(configPath, JSON.stringify(userConfig)) console.log('Key exchange succeeded, starting to use deviceApiKey')
) if hasSupport
return delete userConfig.apiKey
else
userConfig.apiKey = userConfig.deviceApiKey
utils.setConfig('apiKey', userConfig.deviceApiKey)
.then ->
writeAndSyncFile(configPath, JSON.stringify(userConfig))
# We return immediately, and eventually the API key will be exchanged and replaced.
return
bootstrapper.bootstrapped = false bootstrapper.bootstrapped = false
bootstrapper.startBootstrapping = -> bootstrapper.startBootstrapping = ->