removed letsencrypt and other
This commit is contained in:
parent
7525b7d0a7
commit
54fe53dbfb
|
@ -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());
|
||||
}
|
||||
};
|
|
@ -29,39 +29,17 @@ var walnut = tryConf(
|
|||
path.join('..', '..', 'config.walnut')
|
||||
, { externalPort: 443
|
||||
, externalInsecurePort: 80
|
||||
, certspath: path.join(__dirname, '..', '..', 'certs', 'live')
|
||||
}
|
||||
);
|
||||
var caddy = tryConf(
|
||||
path.join('..', '..', 'config.caddy')
|
||||
, { conf: path.join(__dirname, '..', '..', 'Caddyfile')
|
||||
, bin: null // '/usr/local/bin/caddy'
|
||||
, sitespath: null // path.join(__dirname, 'sites-enabled')
|
||||
, locked: false // true
|
||||
}
|
||||
);
|
||||
var letsencrypt = tryConf(
|
||||
path.join('..', '..', 'config.letsencrypt')
|
||||
, { configDir: path.join(__dirname, '..', '..', 'letsencrypt')
|
||||
, email: null
|
||||
, agreeTos: false
|
||||
}
|
||||
);
|
||||
var useCaddy = caddy.bin && require('fs').existsSync(caddy.bin);
|
||||
|
||||
var info = {
|
||||
type: 'walnut.init'
|
||||
, conf: {
|
||||
protocol: useCaddy ? 'http' : 'https'
|
||||
, externalPort: walnut.externalPort
|
||||
, externalPortInsecure: walnut.externalInsecurePort // TODO externalInsecurePort
|
||||
, localPort: walnut.localPort || (useCaddy ? 4080 : 443) // system / local network
|
||||
, insecurePort: walnut.insecurePort || (useCaddy ? 80 : 80) // meh
|
||||
, certPaths: useCaddy ? null : [
|
||||
walnut.certspath
|
||||
, path.join(letsencrypt.configDir, 'live')
|
||||
]
|
||||
, trustProxy: useCaddy ? true : false
|
||||
, lexConf: letsencrypt
|
||||
protocol: 'http'
|
||||
, externalPort: walnut.externalPort || 443
|
||||
, externalPortInsecure: walnut.externalInsecurePort || 80 // TODO externalInsecurePort
|
||||
, localPort: walnut.localPort || 4080 // system / local network
|
||||
, trustProxy: true
|
||||
, varpath: path.join(__dirname, '..', '..', 'var')
|
||||
, etcpath: path.join(__dirname, '..', '..', 'etc')
|
||||
}
|
||||
|
@ -79,11 +57,6 @@ cluster.on('online', function (worker) {
|
|||
|
||||
if (state.firstRun) {
|
||||
state.firstRun = false;
|
||||
if (useCaddy) {
|
||||
caddy = require('../lib/spawn-caddy').create(caddy);
|
||||
// relies on { localPort, locked }
|
||||
caddy.spawn(caddy);
|
||||
}
|
||||
// TODO dyndns in master?
|
||||
}
|
||||
|
||||
|
@ -94,7 +67,6 @@ cluster.on('online', function (worker) {
|
|||
return;
|
||||
}
|
||||
|
||||
state.caddy = caddy;
|
||||
state.workers = workers;
|
||||
// calls init if init has not been called
|
||||
require('../lib/master').touch(info.conf, state).then(function (newConf) {
|
||||
|
|
271
boot/worker.js
271
boot/worker.js
|
@ -1,168 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.create = function (opts) {
|
||||
module.exports.create = function () {
|
||||
var id = '0';
|
||||
var promiseApp;
|
||||
|
||||
function createAndBindInsecure(lex, conf, getOrCreateHttpApp) {
|
||||
// TODO conditional if 80 is being served by caddy
|
||||
|
||||
var appPromise = null;
|
||||
var app = null;
|
||||
var http = require('http');
|
||||
var insecureServer = http.createServer();
|
||||
|
||||
function onRequest(req, res) {
|
||||
if (app) {
|
||||
app(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!appPromise) {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.end('{ "error": { "code": "E_SANITY_FAIL", "message": "should have an express app, but didn\'t" } }');
|
||||
return;
|
||||
}
|
||||
|
||||
appPromise.then(function (_app) {
|
||||
appPromise = null;
|
||||
app = _app;
|
||||
app(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
insecureServer.listen(conf.insecurePort, function () {
|
||||
console.info("#" + id + " Listening on http://"
|
||||
+ insecureServer.address().address + ":" + insecureServer.address().port, '\n');
|
||||
appPromise = getOrCreateHttpApp(null, insecureServer);
|
||||
|
||||
if (!appPromise) {
|
||||
throw new Error('appPromise returned nothing');
|
||||
}
|
||||
});
|
||||
|
||||
insecureServer.on('request', onRequest);
|
||||
}
|
||||
|
||||
function walkLe(domainname) {
|
||||
var PromiseA = require('bluebird');
|
||||
if (!domainname) {
|
||||
return PromiseA.reject(new Error('no domainname given for walkLe'));
|
||||
}
|
||||
var fs = PromiseA.promisifyAll(require('fs'));
|
||||
var path = require('path');
|
||||
var parts = domainname.split('.'); //.replace(/^www\./, '').split('.');
|
||||
var configname = parts.join('.') + '.json';
|
||||
var configpath = path.join(__dirname, '..', '..', 'config', configname);
|
||||
|
||||
if (parts.length < 2) {
|
||||
return PromiseA.resolve(null);
|
||||
}
|
||||
|
||||
// TODO configpath a la varpath
|
||||
return fs.readFileAsync(configpath, 'utf8').then(function (text) {
|
||||
var data = JSON.parse(text);
|
||||
data.name = configname;
|
||||
return data;
|
||||
}, function (/*err*/) {
|
||||
parts.shift();
|
||||
return walkLe(parts.join('.'));
|
||||
});
|
||||
}
|
||||
|
||||
function createLe(lexConf, conf) {
|
||||
var LEX = require('letsencrypt-express');
|
||||
var lex = LEX.create({
|
||||
configDir: lexConf.configDir // i.e. __dirname + '/letsencrypt.config'
|
||||
, approveRegistration: function (hostname, cb) {
|
||||
// TODO cache/report unauthorized
|
||||
if (!hostname) {
|
||||
cb(new Error("[lex.approveRegistration] undefined hostname"), null);
|
||||
return;
|
||||
}
|
||||
|
||||
walkLe(hostname).then(function (leAuth) {
|
||||
// TODO should still check dns for hostname (and mx for email)
|
||||
if (leAuth && leAuth.email && leAuth.agreeTos) {
|
||||
cb(null, {
|
||||
domains: [hostname] // TODO handle www and bare on the same cert
|
||||
, email: leAuth.email
|
||||
, agreeTos: leAuth.agreeTos
|
||||
});
|
||||
}
|
||||
else {
|
||||
// TODO report unauthorized
|
||||
cb(new Error("Valid LetsEncrypt config with email and agreeTos not found for '" + hostname + "'"), null);
|
||||
}
|
||||
});
|
||||
/*
|
||||
letsencrypt.getConfig({ domains: [domain] }, function (err, config) {
|
||||
if (!(config && config.checkpoints >= 0)) {
|
||||
cb(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
cb(null, {
|
||||
email: config.email
|
||||
// can't remember which it is, but the pyconf is different that the regular variable
|
||||
, agreeTos: config.tos || config.agree || config.agreeTos
|
||||
, server: config.server || LE.productionServerUrl
|
||||
, domains: config.domains || [domain]
|
||||
});
|
||||
});
|
||||
*/
|
||||
}
|
||||
});
|
||||
conf.letsencrypt = lex.letsencrypt;
|
||||
conf.lex = lex;
|
||||
conf.walkLe = walkLe;
|
||||
|
||||
return lex;
|
||||
}
|
||||
|
||||
function createAndBindServers(conf, getOrCreateHttpApp) {
|
||||
var lex;
|
||||
|
||||
if (conf.lexConf) {
|
||||
lex = createLe(conf.lexConf, conf);
|
||||
}
|
||||
|
||||
// NOTE that message.conf[x] will be overwritten when the next message comes in
|
||||
require('./local-server').create(lex, conf.certPaths, conf.localPort, conf, function (err, webserver) {
|
||||
if (err) {
|
||||
console.error('[ERROR] worker.js');
|
||||
console.error(err.stack);
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.info("#" + id + " Listening on " + conf.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
||||
|
||||
// we don't need time to pass, just to be able to return
|
||||
process.nextTick(function () {
|
||||
createAndBindInsecure(lex, conf, getOrCreateHttpApp);
|
||||
});
|
||||
|
||||
// we are returning the promise result to the caller
|
||||
return getOrCreateHttpApp(null, null, webserver, conf);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Worker Mode
|
||||
//
|
||||
function waitForConfig(realMessage) {
|
||||
if ('walnut.init' !== realMessage.type) {
|
||||
console.warn('[Worker] 0 got unexpected message:');
|
||||
console.warn(realMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
var conf = realMessage.conf;
|
||||
process.removeListener('message', waitForConfig);
|
||||
|
||||
function createAndBind(conf) {
|
||||
// NOTE: this callback must return a promise for an express app
|
||||
|
||||
function getExpressApp(err, insecserver, webserver/*, newMessage*/) {
|
||||
function getOrCreateHttpApp(err, insecserver, webserver/*, newMessage*/) {
|
||||
var PromiseA = require('bluebird');
|
||||
|
||||
if (promiseApp) {
|
||||
|
@ -198,41 +45,93 @@ module.exports.create = function (opts) {
|
|||
return promiseApp;
|
||||
}
|
||||
|
||||
createAndBindServers(conf, getExpressApp);
|
||||
}
|
||||
|
||||
//
|
||||
// Standalone Mode
|
||||
//
|
||||
if (opts) {
|
||||
// NOTE: this callback must return a promise for an express app
|
||||
createAndBindServers(opts, function (err, insecserver, webserver/*, conf*/) {
|
||||
var PromiseA = require('bluebird');
|
||||
|
||||
if (promiseApp) {
|
||||
return promiseApp;
|
||||
function serverCallback(err, webserver) {
|
||||
if (err) {
|
||||
console.error('[ERROR] worker.js');
|
||||
console.error(err.stack);
|
||||
throw err;
|
||||
}
|
||||
|
||||
promiseApp = new PromiseA(function (resolve) {
|
||||
opts.getConfig(function (srvmsg) {
|
||||
resolve(require('../lib/worker').create(webserver, srvmsg));
|
||||
});
|
||||
}).then(function (app) {
|
||||
console.info('[Standalone Ready]');
|
||||
return app;
|
||||
});
|
||||
console.info("#" + id + " Listening on " + conf.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
||||
|
||||
return promiseApp;
|
||||
});
|
||||
} else {
|
||||
// we are in cluster mode, as opposed to standalone mode
|
||||
id = require('cluster').worker.id.toString();
|
||||
// We have to wait to get the configuration from the master process
|
||||
// before we can start our webserver
|
||||
console.info('[Worker #' + id + '] online!');
|
||||
process.on('message', waitForConfig);
|
||||
// we are returning the promise result to the caller
|
||||
return getOrCreateHttpApp(null, null, webserver, conf);
|
||||
}
|
||||
|
||||
// Note the odd use of callbacks (instead of promises) here
|
||||
// It's to avoid loading bluebird yet (see sni-server.js for explanation)
|
||||
function localServerCreate(port) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
server.on('request', onRequest);
|
||||
}
|
||||
|
||||
initServer(null, require('http').createServer());
|
||||
}
|
||||
|
||||
// NOTE that message.conf[x] will be overwritten when the next message comes in
|
||||
localServerCreate(conf.localPort);
|
||||
}
|
||||
|
||||
function waitForConfig(realMessage) {
|
||||
console.log('realMessage', realMessage);
|
||||
if ('walnut.init' !== realMessage.type) {
|
||||
console.warn('[Worker] 0 got unexpected message:');
|
||||
console.warn(realMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
var conf = realMessage.conf;
|
||||
process.removeListener('message', waitForConfig);
|
||||
|
||||
createAndBind(conf);
|
||||
}
|
||||
|
||||
// we are in cluster mode, as opposed to standalone mode
|
||||
id = require('cluster').worker.id.toString();
|
||||
// We have to wait to get the configuration from the master process
|
||||
// before we can start our webserver
|
||||
console.info('[Worker #' + id + '] online!');
|
||||
process.on('message', waitForConfig);
|
||||
|
||||
//
|
||||
// Debugging
|
||||
//
|
||||
|
|
99
lib/main.js
99
lib/main.js
|
@ -13,54 +13,8 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) {
|
|||
var CORS;
|
||||
var cors;
|
||||
|
||||
function redirectHttpsHelper(req, res) {
|
||||
var host = req.hostname || req.headers.host || '';
|
||||
var url = req.url;
|
||||
|
||||
// TODO
|
||||
// allow exceptions for the case of arduino and whatnot that cannot handle https?
|
||||
// http://evothings.com/is-it-possible-to-secure-micro-controllers-used-within-iot/
|
||||
// needs ECDSA?
|
||||
|
||||
var escapeHtml = require('escape-html');
|
||||
var newLocation = 'https://'
|
||||
+ host.replace(/:\d+/, ':' + xconfx.externalPort) + url
|
||||
;
|
||||
var safeLocation = escapeHtml(newLocation);
|
||||
|
||||
var metaRedirect = ''
|
||||
+ '<html>\n'
|
||||
+ '<head>\n'
|
||||
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||
+ ' <META http-equiv="refresh" content="0;URL=' + safeLocation + '">\n'
|
||||
+ '</head>\n'
|
||||
+ '<body style="display: none;">\n'
|
||||
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
||||
+ ' <a href="' + safeLocation + '">' + safeLocation + '</a></p>\n'
|
||||
+ '</body>\n'
|
||||
+ '</html>\n'
|
||||
;
|
||||
|
||||
// DO NOT HTTP REDIRECT
|
||||
/*
|
||||
res.setHeader('Location', newLocation);
|
||||
res.statusCode = 302;
|
||||
*/
|
||||
|
||||
// BAD NEWS BEARS
|
||||
//
|
||||
// When people are experimenting with the API and posting tutorials
|
||||
// they'll use cURL and they'll forget to prefix with https://
|
||||
// If we allow that, then many users will be sending private tokens
|
||||
// and such with POSTs in clear text and, worse, it will work!
|
||||
// To minimize this, we give browser users a mostly optimal experience,
|
||||
// but people experimenting with the API get a message letting them know
|
||||
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.end(metaRedirect);
|
||||
}
|
||||
|
||||
function redirectSetup(reason, req, res/*, next*/) {
|
||||
console.log('xconfx', xconfx);
|
||||
var url = 'https://cloud.' + xconfx.primaryDomain;
|
||||
|
||||
if (443 !== xconfx.externalPort) {
|
||||
|
@ -74,48 +28,6 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) {
|
|||
res.end();
|
||||
}
|
||||
|
||||
function redirectHttps(req, res) {
|
||||
if (localCache.le[req.hostname]) {
|
||||
if (localCache.le[req.hostname].conf) {
|
||||
redirectHttpsHelper(req, res);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// TODO needs IPC to expire cache
|
||||
redirectSetup(req.hostname, req, res);
|
||||
return;
|
||||
/*
|
||||
if (Date.now() - localCache.le[req.hostname].createdAt < (5 * 60 * 1000)) {
|
||||
// TODO link to dbconf.primaryDomain
|
||||
res.send({ error: { message: "Security Error: Encryption for '" + req.hostname + "' has not been configured."
|
||||
+ " Please use the management interface to set up ACME / Let's Encrypt (or another solution)." } });
|
||||
return;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
return xconfx.walkLe(req.hostname).then(function (leAuth) {
|
||||
if (!leAuth) {
|
||||
redirectSetup(req.hostname, req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
localCache.le[req.hostname] = { conf: leAuth, createdAt: Date.now() };
|
||||
redirectHttps(req, res);
|
||||
}, function (err) {
|
||||
console.error('[Error] lib/main.js walkLe');
|
||||
if (err.stack) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
else {
|
||||
console.error(new Error('getstack').stack);
|
||||
console.error(err);
|
||||
}
|
||||
res.send({ error: { message: "failed to get tls certificate for '" + (req.hostname || '') + "'" } });
|
||||
});
|
||||
}
|
||||
|
||||
function disallowSymLinks(req, res) {
|
||||
res.end(
|
||||
"Symbolic Links are not supported on all platforms and are therefore disallowed."
|
||||
|
@ -245,15 +157,6 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) {
|
|||
var appIdParts = appId.split('#');
|
||||
var appIdPart;
|
||||
|
||||
if (!req.secure) {
|
||||
// did not come from https
|
||||
if (/\.(appcache|manifest)\b/.test(req.url)) {
|
||||
require('./unbrick-appcache').unbrick(req, res);
|
||||
return;
|
||||
}
|
||||
return redirectHttps(req, res);
|
||||
}
|
||||
|
||||
// TODO configuration for allowing www
|
||||
if (/^www\./.test(req.hostname)) {
|
||||
// NOTE: acme responder and appcache unbricker must come before scrubTheDub
|
||||
|
|
|
@ -2,17 +2,27 @@
|
|||
|
||||
var cluster = require('cluster');
|
||||
var PromiseA = require('bluebird');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
|
||||
function init(conf, state) {
|
||||
var newConf = {};
|
||||
|
||||
function rand(n) {
|
||||
var HEX = 16;
|
||||
var BASE_36 = 36;
|
||||
var rnd = require('crypto').randomBytes(n || 16).toString('hex');
|
||||
return parseInt(rnd, HEX).toString(BASE_36);
|
||||
}
|
||||
|
||||
if (!conf.ipcKey) {
|
||||
conf.ipcKey = newConf.ipcKey = require('crypto').randomBytes(16).toString('base64');
|
||||
conf.ipcKey = newConf.ipcKey = rand(16);
|
||||
}
|
||||
if (!conf.sqlite3Sock) {
|
||||
conf.sqlite3Sock = newConf.sqlite3Sock = '/tmp/sqlite3.' + require('crypto').randomBytes(4).toString('hex') + '.sock';
|
||||
conf.sqlite3Sock = newConf.sqlite3Sock = path.join(os.tmpdir(), 'sqlite3.' + rand(8) + '.sock');
|
||||
}
|
||||
if (!conf.memstoreSock) {
|
||||
conf.memstoreSock = newConf.memstoreSock = '/tmp/memstore.' + require('crypto').randomBytes(4).toString('hex') + '.sock';
|
||||
conf.memstoreSock = newConf.memstoreSock = path.join(os.tmpdir(), 'memstore.' + rand(8) + '.sock');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -70,11 +70,13 @@ module.exports.create = function (webserver, xconfx, state) {
|
|||
, indices: [ 'createdAt', 'updatedAt' ]
|
||||
}
|
||||
];
|
||||
console.log('config directive', dir);
|
||||
|
||||
function scopeMemstore(expId) {
|
||||
var scope = expId + '|';
|
||||
return {
|
||||
getAsync: function (id) {
|
||||
id = id.replace(/\|/, );
|
||||
return memstore.getAsync(scope + id);
|
||||
}
|
||||
, setAsync: function (id, data) {
|
||||
|
@ -194,20 +196,15 @@ module.exports.create = function (webserver, xconfx, state) {
|
|||
}));
|
||||
app.use('/api', recase);
|
||||
|
||||
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
||||
app.use('/', function (req, res) {
|
||||
if (!req.secure) {
|
||||
if (!(req.encrypted || req.secure)) {
|
||||
// did not come from https
|
||||
if (/\.(appcache|manifest)\b/.test(req.url)) {
|
||||
require('./unbrick-appcache').unbrick(req, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (xconfx.lex && /\.well-known\/acme-challenge\//.test(req.url)) {
|
||||
var LEX = require('letsencrypt-express');
|
||||
xconfx.lex.debug = true;
|
||||
xconfx.acmeResponder = xconfx.acmeResponder || LEX.createAcmeResponder(xconfx.lex/*, next*/);
|
||||
xconfx.acmeResponder(req, res);
|
||||
res.end("Connection is not encrypted. That's no bueno or, as we say in Hungarian, nem szabad!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ var cluster = require('cluster');
|
|||
|
||||
var crypto;
|
||||
var stacks = {};
|
||||
function realRandom() {
|
||||
return parseFloat(('0.' + (parseInt(crypto.randomBytes(8).toString('hex'), 16))).replace(/(^0)|(0$)/g, ''));
|
||||
}
|
||||
Math.random = function () {
|
||||
var err = new Error("Math.random() was used");
|
||||
|
||||
|
@ -16,7 +19,8 @@ Math.random = function () {
|
|||
crypto = require('crypto');
|
||||
}
|
||||
|
||||
return parseFloat(('0.' + (parseInt(crypto.randomBytes(8).toString('hex'), 16))).replace(/(^0)|(0$)/g, ''));
|
||||
Math.random = realRandom;
|
||||
return realRandom();
|
||||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
|
@ -26,5 +30,5 @@ if (cluster.isMaster) {
|
|||
alternately we could use this and then check require.main
|
||||
cluster.setupMaster({ exec : "app.js", });
|
||||
*/
|
||||
require('./boot/worker').create(null);
|
||||
require('./boot/worker').create(null, null);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue