AJ ONeal
7 years ago
9 changed files with 114 additions and 605 deletions
@ -1,59 +0,0 @@ |
|||
'use strict'; |
|||
|
|||
// Note the odd use of callbacks (instead of promises) here
|
|||
// It's to avoid loading bluebird yet (see sni-server.js for explanation)
|
|||
module.exports.create = function (lex, certPaths, port, conf, serverCallback) { |
|||
function initServer(err, server) { |
|||
var app; |
|||
var promiseApp; |
|||
|
|||
if (err) { |
|||
serverCallback(err); |
|||
return; |
|||
} |
|||
|
|||
server.on('error', serverCallback); |
|||
server.listen(port, function () { |
|||
// is it even theoritically possible for
|
|||
// a request to come in before this callback has fired?
|
|||
// I'm assuming this event must fire before any request event
|
|||
promiseApp = serverCallback(null, server); |
|||
}); |
|||
/* |
|||
server.listen(port, '::::', function () { |
|||
// is it even theoritically possible for
|
|||
// a request to come in before this callback has fired?
|
|||
// I'm assuming this event must fire before any request event
|
|||
promiseApp = serverCallback(null, server); |
|||
}); |
|||
*/ |
|||
|
|||
// Get up and listening as absolutely quickly as possible
|
|||
function onRequest(req, res) { |
|||
// this is a hot piece of code, so we cache the result
|
|||
if (app) { |
|||
app(req, res); |
|||
return; |
|||
} |
|||
|
|||
promiseApp.then(function (_app) { |
|||
console.log('[Server]', req.method, req.host || req.headers['x-forwarded-host'] || req.headers.host, req.url); |
|||
app = _app; |
|||
app(req, res); |
|||
}); |
|||
} |
|||
|
|||
if (lex) { |
|||
var LEX = require('letsencrypt-express'); |
|||
server.on('request', LEX.createAcmeResponder(lex, onRequest)); |
|||
} else { |
|||
server.on('request', onRequest); |
|||
} |
|||
} |
|||
|
|||
if (certPaths) { |
|||
require('../lib/sni-server').create(lex, certPaths, initServer); |
|||
} else { |
|||
initServer(null, require('http').createServer()); |
|||
} |
|||
}; |
@ -1,58 +0,0 @@ |
|||
'use strict'; |
|||
|
|||
// Note the odd use of callbacks here.
|
|||
// We're targetting low-power platforms and so we're trying to
|
|||
// require everything as lazily as possible until our server
|
|||
// is actually listening on the socket. Bluebird is heavy.
|
|||
// Even the built-in modules can take dozens of milliseconds to require
|
|||
module.exports.create = function (lex, certPaths, serverCallback) { |
|||
// Recognize that this secureContexts cache is local to this CPU core
|
|||
var secureContexts = {}; |
|||
var ciphers = 'ECDH+AESGCM:DH+AESGCM:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS:!AES256'; |
|||
|
|||
function createSecureServer() { |
|||
var domainname = 'www.example.com'; |
|||
var fs = require('fs'); |
|||
var secureOpts = { |
|||
// TODO create backup file just in case this one is ever corrupted
|
|||
// NOTE synchronous is faster in this case of initialization
|
|||
// NOTE certsPath[0] must be the default (LE) directory (another may be used for OV and EV certs)
|
|||
key: fs.readFileSync(certPaths[0] + '/' + domainname + '/privkey.pem', 'ascii') |
|||
, cert: fs.readFileSync(certPaths[0] + '/' + domainname + '/fullchain.pem', 'ascii') |
|||
// https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
|||
// https://nodejs.org/api/tls.html
|
|||
// removed :ECDH+AES256:DH+AES256 and added :!AES256 because AES-256 wastes CPU
|
|||
, ciphers: ciphers |
|||
, honorCipherOrder: true |
|||
}; |
|||
|
|||
secureContexts['www.example.com'] = require('tls').createSecureContext(secureOpts); |
|||
secureContexts['example.com'] = secureContexts['www.example.com']; |
|||
|
|||
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
|||
secureOpts.SNICallback = function (domainname, cb) { |
|||
// NOTE: '*.proxyable.*' domains will be truncated
|
|||
require('./load-certs').load(secureContexts, certPaths, domainname).then(function (context) { |
|||
cb(null, context); |
|||
}, function (err) { |
|||
console.error('[SNI Callback]'); |
|||
console.error(err.stack); |
|||
cb(err); |
|||
}); |
|||
}; |
|||
|
|||
serverCallback(null, require('https').createServer(secureOpts)); |
|||
} |
|||
|
|||
function createLeServer() { |
|||
lex.httpsOptions.ciphers = ciphers; |
|||
lex.httpsOptions.honorCipherOrder = true; |
|||
serverCallback(null, require('https').createServer(lex.httpsOptions)); |
|||
} |
|||
|
|||
if (lex) { |
|||
createLeServer(); |
|||
} else { |
|||
createSecureServer(); |
|||
} |
|||
}; |
@ -1,159 +0,0 @@ |
|||
'use strict'; |
|||
|
|||
function tplCaddyfile(caddyConf) { |
|||
var contents = []; |
|||
|
|||
caddyConf.domains.forEach(function (hostname) { |
|||
var content = ""; |
|||
var pagesname = hostname; |
|||
|
|||
// TODO prefix
|
|||
content += "https://" + hostname + " {\n" |
|||
+ " gzip\n" |
|||
+ " tls " |
|||
+ "/srv/walnut/certs/live/" + hostname + "/fullchain.pem " |
|||
+ "/srv/walnut/certs/live/" + hostname + "/privkey.pem\n" |
|||
; |
|||
|
|||
if (caddyConf.locked) { |
|||
content += " root /srv/walnut/init.public/\n"; |
|||
} else { |
|||
content += " root " + caddyConf.sitespath + "/" + pagesname + "/\n"; |
|||
} |
|||
|
|||
content += |
|||
" proxy /api http://localhost:" + caddyConf.localPort.toString() + " {\n" |
|||
+ " proxy_header Host {host}\n" |
|||
+ " proxy_header X-Forwarded-Host {host}\n" |
|||
+ " proxy_header X-Forwarded-Proto {scheme}\n" |
|||
// # TODO internal
|
|||
+ " }\n" |
|||
+ "}"; |
|||
|
|||
contents.push(content); |
|||
}); |
|||
|
|||
return contents.join('\n\n'); |
|||
} |
|||
|
|||
module.exports.tplCaddyfile = tplCaddyfile; |
|||
module.exports.create = function (caddyConf) { |
|||
var spawn = require('child_process').spawn; |
|||
var caddyBin = caddyConf.bin; |
|||
var caddyfilePath = caddyConf.conf; |
|||
// TODO put up a booting / lock screen on boot
|
|||
// and wait for all to be grabbed from db
|
|||
// NOTE caddy cannot yet support multiple roots
|
|||
// (needed for example.com/appname instead of appname.example.com)
|
|||
var caddy; |
|||
var fs = require('fs'); |
|||
|
|||
// TODO this should be expanded to include proxies a la proxydyn
|
|||
function writeCaddyfile(caddyConf, cb) { |
|||
fs.readdir(caddyConf.sitespath, function (err, nodes) { |
|||
if (err) { |
|||
if (cb) { |
|||
cb(err); |
|||
return; |
|||
} |
|||
console.error('[writeCaddyFile] 0'); |
|||
console.error(err.stack); |
|||
throw err; |
|||
} |
|||
|
|||
caddyConf.domains = nodes.filter(function (node) { |
|||
return /\./.test(node) && !/(^\.)|([\/\:\\])/.test(node); |
|||
}); |
|||
|
|||
var contents = tplCaddyfile(caddyConf); |
|||
fs.writeFile(caddyfilePath, contents, 'utf8', function (err) { |
|||
if (err) { |
|||
if (cb) { |
|||
cb(err); |
|||
return; |
|||
} |
|||
console.error('[writeCaddyFile] 1'); |
|||
console.error(err.stack); |
|||
throw err; |
|||
} |
|||
|
|||
if (cb) { cb(null); } |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function spawnCaddy(caddyConf, cb) { |
|||
console.log('[CADDY] start'); |
|||
writeCaddyfile(caddyfilePath, function (err) { |
|||
if (err) { |
|||
console.error('[writeCaddyfile]'); |
|||
console.error(err.stack); |
|||
throw err; |
|||
} |
|||
if (caddy) { |
|||
caddy.kill('SIGUSR1'); |
|||
return caddy; |
|||
|
|||
// TODO caddy.kill('SIGKILL'); if SIGTERM fails
|
|||
// https://github.com/mholt/caddy/issues/107
|
|||
// SIGUSR1
|
|||
|
|||
//caddy.kill('SIGTERM');
|
|||
} |
|||
|
|||
try { |
|||
require('child_process').execSync('killall caddy'); |
|||
} catch(e) { |
|||
// ignore
|
|||
// Command failed: killall caddy
|
|||
// caddy: no process found
|
|||
} |
|||
caddy = spawn(caddyBin, ['-conf', caddyfilePath], { stdio: ['ignore', 'pipe', 'pipe'] }); |
|||
caddy.stdout.on('data', function (str) { |
|||
console.error('[Caddy]', str.toString('utf8')); |
|||
}); |
|||
|
|||
caddy.stderr.on('data', function (errstr) { |
|||
console.error('[Caddy]', errstr.toString('utf8')); |
|||
}); |
|||
|
|||
caddy.on('close', function (code, signal) { |
|||
// TODO catch if caddy doesn't exist
|
|||
console.log('[Caddy]'); |
|||
console.log(code, signal); |
|||
caddy = null; |
|||
setTimeout(function () { |
|||
spawnCaddy(caddyConf); |
|||
}, 1 * 1000); |
|||
}); |
|||
|
|||
try { |
|||
if ('function' === typeof cb) { cb(null, caddy); } |
|||
} catch(e) { |
|||
console.error('ERROR: [spawn-caddy.js]'); |
|||
console.error(e.stack); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function sighup() { |
|||
if (caddy) { |
|||
caddy.kill('SIGUSR1'); |
|||
return; |
|||
} |
|||
|
|||
// sudo kill -s SIGUSR1 `cat caddy.pid`
|
|||
fs.readFileAsync('/srv/walnut/caddy.pid', 'utf8').then(function (pid) { |
|||
console.log('[caddy] pid', pid); |
|||
caddy = spawn('/bin/kill', ['-s', 'SIGUSR1', pid]); |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
spawn: spawnCaddy |
|||
, update: function (caddyConf) { |
|||
return writeCaddyfile(caddyConf, sighup); |
|||
} |
|||
, sighup: sighup |
|||
}; |
|||
}; |
Loading…
Reference in new issue