update README and code

This commit is contained in:
AJ ONeal 2016-08-12 01:59:19 -04:00
parent ef26fe59dd
commit feacdbaa74
2 changed files with 133 additions and 38 deletions

112
README.md
View File

@ -1,8 +1,6 @@
le-sni-auto
===========
**DRAFT** this is not yet published to npm
An auto-sni strategy for registering and renewing letsencrypt certificates using SNICallback.
This does a couple of rather simple things:
@ -31,10 +29,10 @@ With node-letsencrypt
var leSni = require('le-sni-auto').create({
notBefore: 10 * 24 * 60 * 60 1000 // do not renew more than 10 days before expiration
, notAfter: 5 * 24 * 60 * 60 1000 // do not wait more than 5 days before expiration
renewWithin: 10 * 24 * 60 * 60 1000 // do not renew more than 10 days before expiration
, renewBy: 5 * 24 * 60 * 60 1000 // do not wait more than 5 days before expiration
, httpsOptions: {
, tlsOptions: {
rejectUnauthorized: true // These options will be used with tls.createSecureContext()
, requestCert: false // in addition to key (privkey.pem) and cert (cert.pem + chain.pem),
, ca: null // which are provided by letsencrypt
@ -58,14 +56,15 @@ var le = require('letsencrypt').create({
var redirectHttps = require('redirect-https').create();
http.createServer(le.middleware(redirectHttps));
var app = require('express')();
var httpsOptions = { SNICallback: le.sni.callback };
httpsOptions = require('localhost.daplie.com-certificates').merge(httpsOptions);
http.createServer(le.handleAcmeOrRedirectToHttps());
https.createServer(dummyCerts, le.handleAcmeOrUse(app)).listen(443);
https.createServer(dummyCerts, le.middleware(app)).listen(443);
```
You can also provide a thunk-style `getCertificates(domain, certs, cb)`.
@ -77,12 +76,13 @@ Standalone
'use strict';
var le = require('letsencrypt').create({
notBefore: 10 * 24 * 60 * 60 1000 // do not renew prior to 10 days before expiration
, notAfter: 5 * 24 * 60 * 60 1000 // do not wait more than 5 days before expiration
var leSni = require('le-sni-auto').create({
renewWithin: 10 * 24 * 60 * 60 1000 // do not renew prior to 10 days before expiration
, renewBy: 5 * 24 * 60 * 60 1000 // do not wait more than 5 days before expiration
// key (privkey.pem) and cert (cert.pem + chain.pem) will be provided by letsencrypt
, httpsOptions: { rejectUnauthorized: true, requestCert: false, ca: null, crl: null }
, tlsOptions: { rejectUnauthorized: true, requestCert: false, ca: null, crl: null }
, getCertificatesAsync: function (domain, certs) {
// return a promise with an object with the following keys:
@ -92,11 +92,87 @@ var le = require('letsencrypt').create({
// some default certificates that work with localhost
// (because default certificates are required as a fallback)
var httpsOptions = require('localhost.daplie.com-certificates').merge({
SNICallback: leSni.sniCallback
});
var dummyCerts = require('localhost.daplie.com-certificates');
dummyCerts.SNICallback = le.sni.sniCallback;
https.createServer(dummyCerts, );
https.createServer(httpsOptions, app);
```
You can also provide a thunk-style `getCertificates(domain, certs, cb)`.
API
===
* create(options)
* `getCertificates(domain, certs, cb)` or `getCertificatesAsync(domain, certs)`
* `renewWithin` (default 7 days, min 3 days)
* `renewBy` (default 2 days, min 12 hours)
* `sniCallback(domain, cb)`
* `cacheCerts(certs)`
.renewWithin
-----------
Specifies the maximum amount of time (in ms) before
the certificate expires to renew it.
Say the cert expires in 90 days and you would like
to renew, **at earliest** 10 days before it expires.
You would set this to `10 * 24 * 60 * 60 * 1000`.
.renewBy
--------
Specifies the maximum amount of time (in ms) before
the certificate expires to renew it.
Say the cert expires in 90 days and you would like
to renew, **at latest** 10 days before it expires.
You would set this to `10 * 24 * 60 * 60 * 1000`.
**MUST** be **less than** `renewWithin`.
.sniCallback()
-----------
This gets passed to `https.createServer(httpsOptions, app)` as `httpsOptions.SNICallback`.
```javascript
var leSni = require('le-sni-auto').create({
renewWithin: 10 * 24 * 60 * 60 1000
});
var httpsOptions = require('localhost.daplie.com-certificates').merge({
SNICallback: leSni.sniCallback
});
function app(req, res) {
res.end("Hello, World!");
}
https.createServer(httpsOptions, app);
```
.cacheCerts()
-----------
Manually load a certificate into the cache.
This is useful in a cluster environment where the master
may wish to inform multiple workers of a new or renewed certificate.
```
leSni.cacheCerts({
, privkey: '<<privkey.pem>>'
, cert: '<<cert.pem + chain.pem>>'
, subject: 'example.com'
, altnames: [ 'example.com', 'www.example.com' ]
, issuedAt: 1470975565000
, expiresAt: 1478751565000
});
```

View File

@ -1,21 +1,39 @@
'use strict';
// autoSni = { notBefore, notAfter, getCertificates, httpsOptions, _dbg_now }
module.exports.create = function (autoSni) {
var DAY = 24 * 60 * 60 * 1000;
var MIN = 60 * 1000;
var defaults = {
// don't renew before the renewWithin period
renewWithin: 7 * DAY
, _renewWithinMin: 3 * DAY
// renew before the renewBy period
, renewBy: 2 * DAY
, _renewByMin: Math.floor(DAY / 2)
// just to account for clock skew really
, _dropDead: 5 * MIN
};
// autoSni = { renewWithin, renewBy, getCertificates, tlsOptions, _dbg_now }
module.exports.create = function (autoSni) {
if (!autoSni.getCertificatesAsync) { autoSni.getCertificatesAsync = require('bluebird').promisify(autoSni.getCertificates); }
if (!autoSni.notBefore) { throw new Error("must supply options.notBefore (and options.notAfter)"); }
if (!autoSni.notAfter) { autoSni.notAfter = autoSni.notBefore - (3 * DAY); }
if (!autoSni.httpsOptions) { autoSni.httpsOptions = {}; }
if (!autoSni.renewWithin) { autoSni.renewWithin = autoSni.notBefore || defaults.renewWithin; }
if (autoSni.renewWithin < defaults._renewWithinMin) {
throw new Error("options.renewWithin should be at least 3 days");
}
if (!autoSni.renewBy) { autoSni.renewBy = autoSni.notBefore || defaults.renewBy; }
if (autoSni.renewBy < defaults._renewByMin) {
throw new Error("options.renewBy should be at least 12 hours");
}
if (!autoSni.tlsOptions) { autoSni.tlsOptions = autoSni.httpsOptions || {}; }
autoSni._dropDead = defaults._dropDead;
//autoSni.renewWithin = autoSni.notBefore; // i.e. 15 days
autoSni.renewWindow = autoSni.notBefore - autoSni.notAfter; // i.e. 1 day
//autoSni.renewRatio = autoSni.notBefore = autoSni.renewWindow; // i.e. 1/15 (6.67%)
autoSni._renewWindow = autoSni.renewWithin - autoSni.renewBy; // i.e. 1 day
//autoSni.renewRatio = autoSni.notBefore = autoSni._renewWindow; // i.e. 1/15 (6.67%)
@ -32,31 +50,32 @@ module.exports.create = function (autoSni) {
// in-process cache
_ipc: {}
// just to account for clock skew
, _fiveMin: 5 * MIN
, getOptions: function () {
return JSON.parse(JSON.stringify(defaults));
}
// cache and format incoming certs
, _cacheCerts: function (certs) {
, cacheCerts: function (certs) {
var meta = {
certs: certs
, tlsContext: 'string' === typeof certs.cert && tls.createSecureContext({
key: certs.privkey
, cert: certs.cert + certs.chain
, rejectUnauthorized: autoSni.httpsOptions.rejectUnauthorized
, rejectUnauthorized: autoSni.tlsOptions.rejectUnauthorized
, requestCert: autoSni.httpsOptions.requestCert // request peer verification
, ca: autoSni.httpsOptions.ca // this chain is for incoming peer connctions
, crl: autoSni.httpsOptions.crl // this crl is for incoming peer connections
, requestCert: autoSni.tlsOptions.requestCert // request peer verification
, ca: autoSni.tlsOptions.ca // this chain is for incoming peer connctions
, crl: autoSni.tlsOptions.crl // this crl is for incoming peer connections
}) || { '_fake_tls_context_': true }
, subject: certs.subject
// stagger renewal time by a little bit of randomness
, renewAt: (certs.expiresAt - (autoSni.notBefore - (autoSni.renewWindow * Math.random())))
, renewAt: (certs.expiresAt - (autoSni.renewWithin - (autoSni._renewWindow * Math.random())))
// err just barely on the side of safety
, expiresNear: certs.expiresAt - autoSni._fiveMin
, expiresNear: certs.expiresAt - autoSni._dropDead
};
var link = { subject: certs.subject };
@ -99,7 +118,7 @@ module.exports.create = function (autoSni) {
// give the cert some time (2-5 min) to be validated and replaced before trying again
certMeta.renewAt = (autoSni._dbg_now || Date.now()) + (2 * MIN) + (3 * MIN * Math.random());
// let the update happen in the background
autoSni.getCertificatesAsync(domain, certMeta.certs).then(autoSni._cacheCerts);
autoSni.getCertificatesAsync(domain, certMeta.certs).then(autoSni.cacheCerts);
}
// return the valid cert right away
@ -108,7 +127,7 @@ module.exports.create = function (autoSni) {
}
// promise the non-existent or expired cert
promise.then(autoSni._cacheCerts).then(function (certMeta) {
promise.then(autoSni.cacheCerts).then(function (certMeta) {
cb(null, certMeta.tlsContext);
}, function (err) {
console.error('ERROR in le-sni-auto:');