From 671753bb947d47687d230f02f0909cc00f19346e Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 29 Mar 2016 15:03:09 -0400 Subject: [PATCH] add letsencrypt --- bin/walnut.js | 66 +++++++++++++++++++++++++++++++++++++++++- boot/master.js | 1 + boot/worker.js | 47 +++++++++++++++++++++++++++--- lib/insecure-server.js | 12 ++++++-- lib/local-server.js | 15 +++++++--- lib/sni-server.js | 17 +++++++++-- package.json | 1 + 7 files changed, 144 insertions(+), 15 deletions(-) mode change 120000 => 100755 bin/walnut.js diff --git a/bin/walnut.js b/bin/walnut.js deleted file mode 120000 index f26d3ff..0000000 --- a/bin/walnut.js +++ /dev/null @@ -1 +0,0 @@ -walnut \ No newline at end of file diff --git a/bin/walnut.js b/bin/walnut.js new file mode 100755 index 0000000..03ea920 --- /dev/null +++ b/bin/walnut.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +'use strict'; + +require('../walnut.js'); +/* +var c = require('console-plus'); +console.log = c.log; +console.error = c.error; +*/ + +function eagerLoad() { + var PromiseA = require('bluebird').Promise; + var promise = PromiseA.resolve(); + + [ 'express' + , 'request' + , 'sqlite3' + , 'body-parser' + , 'urlrouter' + , 'express-lazy' + , 'connect-send-error' + , 'underscore.string' + , 'secret-utils' + , 'connect-cors' + , 'uuid' + , 'connect-recase' + , 'escape-string-regexp' + , 'connect-query' + , 'recase' + ].forEach(function (name/*, i*/) { + promise = promise.then(function () { + return new PromiseA(function (resolve/*, reject*/) { + setTimeout(function () { + require(name); + resolve(); + }, 4); + }); + }); + }); + + [ function () { + require('body-parser').json(); + } + /* + // do not use urlencoded as it enables csrf + , function () { + require('body-parser').urlencoded(); + } + */ + ].forEach(function (fn) { + promise = promise.then(function (thing) { + return new PromiseA(function (resolve) { + setTimeout(function () { + resolve(fn(thing)); + }, 4); + }); + }); + }); + + promise.then(function () { + console.log('Eager Loading Complete'); + }); +} + +setTimeout(eagerLoad, 100); diff --git a/boot/master.js b/boot/master.js index 7aec33a..563b7b1 100644 --- a/boot/master.js +++ b/boot/master.js @@ -63,6 +63,7 @@ cluster.on('online', function (worker) { , 'org.oauth3.consumer': config['org.oauth3.consumer'] , 'org.oauth3.provider': config['org.oauth3.provider'] , keys: config.keys + , letsencrypt: config.letsencrypt } }; worker.send(info); diff --git a/boot/worker.js b/boot/worker.js index 1e039fd..c03e202 100644 --- a/boot/worker.js +++ b/boot/worker.js @@ -4,9 +4,9 @@ module.exports.create = function (opts) { var id = '0'; var promiseApp; - function createAndBindInsecure(message, cb) { + function createAndBindInsecure(lex, message, cb) { // TODO conditional if 80 is being served by caddy - require('../lib/insecure-server').create(message.conf.externalPort, message.conf.insecurePort, message, function (err, webserver) { + require('../lib/insecure-server').create(lex, message.conf.externalPort, message.conf.insecurePort, message, function (err, webserver) { console.info("#" + id + " Listening on http://" + webserver.address().address + ":" + webserver.address().port, '\n'); // we are returning the promise result to the caller @@ -14,9 +14,48 @@ module.exports.create = function (opts) { }); } + function createLe(conf) { + var LEX = require('letsencrypt-express'); + var lex = LEX.create({ + configDir: conf.letsencrypt.configDir // i.e. __dirname + '/letsencrypt.config' + , approveRegistration: function (hostname, cb) { + cb(null, { + domains: [hostname] // TODO handle www and bare on the same cert + , email: conf.letsencrypt.email + , agreeTos: conf.letsencrypt.agreeTos + }); + /* + 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] + }); + }); + */ + } + }); + //var letsencrypt = lex.letsencrypt; + + return lex; + } + function createAndBindServers(message, cb) { + var lex; + + if (message.conf.letsencrypt) { + lex = createLe(message.conf); + } + // NOTE that message.conf[x] will be overwritten when the next message comes in - require('../lib/local-server').create(message.conf.certPaths, message.conf.localPort, message, function (err, webserver) { + require('../lib/local-server').create(lex, message.conf.certPaths, message.conf.localPort, message, function (err, webserver) { if (err) { console.error('[ERROR] worker.js'); console.error(err.stack); @@ -27,7 +66,7 @@ module.exports.create = function (opts) { // we don't need time to pass, just to be able to return process.nextTick(function () { - createAndBindInsecure(message, cb); + createAndBindInsecure(lex, message, cb); }); // we are returning the promise result to the caller diff --git a/lib/insecure-server.js b/lib/insecure-server.js index 359fed9..1166aab 100644 --- a/lib/insecure-server.js +++ b/lib/insecure-server.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports.create = function (securePort, insecurePort, info, serverCallback) { +module.exports.create = function (lex, securePort, insecurePort, info, serverCallback) { var PromiseA = require('bluebird').Promise; var appPromise; //var app; @@ -42,7 +42,7 @@ module.exports.create = function (securePort, insecurePort, info, serverCallback // http://evothings.com/is-it-possible-to-secure-micro-controllers-used-within-iot/ // needs ECDSA? - console.warn('HARD-CODED HTTPS EXCEPTION in insecure-server.js'); + console.warn('HARD-CODED HTTPS EXCEPTION in insecure-server.js for redirect-www.org'); if (/redirect-www.org$/.test(host) && useAppInsecurely(req, res)) { return true; } @@ -103,7 +103,13 @@ module.exports.create = function (securePort, insecurePort, info, serverCallback appPromise = serverCallback(null, insecureServer); } }); - insecureServer.on('request', redirectHttps); + + if (lex) { + var LEX = require('letsencrypt-express'); + insecureServer.on('request', LEX.createAcmeResponder(lex, redirectHttps)); + } else { + insecureServer.on('request', redirectHttps); + } return PromiseA.resolve(insecureServer); }; diff --git a/lib/local-server.js b/lib/local-server.js index 37d776d..e088213 100644 --- a/lib/local-server.js +++ b/lib/local-server.js @@ -2,7 +2,7 @@ // 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 (certPaths, port, info, serverCallback) { +module.exports.create = function (lex, certPaths, port, info, serverCallback) { function initServer(err, server) { var app; var promiseApp; @@ -29,7 +29,7 @@ module.exports.create = function (certPaths, port, info, serverCallback) { */ // Get up and listening as absolutely quickly as possible - server.on('request', function (req, res) { + function onRequest(req, res) { // this is a hot piece of code, so we cache the result if (app) { app(req, res); @@ -41,11 +41,18 @@ module.exports.create = function (certPaths, port, info, serverCallback) { 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('./sni-server').create(certPaths, initServer); + require('./sni-server').create(lex, certPaths, initServer); } else { initServer(null, require('http').createServer()); } diff --git a/lib/sni-server.js b/lib/sni-server.js index 8e30e5e..e1c48e4 100644 --- a/lib/sni-server.js +++ b/lib/sni-server.js @@ -5,9 +5,10 @@ // 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 (certPaths, serverCallback) { +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'; @@ -21,7 +22,7 @@ module.exports.create = function (certPaths, serverCallback) { // 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: 'ECDH+AESGCM:DH+AESGCM:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS:!AES256' + , ciphers: ciphers , honorCipherOrder: true }; @@ -43,5 +44,15 @@ module.exports.create = function (certPaths, serverCallback) { serverCallback(null, require('https').createServer(secureOpts)); } - createSecureServer(); + function createLeServer() { + lex.httpsOptions.ciphers = ciphers; + lex.httpsOptions.honorCipherOrder = true; + serverCallback(null, require('https').createServer(lex.httpsOptions)); + } + + if (lex) { + createLeServer(); + } else { + createSecureServer(); + } }; diff --git a/package.json b/package.json index 4a7abed..acdd50a 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "json-storage": "2.x", "jsonwebtoken": "^5.4.0", "lodash": "2.x", + "letsencrypt-express": "1.1.x", "masterquest-sqlite3": "git://github.com/coolaj86/masterquest-sqlite3.git", "media-typer": "^0.3.0", "methods": "^1.1.1",