2018-08-13 21:10:53 +02:00
'use strict' ;
exports = module . exports = {
start : start ,
stop : stop
} ;
2018-08-14 19:35:14 -07:00
var apps = require ( './apps.js' ) ,
AppsError = apps . AppsError ,
assert = require ( 'assert' ) ,
2018-08-13 21:10:53 +02:00
config = require ( './config.js' ) ,
2018-08-14 18:27:08 -07:00
express = require ( 'express' ) ,
2018-08-13 22:06:28 +02:00
debug = require ( 'debug' ) ( 'box:dockerproxy' ) ,
2018-08-13 22:14:56 +02:00
http = require ( 'http' ) ,
2018-08-14 19:35:14 -07:00
HttpError = require ( 'connect-lastmile' ) . HttpError ,
2018-08-14 18:27:08 -07:00
middleware = require ( './middleware' ) ,
2018-08-14 20:20:19 -07:00
net = require ( 'net' ) ,
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
safe = require ( 'safetydance' ) ,
_ = require ( 'underscore' ) ;
2018-08-13 21:10:53 +02:00
2018-08-14 19:03:10 -07:00
var gHttpServer = null ;
2018-08-13 21:10:53 +02:00
2018-08-14 18:27:08 -07:00
function authorizeApp ( req , res , next ) {
// TODO add here some authorization
// - block apps not using the docker addon
// - block calls regarding platform containers
// - only allow managing and inspection of containers belonging to the app
2018-08-13 21:10:53 +02:00
2018-08-14 19:35:14 -07:00
if ( config . TEST ) return next ( ) ; // make the tests pass for now
apps . getByIpAddress ( req . connection . remoteAddress , function ( error , app ) {
if ( error && error . reason === AppsError . NOT _FOUND ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
if ( ! ( 'docker' in app . manifest . addons ) ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
req . app = app ;
next ( ) ;
} ) ;
2018-08-14 18:27:08 -07:00
}
2018-08-13 22:14:56 +02:00
2018-08-14 18:27:08 -07:00
function attachDockerRequest ( req , res , next ) {
var options = {
socketPath : '/var/run/docker.sock' ,
method : req . method ,
path : req . url ,
headers : req . headers
} ;
2018-08-13 22:14:56 +02:00
2018-08-14 18:27:08 -07:00
req . dockerRequest = http . request ( options , function ( dockerResponse ) {
res . writeHead ( dockerResponse . statusCode , dockerResponse . headers ) ;
2018-08-13 21:10:53 +02:00
2018-08-14 18:27:08 -07:00
// Force node to send out the headers, this is required for the /container/wait api to make the docker cli proceed
res . write ( ' ' ) ;
2018-08-13 21:10:53 +02:00
2018-08-14 18:27:08 -07:00
dockerResponse . on ( 'error' , function ( error ) { console . error ( 'dockerResponse error:' , error ) ; } ) ;
dockerResponse . pipe ( res , { end : true } ) ;
} ) ;
2018-08-13 21:10:53 +02:00
2018-08-14 18:27:08 -07:00
next ( ) ;
}
2018-08-13 22:01:51 +02:00
2018-08-14 18:27:08 -07:00
function containersCreate ( req , res , next ) {
2018-08-14 20:20:19 -07:00
safe . set ( req . body , 'HostConfig.NetworkMode' , 'cloudron' ) ; // overwrite the network the container lives in
safe . set ( req . body , 'Config.Labels' , _ . extend ( { } , safe . query ( req . body , 'Config.Labels' ) , { appId : req . app . id } ) ) ; // overwrite the app id to track containers of an app
let binds = [ ] ;
for ( let bind of ( req . body . HostConfig . Binds || [ ] ) ) {
if ( ! bind . startsWith ( '/app/data' ) ) {
debug ( ` Dropping mount ${ bind } ` ) ;
continue ;
}
binds . push ( bind . replace ( new RegExp ( '^/app/data' ) , path . join ( paths . APPS _DATA _DIR , req . app . id , 'data' ) ) ) ;
}
safe . set ( req . body , 'HostConfig.Binds' , binds ) ;
2018-08-13 22:14:56 +02:00
2018-08-14 19:35:14 -07:00
let plainBody = JSON . stringify ( req . body ) ;
2018-08-13 22:14:56 +02:00
2018-08-14 18:27:08 -07:00
req . dockerRequest . setHeader ( 'Content-Length' , Buffer . byteLength ( plainBody ) ) ;
req . dockerRequest . end ( plainBody ) ;
}
function process ( req , res , next ) {
if ( ! req . readable ) {
req . dockerRequest . end ( ) ;
} else {
req . pipe ( req . dockerRequest , { end : true } ) ;
}
}
function start ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-08-14 19:03:10 -07:00
assert ( gHttpServer === null , 'Already started' ) ;
2018-08-13 21:10:53 +02:00
2018-08-14 18:27:08 -07:00
let json = middleware . json ( { strict : true } ) ;
let router = new express . Router ( ) ;
2018-08-14 20:20:19 -07:00
router . post ( '/:version/containers/create' , containersCreate ) ;
2018-08-13 22:14:56 +02:00
2018-08-14 19:35:14 -07:00
let proxyServer = express ( ) ;
2018-08-14 18:27:08 -07:00
proxyServer . use ( authorizeApp )
. use ( attachDockerRequest )
2018-08-14 19:35:14 -07:00
. use ( json )
2018-08-14 18:27:08 -07:00
. use ( router )
2018-08-14 19:35:14 -07:00
. use ( process )
. use ( middleware . lastMile ( ) ) ;
2018-08-14 22:52:00 +02:00
2018-08-14 19:03:10 -07:00
gHttpServer = http . createServer ( proxyServer ) ;
2018-08-14 19:35:14 -07:00
gHttpServer . listen ( config . get ( 'dockerProxyPort' ) , '0.0.0.0' , callback ) ;
2018-08-14 22:52:00 +02:00
2018-08-14 18:27:08 -07:00
debug ( ` startDockerProxy: started proxy on port ${ config . get ( 'dockerProxyPort' ) } ` ) ;
2018-08-13 22:14:56 +02:00
2018-08-14 19:03:10 -07:00
gHttpServer . on ( 'upgrade' , function ( req , client , head ) {
2018-08-13 22:14:56 +02:00
// Create a new tcp connection to the TCP server
var remote = net . connect ( '/var/run/docker.sock' , function ( ) {
// two-way pipes between client and docker daemon
client . pipe ( remote ) . pipe ( client ) ;
// resend the upgrade event to the docker daemon, so it responds with the proper message through the pipes
remote . write ( req . method + ' ' + req . url + ' HTTP/1.1\r\n' +
` Host: ${ req . headers . host } \r \n ` +
'Connection: Upgrade\r\n' +
'Upgrade: tcp\r\n' +
'\r\n'
) ;
} ) ;
} ) ;
2018-08-13 21:10:53 +02:00
}
function stop ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-08-14 19:03:10 -07:00
if ( gHttpServer ) gHttpServer . close ( ) ;
gHttpServer = null ;
2018-08-13 21:10:53 +02:00
callback ( ) ;
}