This commit is contained in:
AJ ONeal 2016-08-15 21:15:16 -04:00
parent fe039dc137
commit 26eb38fb25
4 changed files with 60 additions and 193 deletions

View File

@ -59,6 +59,8 @@ require('letsencrypt-express').create({
, agreeTos: true , agreeTos: true
, approvedDomains: [ 'example.com' ]
, app: require('express')().use('/', function (req, res) { , app: require('express')().use('/', function (req, res) {
res.end('Hello, World!'); res.end('Hello, World!');
}) })
@ -134,7 +136,7 @@ var lex = require('letsencrypt-express').create({
// handles acme-challenge and redirects to https // handles acme-challenge and redirects to https
require('http').createServer(lex.middleware()).listen(80, function () { require('http').createServer(le.middleware()).listen(80, function () {
console.log("Listening for ACME http-01 challenges on", this.address()); console.log("Listening for ACME http-01 challenges on", this.address());
}); });
@ -146,7 +148,7 @@ app.use('/', function (req, res) {
}); });
// handles your app // handles your app
require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443, function () { require('https').createServer(le.httpsOptions, le.middleware(app)).listen(443, function () {
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
}); });
``` ```
@ -154,22 +156,22 @@ require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443,
API API
=== ===
All options are passed directly to `node-letsencrypt`, This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO).
so `lex` is an instance of `letsencrypt`, but has a few
extra helper methods and options.
See [node-letsencrypt options](https://github.com/Daplie/node-letsencrypt) The API is actually located at [node-letsencrypt options](https://github.com/Daplie/node-letsencrypt)
(because all options are simply passed through to `node-letsencrypt` proper without modification).
* `lexOptions.approveDomains(options, certs, cb)` is special for `letsencrypt-express`, but will probably be included in `node-letsencrypt` in the future (no API change). The only "API" consists of two options, the rest is just a wrapper around `node-letsencrypt` to take LOC from 15 to 5:
* `lexOptions.app` is just an elaborate ruse used for the Quickstart. It's sole purpose is to trim out 5 lines of code for setting http and https servers so that whiners won't whine. Real programmers don't use this. * `opts.app` An express app in the format `function (req, res) { ... }` (no `next`).
* `leOptions.server` set to https://acme-v01.api.letsencrypt.org/directory in production * `lex.listen(plainPort, tlsPort)` Accepts port numbers (or arrays of port numbers) to listen on.
* `leOptions.email` useful for simple sites where there is only one owner. Leave this `null` and use `approveDomains` otherwise.
* `leOptions.agreeTos` useful for simple sites where there is only one owner. Leave this `null` and use `approveDomains` otherwise.
* `leOptions.renewWithin` is shared so that the worker knows how earlier to request a new cert
* `leOptions.renewBy` is passed to `le-sni-auto` so that it staggers renewals between `renewWithin` (latest) and `renewBy` (earlier)
* `lex.middleware(nextApp)` uses `letsencrypt/middleware` for GET-ing `http-01`, hence `sharedOptions.webrootPath`
* `lex.httpsOptions` has a default localhost certificate and the `SNICallback`.
There are a few options that aren't shown in these examples, so if you need to change something Brief overview of some simple options for `node-letsencrypt`:
that isn't shown here, look at the code (it's not that much) or open an issue.
* `opts.server` set to https://acme-v01.api.letsencrypt.org/directory in production
* `opts.email` The default email to use to accept agreements.
* `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first.
* `opts.approvedDomains` An explicit array of The allowed domains (can be used instead of `approveDomains`).
* `opts.approveDomains` A callback for checking your database before allowing a domain `function (opts, certs, cb) { }`
* `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate.
* `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.

View File

@ -1,12 +0,0 @@
'use strict';
console.error("");
console.error("One does not simply require('letsencrypt-cluster');");
console.error("");
console.error("Usage:");
console.error("\trequire('letsencrypt-cluster/master').create({ ... });");
console.error("\trequire('letsencrypt-cluster/worker').create({ ... });");
console.error("");
console.error("");
process.exit(1);

110
master.js
View File

@ -1,91 +1,55 @@
'use strict'; 'use strict';
// opts.addWorker(worker)
// opts.approveDomains(options, certs, cb) // opts.approveDomains(options, certs, cb)
module.exports.create = function (opts) { module.exports.create = function (opts) {
opts = opts || { }; // accept all defaults for le.challenges, le.store, le.middleware
opts._workers = []; var le = require('letsencrypt').create(opts);
opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge';
if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create(opts); }
if ('function' !== typeof opts.approveDomains) {
throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
}
function log(debug) { opts.app = opts.app || require('express')().use('/', function (req, res) {
if (!debug) { res.end("Hello, World!\nWith Love,\nLet's Encrypt Express");
return;
}
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift("[le/lib/core.js]");
console.log.apply(console, args);
}
opts.addWorker = function (worker) {
opts._workers.push(worker);
worker.on('online', function () {
log(opts.debug, 'worker is up');
}); });
worker.on('message', function (msg) { opts.listen = function (plainPort, port) {
log(opts.debug, 'Message from worker ' + worker.id); var PromiseA = require('bluebird');
if ('LE_REQUEST' !== (msg && msg.type)) { var promises = [];
log(opts.debug, 'Ignoring irrelevant message'); var plainPorts = plainPort;
log(opts.debug, msg); var ports = port;
return; var servers = [];
if (!plainPorts || !ports) {
plainPorts = 80;
ports = 443;
} }
log(opts.debug, 'about to approveDomains'); if (!Array.isArray(plainPorts)) {
opts.approveDomains(msg.options, msg.certs, function (err, results) { plainPorts = [ plainPorts ];
if (err) { ports = [ ports ];
log(opts.debug, 'Approval got ERROR', err.stack || err);
worker.send({
type: 'LE_RESPONSE'
, domain: msg.domain
, error: { message: err.message, code: err.code, stack: err.stack }
});
return;
} }
var promise; plainPorts.forEach(function (p) {
promises.push(new PromiseA(function (resolve, reject) {
// require('http').createServer(le.middleware(require('https-redirect').create())).listen(p, function () {
/* resolve();
var certs = require('localhost.daplie.com-certificates').merge({ }).on('error', reject);
subject: msg.domain }));
, altnames: [ msg.domain ]
, issuedAt: Date.now()
, expiresAt: Date.now() + (90 * 24 * 60 * 60 * 1000)
}); });
certs.privkey = certs.key.toString('ascii');
certs.cert = certs.cert.toString('ascii');
certs.chain = '';
worker.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs });
return;
// */
if (results.certs) { ports.forEach(function (p) {
promise = opts.letsencrypt.renew(results.options, results.certs); promises.push(new PromiseA(function (resolve, reject) {
} var server = require('https').createServer(le.httpsOptions, le.middleware(le.app)).listen(p, function () {
else { resolve();
promise = opts.letsencrypt.register(results.options); }).on('error', reject);
servers.push(server);
}));
});
if (!Array.isArray(port)) {
servers = servers[0];
} }
promise.then(function (certs) { return servers;
log(opts.debug, 'Approval got certs', certs);
// certs = { subject, domains, issuedAt, expiresAt, privkey, cert, chain };
opts._workers.forEach(function (w) {
w.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs });
});
}, function (err) {
log(opts.debug, 'Approval got ERROR', err.stack || err);
worker.send({ type: 'LE_RESPONSE', domain: msg.domain, error: err });
});
});
});
}; };
return opts;
return le;
}; };

View File

@ -1,87 +0,0 @@
'use strict';
function log(debug) {
if (!debug) {
return;
}
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift("[le/lib/core.js]");
console.log.apply(console, args);
}
module.exports.create = function (opts) {
// if another worker updates the certs,
// receive a copy from master here as well
// and update the sni cache manually
process.on('message', function (msg) {
if ('LE_RESPONSE' === msg.type && msg.certs) {
opts.sni.cacheCerts(msg.certs);
}
});
opts.sni = require('le-sni-auto').create({
renewWithin: opts.renewWithin || (10 * 24 * 60 * 60 * 1000)
, renewBy: opts.renewBy || (5 * 24 * 60 * 60 * 1000)
, getCertificates: function (domain, certs, cb) {
var workerOptions = { domains: [ domain ] };
opts.approveDomains(workerOptions, certs, function (_err, results) {
if (_err) {
cb(_err);
return;
}
var err = new Error("___MESSAGE___");
process.send({ type: 'LE_REQUEST', domain: domain, options: results.options, certs: results.certs });
process.on('message', function (msg) {
log(opts.debug, 'Message from master');
log(opts.debug, msg);
if (msg.domain !== domain) {
return;
}
if (msg.error) {
err.message = msg.error.message || "unknown error sent from cluster master to worker";
err.stack.replace("___MESSAGE___", err.message);
err = {
message: err.message
, stack: err.stack
, data: { options: workerOptions, certs: certs }
};
} else {
err = null;
}
cb(err, msg.certs);
});
});
}
});
opts.httpsOptions = require('localhost.daplie.com-certificates').merge({ SNICallback: opts.sni.sniCallback });
opts.challenge = {
get: opts.getChallenge
|| (opts.challenge && opts.challenge.get)
|| require('le-challenge-fs').create({ webrootPath: opts.webrootPath }).get
};
// opts.challenge.get, opts.acmeChallengePrefix
opts.middleware = require('letsencrypt/lib/middleware').create(opts);
return opts;
};