updates
This commit is contained in:
parent
145dbad411
commit
88406b9c0b
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var homedir = require('homedir');
|
var leBinPath = require('homedir')() + '/.local/share/letsencrypt/bin/letsencrypt';
|
||||||
var leBinPath = homedir() + '/.local/share/letsencrypt/bin/letsencrypt';
|
|
||||||
var lep = require('letsencrypt-python').create(leBinPath);
|
var lep = require('letsencrypt-python').create(leBinPath);
|
||||||
var conf = {
|
var conf = {
|
||||||
domains: process.argv[2]
|
domains: process.argv[2]
|
||||||
|
@ -21,13 +20,13 @@ var bkDefaults = {
|
||||||
, workDir: '/var/lib/letsencrypt'
|
, workDir: '/var/lib/letsencrypt'
|
||||||
, text: true
|
, text: true
|
||||||
};
|
};
|
||||||
var le = require('letsencrypt').create(lep, bkDefaults);
|
var le = require('../').create(lep, bkDefaults);
|
||||||
|
|
||||||
var localCerts = require('localhost.daplie.com-certificates');
|
var localCerts = require('localhost.daplie.com-certificates');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
app.use(le.middleware);
|
app.use(le.middleware());
|
||||||
|
|
||||||
var server = require('http').createServer();
|
var server = require('http').createServer();
|
||||||
server.on('request', app);
|
server.on('request', app);
|
||||||
|
@ -38,14 +37,14 @@ server.listen(80, function () {
|
||||||
var tlsServer = require('https').createServer({
|
var tlsServer = require('https').createServer({
|
||||||
key: localCerts.key
|
key: localCerts.key
|
||||||
, cert: localCerts.cert
|
, cert: localCerts.cert
|
||||||
, SNICallback: le.SNICallback
|
, SNICallback: le.sniCallback
|
||||||
});
|
});
|
||||||
tlsServer.on('request', app);
|
tlsServer.on('request', app);
|
||||||
tlsServer.listen(443, function () {
|
tlsServer.listen(443, function () {
|
||||||
console.log('Listening http', tlsServer.address());
|
console.log('Listening http', tlsServer.address());
|
||||||
});
|
});
|
||||||
|
|
||||||
le.register('certonly', {
|
le.register({
|
||||||
agreeTos: 'agree' === conf.agree
|
agreeTos: 'agree' === conf.agree
|
||||||
, domains: conf.domains.split(',')
|
, domains: conf.domains.split(',')
|
||||||
, email: conf.email
|
, email: conf.email
|
||||||
|
|
164
index.js
164
index.js
|
@ -1,48 +1,93 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports.create = function (lebinpath, defaults, options) {
|
module.exports.create = function (letsencrypt, defaults, options) {
|
||||||
var PromiseA = require('bluebird');
|
var PromiseA = require('bluebird');
|
||||||
var tls = require('tls');
|
var tls = require('tls');
|
||||||
var fs = PromiseA.promisifyAll(require('fs'));
|
var fs = PromiseA.promisifyAll(require('fs'));
|
||||||
var letsencrypt = PromiseA.promisifyAll(require('./le-exec-wrapper'));
|
var utils = require('./utils');
|
||||||
|
var registerAsync = PromiseA.promisify(function (args) {
|
||||||
//var attempts = {}; // should exist in master process only
|
return letsencrypt.registerAsync('certonly', args);
|
||||||
var ipc = {}; // in-process cache
|
});
|
||||||
var count = 0;
|
var fetchAsync = PromiseA.promisify(function (args) {
|
||||||
|
var hostname = args.domains[0];
|
||||||
//var certTpl = "/live/:hostname/cert.pem";
|
var crtpath = defaults.configDir + defaults.fullchainTpl.replace(/:hostname/, hostname);
|
||||||
var certTpl = "/live/:hostname/fullchain.pem";
|
var privpath = defaults.configDir + defaults.privkeyTpl.replace(/:hostname/, hostname);
|
||||||
var privTpl = "/live/:hostname/privkey.pem";
|
|
||||||
|
|
||||||
options.cacheContextsFor = options.cacheContextsFor || (1 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
defaults.webroot = true;
|
|
||||||
defaults.webrootPath = '/srv/www/acme-challenge';
|
|
||||||
|
|
||||||
return letsencrypt.optsAsync(lebinpath).then(function (keys) {
|
|
||||||
var now;
|
|
||||||
var le;
|
|
||||||
|
|
||||||
le = {
|
|
||||||
validate: function () {
|
|
||||||
}
|
|
||||||
, argnames: keys
|
|
||||||
, readCerts: function (hostname) {
|
|
||||||
var crtpath = defaults.configDir + certTpl.replace(/:hostname/, hostname);
|
|
||||||
var privpath = defaults.configDir + privTpl.replace(/:hostname/, hostname);
|
|
||||||
|
|
||||||
return PromiseA.all([
|
return PromiseA.all([
|
||||||
fs.readFileAsync(privpath, 'ascii')
|
fs.readFileAsync(privpath, 'ascii')
|
||||||
, fs.readFileAsync(crtpath, 'ascii')
|
, fs.readFileAsync(crtpath, 'ascii')
|
||||||
// stat the file, not the link
|
// stat the file, not the link
|
||||||
, fs.statAsync(crtpath, 'ascii')
|
, fs.statAsync(crtpath, 'ascii')
|
||||||
]).then(function (arr) {
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
//var attempts = {}; // should exist in master process only
|
||||||
|
var ipc = {}; // in-process cache
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
return arr;
|
var now;
|
||||||
|
var le;
|
||||||
|
|
||||||
|
options.cacheContextsFor = options.cacheContextsFor || (1 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
defaults.webroot = true;
|
||||||
|
|
||||||
|
function merge(args) {
|
||||||
|
var copy = {};
|
||||||
|
Object.keys(defaults).forEach(function (key) {
|
||||||
|
copy[key] = defaults[key];
|
||||||
|
});
|
||||||
|
Object.keys(args).forEach(function (key) {
|
||||||
|
copy[key] = args[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, cacheCerts: function (hostname, certs) {
|
|
||||||
|
function sniCallback(hostname, cb) {
|
||||||
|
var args = merge({});
|
||||||
|
args.domains = [hostname];
|
||||||
|
le.fetch(args, function (err, cache) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cache.context) {
|
||||||
|
cache.context = tls.createSecureContext({
|
||||||
|
key: cache.key // privkey.pem
|
||||||
|
, cert: cache.cert // fullchain.pem
|
||||||
|
//, ciphers // node's defaults are great
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, cache.context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
le = {
|
||||||
|
validate: function () {
|
||||||
|
}
|
||||||
|
, middleware: function () {
|
||||||
|
var serveStatic = require('serve-static')(defaults.webrootPath);
|
||||||
|
var prefix = '/.well-known/acme-challenge/';
|
||||||
|
|
||||||
|
return function (req, res, next) {
|
||||||
|
if (0 === req.url.indexOf(prefix)) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathname = req.url;
|
||||||
|
req.url = req.url.substr(prefix.length - 1);
|
||||||
|
serveStatic(req, res, function (err) {
|
||||||
|
req.url = pathname;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
, SNICallback: sniCallback
|
||||||
|
, sniCallback: sniCallback
|
||||||
|
, cacheCerts: function (args, certs) {
|
||||||
|
var hostname = args.domains[0];
|
||||||
// assume 90 day renewals based on stat time, for now
|
// assume 90 day renewals based on stat time, for now
|
||||||
ipc[hostname] = {
|
ipc[hostname] = {
|
||||||
context: tls.createSecureContext({
|
context: tls.createSecureContext({
|
||||||
|
@ -55,12 +100,32 @@ module.exports.create = function (lebinpath, defaults, options) {
|
||||||
|
|
||||||
return ipc[hostname];
|
return ipc[hostname];
|
||||||
}
|
}
|
||||||
, readAndCacheCerts: function (hostname) {
|
, readAndCacheCerts: function (args) {
|
||||||
return le.readCerts(hostname).then(function (certs) {
|
return fetchAsync(args).then(function (certs) {
|
||||||
return le.cacheCerts(hostname, certs);
|
return le.cacheCerts(args, certs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, get: function (hostname, args, opts, cb) {
|
, register: function (args) {
|
||||||
|
// TODO validate domains and such
|
||||||
|
|
||||||
|
var copy = merge(args);
|
||||||
|
|
||||||
|
if (!utils.isValidDomain(args.domains[0])) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: "invalid domain"
|
||||||
|
, code: "INVALID_DOMAIN"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return le.validate(args.domains).then(function () {
|
||||||
|
return registerAsync(copy).then(function () {
|
||||||
|
return fetchAsync(args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, fetch: function (args, cb) {
|
||||||
|
var hostname = args.domains[0];
|
||||||
|
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
if (count >= 1000) {
|
if (count >= 1000) {
|
||||||
|
@ -75,25 +140,27 @@ module.exports.create = function (lebinpath, defaults, options) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return le.readCerts(hostname).then(function (cached) {
|
return fetchAsync(args).then(function (cached) {
|
||||||
cb(null, cached.context);
|
cb(null, cached.context);
|
||||||
}, function (/*err*/) {
|
}, cb);
|
||||||
var copy = {};
|
}
|
||||||
var arr;
|
, fetchOrRegister: function (args, cb) {
|
||||||
|
le.fetch(args, function (err, hit) {
|
||||||
|
var hostname = args.domains[0];
|
||||||
|
|
||||||
// TODO validate domains and such
|
if (err) {
|
||||||
Object.keys(defaults).forEach(function (key) {
|
cb(err);
|
||||||
copy[key] = defaults[key];
|
return;
|
||||||
});
|
}
|
||||||
Object.keys(args).forEach(function (key) {
|
else if (hit) {
|
||||||
copy[key] = args[key];
|
cb(null, hit);
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
arr = letsencrypt.objToArr(keys, copy);
|
|
||||||
// TODO validate domains empirically before trying le
|
// TODO validate domains empirically before trying le
|
||||||
return letsencrypt.execAsync(lebinpath, arr, opts).then(function () {
|
return registerAsync(args/*, opts*/).then(function () {
|
||||||
// wait at least n minutes
|
// wait at least n minutes
|
||||||
return le.readCerts(hostname).then(function (cached) {
|
return fetchAsync(args).then(function (cached) {
|
||||||
// success
|
// success
|
||||||
cb(null, cached.context);
|
cb(null, cached.context);
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
|
@ -116,5 +183,4 @@ module.exports.create = function (lebinpath, defaults, options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return le;
|
return le;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "letsencrypt",
|
"name": "letsencrypt",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Let's Encrypt for node.js on npm",
|
"description": "Let's Encrypt for node.js on npm",
|
||||||
"main": "le.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,74 +1,37 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var letsencrypt = require('letsencrypt');
|
var config = require('./config');
|
||||||
var networkInterfaces = require('os').networkInterfaces();
|
var Letsencrypt = require('../');
|
||||||
var ipify = require('ipify');
|
var leBinPath = '/home/user/.local/share/letsencrypt/bin/letsencrypt';
|
||||||
|
var LEP = require('letsencrypt-python');
|
||||||
|
var lep = LEP.create(leBinPath);
|
||||||
|
|
||||||
function getSecureContext(le, hostname, cb) {
|
require('./serve-acme-challenges').create({
|
||||||
hostname = hostname.replace(/^www\./, '');
|
configDir: config.configDir
|
||||||
|
|
||||||
function needsRegistration(hostnames, cb) {
|
|
||||||
//
|
|
||||||
// IMPORTANT
|
|
||||||
//
|
|
||||||
// Before attempting a dynamic registration you need to validate that
|
|
||||||
//
|
|
||||||
// * these are hostnames that you expected to exist on the system
|
|
||||||
// * their A records currently point to this ip
|
|
||||||
// * this system's ip hasn't changed
|
|
||||||
//
|
|
||||||
// If you do not check these things, then someone could attack you
|
|
||||||
// and cause you, in return, to have your ip be rate-limit blocked
|
|
||||||
//
|
|
||||||
le.validate(hostnames, {
|
|
||||||
networkInterfaces: networkInterfaces
|
|
||||||
, ipify: ipify
|
|
||||||
}, function (err) {
|
|
||||||
if (err) {
|
|
||||||
cb(null, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// these hostnames need to be registered
|
|
||||||
//
|
|
||||||
cb(null, {
|
|
||||||
email: 'john.doe@gmail.com'
|
|
||||||
, agreeTos: true
|
|
||||||
, domains: ['www.' + hostname, hostname]
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// secure contexts will be cached
|
//var networkInterfaces = require('os').networkInterfaces();
|
||||||
// renewals will be checked in the background
|
//var ipify = require('ipify');
|
||||||
|
|
||||||
le.get(hostname, needsRegistration, function (secureContext) {
|
var le = Letsencrypt.create(
|
||||||
// this will fallback to the localCerts if the domain cannot be registered
|
lep
|
||||||
if (!secureContext) {
|
|
||||||
var localCerts = require('localhost.daplie.com-certificates');
|
|
||||||
secureContext = localCerts;
|
|
||||||
}
|
|
||||||
cb(null, secureContext);
|
|
||||||
}, function (err) {
|
|
||||||
cb(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
letsencrypt.create(
|
|
||||||
'/home/user/.local/share/letsencrypt/bin/letsencrypt'
|
|
||||||
// set some defaults
|
// set some defaults
|
||||||
, { configDir: '/etc/letsencrypt'
|
, { configDir: config.configDir
|
||||||
, workDir: '/var/lib/letsencrypt'
|
, workDir: config.workDir
|
||||||
, logsDir: '/var/log/letsencrypt'
|
, logsDir: config.logsDir
|
||||||
, standalone: true
|
|
||||||
//, webroot: true
|
, webroot: true
|
||||||
//, webrootPath: '/srv/www/acme-challenges/'
|
, webrootPath: config.webrootPath
|
||||||
|
|
||||||
|
, server: LEP.stagingServer
|
||||||
}
|
}
|
||||||
, { cacheContextsFor: 1 * 60 * 60 * 1000 // 1 hour
|
, { cacheContextsFor: 1 * 60 * 60 * 1000 // 1 hour
|
||||||
, cacheRenewChecksFor: 3 * 24 * 60 * 60 * 1000 // 3 days
|
, cacheRenewChecksFor: 3 * 24 * 60 * 60 * 1000 // 3 days
|
||||||
}
|
}
|
||||||
).then(function (le) {
|
);
|
||||||
getSecureContext(le, 'example.com', function (secureContext) {
|
|
||||||
console.log(secureContext);
|
le.register({
|
||||||
});
|
agreeTos: true
|
||||||
|
, domains: ['lds.io']
|
||||||
|
, email: 'coolaj86@gmail.com'
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue