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 le-sni-auto
=========== ===========
**DRAFT** this is not yet published to npm
An auto-sni strategy for registering and renewing letsencrypt certificates using SNICallback. An auto-sni strategy for registering and renewing letsencrypt certificates using SNICallback.
This does a couple of rather simple things: This does a couple of rather simple things:
@ -31,10 +29,10 @@ With node-letsencrypt
var leSni = require('le-sni-auto').create({ var leSni = require('le-sni-auto').create({
notBefore: 10 * 24 * 60 * 60 1000 // do not renew more than 10 days before expiration renewWithin: 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 , 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() rejectUnauthorized: true // These options will be used with tls.createSecureContext()
, requestCert: false // in addition to key (privkey.pem) and cert (cert.pem + chain.pem), , requestCert: false // in addition to key (privkey.pem) and cert (cert.pem + chain.pem),
, ca: null // which are provided by letsencrypt , 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 app = require('express')();
var httpsOptions = { SNICallback: le.sni.callback }; var httpsOptions = { SNICallback: le.sni.callback };
httpsOptions = require('localhost.daplie.com-certificates').merge(httpsOptions); httpsOptions = require('localhost.daplie.com-certificates').merge(httpsOptions);
https.createServer(dummyCerts, le.middleware(app)).listen(443);
http.createServer(le.handleAcmeOrRedirectToHttps());
https.createServer(dummyCerts, le.handleAcmeOrUse(app)).listen(443);
``` ```
You can also provide a thunk-style `getCertificates(domain, certs, cb)`. You can also provide a thunk-style `getCertificates(domain, certs, cb)`.
@ -77,12 +76,13 @@ Standalone
'use strict'; 'use strict';
var le = require('letsencrypt').create({
notBefore: 10 * 24 * 60 * 60 1000 // do not renew prior to 10 days before expiration var leSni = require('le-sni-auto').create({
, notAfter: 5 * 24 * 60 * 60 1000 // do not wait more than 5 days before expiration 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 // 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) { , getCertificatesAsync: function (domain, certs) {
// return a promise with an object with the following keys: // 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'); https.createServer(httpsOptions, app);
dummyCerts.SNICallback = le.sni.sniCallback;
https.createServer(dummyCerts, );
``` ```
You can also provide a thunk-style `getCertificates(domain, certs, cb)`. 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'; 'use strict';
// autoSni = { notBefore, notAfter, getCertificates, httpsOptions, _dbg_now } 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) { module.exports.create = function (autoSni) {
var DAY = 24 * 60 * 60 * 1000;
var MIN = 60 * 1000;
if (!autoSni.getCertificatesAsync) { autoSni.getCertificatesAsync = require('bluebird').promisify(autoSni.getCertificates); } 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.renewWithin) { autoSni.renewWithin = autoSni.notBefore || defaults.renewWithin; }
if (!autoSni.notAfter) { autoSni.notAfter = autoSni.notBefore - (3 * DAY); } if (autoSni.renewWithin < defaults._renewWithinMin) {
if (!autoSni.httpsOptions) { autoSni.httpsOptions = {}; } 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.renewWithin = autoSni.notBefore; // i.e. 15 days autoSni._dropDead = defaults._dropDead;
autoSni.renewWindow = autoSni.notBefore - autoSni.notAfter; // i.e. 1 day //autoSni.renewWithin = autoSni.notBefore; // i.e. 15 days
//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 // in-process cache
_ipc: {} _ipc: {}
// just to account for clock skew , getOptions: function () {
, _fiveMin: 5 * MIN return JSON.parse(JSON.stringify(defaults));
}
// cache and format incoming certs // cache and format incoming certs
, _cacheCerts: function (certs) { , cacheCerts: function (certs) {
var meta = { var meta = {
certs: certs certs: certs
, tlsContext: 'string' === typeof certs.cert && tls.createSecureContext({ , tlsContext: 'string' === typeof certs.cert && tls.createSecureContext({
key: certs.privkey key: certs.privkey
, cert: certs.cert + certs.chain , cert: certs.cert + certs.chain
, rejectUnauthorized: autoSni.httpsOptions.rejectUnauthorized , rejectUnauthorized: autoSni.tlsOptions.rejectUnauthorized
, requestCert: autoSni.httpsOptions.requestCert // request peer verification , requestCert: autoSni.tlsOptions.requestCert // request peer verification
, ca: autoSni.httpsOptions.ca // this chain is for incoming peer connctions , ca: autoSni.tlsOptions.ca // this chain is for incoming peer connctions
, crl: autoSni.httpsOptions.crl // this crl is for incoming peer connections , crl: autoSni.tlsOptions.crl // this crl is for incoming peer connections
}) || { '_fake_tls_context_': true } }) || { '_fake_tls_context_': true }
, subject: certs.subject , subject: certs.subject
// stagger renewal time by a little bit of randomness // 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 // err just barely on the side of safety
, expiresNear: certs.expiresAt - autoSni._fiveMin , expiresNear: certs.expiresAt - autoSni._dropDead
}; };
var link = { subject: certs.subject }; 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 // 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()); certMeta.renewAt = (autoSni._dbg_now || Date.now()) + (2 * MIN) + (3 * MIN * Math.random());
// let the update happen in the background // 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 // return the valid cert right away
@ -108,7 +127,7 @@ module.exports.create = function (autoSni) {
} }
// promise the non-existent or expired cert // 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); cb(null, certMeta.tlsContext);
}, function (err) { }, function (err) {
console.error('ERROR in le-sni-auto:'); console.error('ERROR in le-sni-auto:');