update README and code
This commit is contained in:
parent
ef26fe59dd
commit
feacdbaa74
112
README.md
112
README.md
|
@ -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
|
||||
});
|
||||
```
|
||||
|
|
59
index.js
59
index.js
|
@ -1,21 +1,39 @@
|
|||
'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) {
|
||||
|
||||
var DAY = 24 * 60 * 60 * 1000;
|
||||
var MIN = 60 * 1000;
|
||||
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.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._dropDead = defaults._dropDead;
|
||||
//autoSni.renewWithin = autoSni.notBefore; // i.e. 15 days
|
||||
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:');
|
||||
|
|
Loading…
Reference in New Issue