Make local mode only work in development OS, and make it remove app containers and allow unauthenticated API requests

Local mode makes the API accept unauthenticated requests.
Local mode now also removes app containers when stopping them.

Local mode only works on a host OS that has `VARIANT_ID = "dev"` in /etc/os-release.

Also add more explicit logging when stopping an app and it was already stopped
or the container was already removed.

Change-Type: patch
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2017-03-08 16:02:56 -03:00 committed by Pablo Carranza Vélez
parent 72f6b2cea5
commit 208b799c4b
4 changed files with 43 additions and 19 deletions

View File

@ -27,6 +27,8 @@ module.exports = (application) ->
next()
else if headerKey? && bufferEq(new Buffer(headerKey), new Buffer(secret))
next()
else if application.localMode
next()
else
res.sendStatus(401)
.catch (err) ->

View File

@ -31,6 +31,12 @@ logTypes =
stopAppSuccess:
eventName: 'Application stop'
humanName: 'Killed application'
stopAppNoop:
eventName: 'Application already stopped'
humanName: 'Application is already stopped, removing container'
stopRemoveAppNoop:
eventName: 'Application already stopped and container removed'
humanName: 'Application is already stopped and the container removed'
stopAppError:
eventName: 'Application stop error'
humanName: 'Failed to kill application'
@ -160,10 +166,12 @@ application.kill = kill = (app, { updateDB = true, removeContainer = true } = {}
statusCode = '' + err.statusCode
# 304 means the container was already stopped - so we can just remove it
if statusCode is '304'
logSystemEvent(logTypes.stopAppNoop, app)
container.removeAsync(v: true) if removeContainer
return
# 404 means the container doesn't exist, precisely what we want! :D
if statusCode is '404'
logSystemEvent(logTypes.stopRemoveAppNoop, app)
return
throw err
.tap ->
@ -412,22 +420,27 @@ apiPollInterval = (val) ->
setLocalMode = (val) ->
mode = checkTruthy(val) ? false
Promise.try ->
if mode and !application.localMode
logSystemMessage('Entering local mode, app will be forcefully stopped', {}, 'Enter local mode')
Promise.map utils.getKnexApps(), (theApp) ->
Promise.using application.lockUpdates(theApp.appId, true), ->
# There's a slight chance the app changed after the previous select
# So we fetch it again now the lock is acquired
utils.getKnexApp(theApp.appId)
.then (app) ->
application.kill(app, removeContainer: false) if app?
else if !mode and application.localMode
logSystemMessage('Exiting local mode, app will be resumed', {}, 'Exit local mode')
Promise.map utils.getKnexApps(), (app) ->
unlockAndStart(app)
.then ->
application.localMode = mode
device.getOSVariant()
.then (variant) ->
if variant is not 'dev'
logSystemMessage('Not a development OS, ignoring local mode', {}, 'Ignore local mode')
return
Promise.try ->
if mode and !application.localMode
logSystemMessage('Entering local mode, app will be forcefully stopped', {}, 'Enter local mode')
Promise.map utils.getKnexApps(), (theApp) ->
Promise.using application.lockUpdates(theApp.appId, true), ->
# There's a slight chance the app changed after the previous select
# So we fetch it again now the lock is acquired
utils.getKnexApp(theApp.appId)
.then (app) ->
application.kill(app) if app?
else if !mode and application.localMode
logSystemMessage('Exiting local mode, app will be resumed', {}, 'Exit local mode')
Promise.map utils.getKnexApps(), (app) ->
unlockAndStart(app)
.then ->
application.localMode = mode
specialActionConfigVars = [
[ 'RESIN_SUPERVISOR_LOCAL_MODE', setLocalMode ]

View File

@ -229,4 +229,10 @@ exports.getOSVersion = memoizePromise ->
exports.isResinOSv1 = memoizePromise ->
exports.getOSVersion().then (osVersion) ->
return true if /^Resin OS 1./.test(osVersion)
return false
return false
exports.getOSVariant = memoizePromise ->
utils.getOSReleaseField(config.hostOsVersionPath, 'VARIANT_ID')
.catch (err) ->
console.error('Failed to get OS variant', err, err.stack)
return undefined

View File

@ -230,7 +230,7 @@ exports.getKnexApp = (appId, columns) ->
exports.getKnexApps = (columns) ->
knex('app').select(columns)
exports.getOSVersion = (path) ->
exports.getOSReleaseField = (path, field) ->
fs.readFileAsync(path)
.then (releaseData) ->
lines = releaseData.toString().split('\n')
@ -239,7 +239,10 @@ exports.getOSVersion = (path) ->
[ key, val ] = line.split('=')
releaseItems[_.trim(key)] = _.trim(val)
# Remove enclosing quotes: http://stackoverflow.com/a/19156197/2549019
return releaseItems['PRETTY_NAME'].replace(/^"(.+(?="$))"$/, '$1')
return releaseItems[field].replace(/^"(.+(?="$))"$/, '$1')
exports.getOSVersion = (path) ->
exports.getOSReleaseField(path, 'PRETTY_NAME')
.catch (err) ->
console.log('Could not get OS Version: ', err, err.stack)
return undefined