2017-12-08 01:18:21 +00:00
|
|
|
Promise = require 'bluebird'
|
2017-10-30 21:54:02 +00:00
|
|
|
express = require 'express'
|
|
|
|
bufferEq = require 'buffer-equal-constant-time'
|
|
|
|
blink = require './lib/blink'
|
|
|
|
iptables = require './lib/iptables'
|
|
|
|
{ checkTruthy } = require './lib/validation'
|
|
|
|
|
|
|
|
authenticate = (config) ->
|
|
|
|
return (req, res, next) ->
|
|
|
|
queryKey = req.query.apikey
|
|
|
|
header = req.get('Authorization') ? ''
|
|
|
|
match = header.match(/^ApiKey (\w+)$/)
|
|
|
|
headerKey = match?[1]
|
2018-12-12 12:42:53 +00:00
|
|
|
config.getMany([ 'apiSecret', 'localMode', 'unmanaged', 'osVariant' ])
|
2017-10-30 21:54:02 +00:00
|
|
|
.then (conf) ->
|
2018-12-12 12:42:53 +00:00
|
|
|
needsAuth = if conf.unmanaged
|
|
|
|
conf.osVariant is 'prod'
|
2017-10-30 21:54:02 +00:00
|
|
|
else
|
2018-12-12 12:42:53 +00:00
|
|
|
not conf.localMode
|
|
|
|
|
|
|
|
if needsAuth
|
|
|
|
key = queryKey ? headerKey
|
|
|
|
if bufferEq(Buffer.from(key), Buffer.from(conf.apiSecret))
|
|
|
|
next()
|
|
|
|
else
|
|
|
|
res.sendStatus(401)
|
|
|
|
else
|
|
|
|
next()
|
2017-10-30 21:54:02 +00:00
|
|
|
.catch (err) ->
|
2018-12-12 12:42:53 +00:00
|
|
|
res.status(503).send("Unexpected error: #{err}")
|
2017-10-30 21:54:02 +00:00
|
|
|
|
|
|
|
module.exports = class SupervisorAPI
|
2017-12-08 01:18:21 +00:00
|
|
|
constructor: ({ @config, @eventTracker, @routers, @healthchecks }) ->
|
2017-10-30 21:54:02 +00:00
|
|
|
@server = null
|
|
|
|
@_api = express()
|
2018-10-16 11:26:21 +00:00
|
|
|
@_api.disable('x-powered-by')
|
2017-10-30 21:54:02 +00:00
|
|
|
|
2017-12-08 01:18:21 +00:00
|
|
|
@_api.get '/v1/healthy', (req, res) =>
|
|
|
|
Promise.map @healthchecks, (fn) ->
|
|
|
|
fn()
|
|
|
|
.then (healthy) ->
|
|
|
|
if !healthy
|
|
|
|
throw new Error('Unhealthy')
|
|
|
|
.then ->
|
|
|
|
res.sendStatus(200)
|
|
|
|
.catch ->
|
|
|
|
res.sendStatus(500)
|
|
|
|
|
2017-10-30 21:54:02 +00:00
|
|
|
@_api.use(authenticate(@config))
|
|
|
|
|
|
|
|
@_api.get '/ping', (req, res) ->
|
|
|
|
res.send('OK')
|
|
|
|
|
|
|
|
@_api.post '/v1/blink', (req, res) =>
|
|
|
|
@eventTracker.track('Device blink')
|
|
|
|
blink.pattern.start()
|
|
|
|
setTimeout(blink.pattern.stop, 15000)
|
|
|
|
res.sendStatus(200)
|
|
|
|
|
|
|
|
# Expires the supervisor's API key and generates a new one.
|
2018-10-18 18:52:35 +00:00
|
|
|
# It also communicates the new key to the balena API.
|
2017-10-30 21:54:02 +00:00
|
|
|
@_api.post '/v1/regenerate-api-key', (req, res) =>
|
|
|
|
@config.newUniqueKey()
|
|
|
|
.then (secret) =>
|
|
|
|
@config.set(apiSecret: secret)
|
|
|
|
.then ->
|
|
|
|
res.status(200).send(secret)
|
|
|
|
.catch (err) ->
|
|
|
|
res.status(503).send(err?.message or err or 'Unknown error')
|
|
|
|
|
2017-12-08 01:18:21 +00:00
|
|
|
|
2017-11-29 21:32:57 +00:00
|
|
|
for router in @routers
|
2017-10-30 21:54:02 +00:00
|
|
|
@_api.use(router)
|
|
|
|
|
|
|
|
listen: (allowedInterfaces, port, apiTimeout) =>
|
2018-09-28 13:32:38 +00:00
|
|
|
@config.get('localMode').then (localMode) =>
|
|
|
|
@applyListeningRules(checkTruthy(localMode), port, allowedInterfaces)
|
|
|
|
.then =>
|
|
|
|
# Monitor the switching of local mode, and change which interfaces will
|
|
|
|
# be listented to based on that
|
|
|
|
@config.on 'change', (changedConfig) =>
|
|
|
|
if changedConfig.localMode?
|
|
|
|
@applyListeningRules(changedConfig.localMode, port, allowedInterfaces)
|
2017-10-30 21:54:02 +00:00
|
|
|
.then =>
|
|
|
|
@server = @_api.listen(port)
|
|
|
|
@server.timeout = apiTimeout
|
|
|
|
|
2018-09-28 13:32:38 +00:00
|
|
|
applyListeningRules: (allInterfaces, port, allowedInterfaces) =>
|
|
|
|
Promise.try ->
|
|
|
|
if checkTruthy(allInterfaces)
|
|
|
|
iptables.removeRejections(port).then ->
|
|
|
|
console.log('Supervisor API listening on all interfaces')
|
|
|
|
else
|
|
|
|
iptables.rejectOnAllInterfacesExcept(allowedInterfaces, port).then ->
|
|
|
|
console.log('Supervisor API listening on allowed interfaces only')
|
|
|
|
.catch (e) =>
|
|
|
|
# If there's an error, stop the supervisor api from answering any endpoints,
|
|
|
|
# and this will eventually be restarted by the healthcheck
|
|
|
|
console.log('Error on switching supervisor API listening rules - stopping API.')
|
|
|
|
console.log(' ', e)
|
|
|
|
if @server?
|
|
|
|
@stop()
|
|
|
|
|
2017-10-30 21:54:02 +00:00
|
|
|
stop: ->
|
|
|
|
@server.close()
|