wip: API looks good, on to testing
This commit is contained in:
parent
9ab7844ea8
commit
0dd3641dc2
|
@ -0,0 +1,30 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Greenlock = require("./");
|
||||||
|
var greenlockOptions = {
|
||||||
|
cluster: false,
|
||||||
|
|
||||||
|
maintainerEmail: "greenlock-test@rootprojects.org",
|
||||||
|
servername: "foo-gl.test.utahrust.com",
|
||||||
|
serverId: "bowie.local"
|
||||||
|
|
||||||
|
/*
|
||||||
|
manager: {
|
||||||
|
module: "greenlock-manager-sequelize",
|
||||||
|
dbUrl: "postgres://foo@bar:baz/quux"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
Greenlock.create(greenlockOptions)
|
||||||
|
.worker(function(glx) {
|
||||||
|
console.info();
|
||||||
|
console.info("Hello from worker");
|
||||||
|
|
||||||
|
glx.serveApp(function(req, res) {
|
||||||
|
res.end("Hello, Encrypted World!");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.master(function() {
|
||||||
|
console.log("Hello from master");
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
require("./lib/compat");
|
||||||
|
|
||||||
|
// Greenlock Express
|
||||||
|
var GLE = module.exports;
|
||||||
|
|
||||||
|
// opts.approveDomains(options, certs, cb)
|
||||||
|
GLE.create = function(opts) {
|
||||||
|
if (!opts) {
|
||||||
|
opts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// just for ironic humor
|
||||||
|
["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) {
|
||||||
|
if (opts[k]) {
|
||||||
|
opts.cluster = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// we want to be minimal, and only load the code that's necessary to load
|
||||||
|
if (opts.cluster) {
|
||||||
|
if (require("cluster").isMaster) {
|
||||||
|
return require("./master.js").create(opts);
|
||||||
|
}
|
||||||
|
return require("./worker.js").create(opts);
|
||||||
|
}
|
||||||
|
return require("./single.js").create(opts);
|
||||||
|
};
|
|
@ -0,0 +1,102 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var HttpMiddleware = module.exports;
|
||||||
|
var servernameRe = /^[a-z0-9\.\-]+$/i;
|
||||||
|
var challengePrefix = "/.well-known/acme-challenge/";
|
||||||
|
|
||||||
|
HttpMiddleware.create = function(gl, defaultApp) {
|
||||||
|
if (defaultApp && "function" !== typeof defaultApp) {
|
||||||
|
throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})");
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(req, res, next) {
|
||||||
|
var hostname = HttpMiddleware.sanitizeHostname(req);
|
||||||
|
|
||||||
|
req.on("error", function(err) {
|
||||||
|
explainError(gl, err, "http_01_middleware_socket", hostname);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (skipIfNeedBe(req, res, next, defaultApp, hostname)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = req.url.slice(challengePrefix.length);
|
||||||
|
|
||||||
|
gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token })
|
||||||
|
.then(function(result) {
|
||||||
|
respondWithGrace(res, result, hostname, token);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function skipIfNeedBe(req, res, next, defaultApp, hostname) {
|
||||||
|
if (!hostname || 0 !== req.url.indexOf(challengePrefix)) {
|
||||||
|
if ("function" === typeof defaultApp) {
|
||||||
|
defaultApp(req, res, next);
|
||||||
|
} else if ("function" === typeof next) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function respondWithGrace(res, result, hostname, token) {
|
||||||
|
var keyAuth = result.keyAuthorization;
|
||||||
|
if (keyAuth && "string" === typeof keyAuth) {
|
||||||
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
res.end(keyAuth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||||
|
res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function explainError(gl, err, ctx, hostname) {
|
||||||
|
if (!err.servername) {
|
||||||
|
err.servername = hostname;
|
||||||
|
}
|
||||||
|
if (!err.context) {
|
||||||
|
err.context = ctx;
|
||||||
|
}
|
||||||
|
(gl.notify || gl._notify)("error", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
function respondToError(gl, res, err, ctx, hostname) {
|
||||||
|
err = explainError(gl, err, ctx, hostname);
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("Internal Server Error: See logs for details.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMiddleware.getHostname = function(req) {
|
||||||
|
return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || "");
|
||||||
|
};
|
||||||
|
HttpMiddleware.sanitizeHostname = function(req) {
|
||||||
|
// we can trust XFH because spoofing causes no ham in this limited use-case scenario
|
||||||
|
// (and only telebit would be legitimately setting XFH)
|
||||||
|
var servername = HttpMiddleware.getHostname(req)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/:.*/, "");
|
||||||
|
try {
|
||||||
|
req.hostname = servername;
|
||||||
|
} catch (e) {
|
||||||
|
// read-only express property
|
||||||
|
}
|
||||||
|
if (req.headers["x-forwarded-host"]) {
|
||||||
|
req.headers["x-forwarded-host"] = servername;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
req.headers.host = servername;
|
||||||
|
} catch (e) {
|
||||||
|
// TODO is this a possible error?
|
||||||
|
}
|
||||||
|
|
||||||
|
return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || "";
|
||||||
|
};
|
|
@ -0,0 +1,133 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var SanitizeHost = module.exports;
|
||||||
|
var HttpMiddleware = require("./http-middleware.js");
|
||||||
|
|
||||||
|
SanitizeHost.create = function(gl, app) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
function realNext() {
|
||||||
|
if ("function" === typeof app) {
|
||||||
|
app(req, res);
|
||||||
|
} else if ("function" === typeof next) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("Error: no middleware assigned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostname = HttpMiddleware.getHostname(req);
|
||||||
|
// Replace the hostname, and get the safe version
|
||||||
|
var safehost = HttpMiddleware.sanitizeHostname(req);
|
||||||
|
|
||||||
|
// if no hostname, move along
|
||||||
|
if (!hostname) {
|
||||||
|
realNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there were unallowed characters, complain
|
||||||
|
if (safehost.length !== hostname.length) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.end("Malformed HTTP Header: 'Host: " + hostname + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
|
||||||
|
if (req.socket.encrypted) {
|
||||||
|
if (req.socket && "string" === typeof req.socket.servername) {
|
||||||
|
// Workaround for https://github.com/nodejs/node/issues/22389
|
||||||
|
if (!SanitizeHost._checkServername(safehost, req.socket)) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||||
|
res.end(
|
||||||
|
"<h1>Domain Fronting Error</h1>" +
|
||||||
|
"<p>This connection was secured using TLS/SSL for '" +
|
||||||
|
(req.socket.servername || "").toLowerCase() +
|
||||||
|
"'</p>" +
|
||||||
|
"<p>The HTTP request specified 'Host: " +
|
||||||
|
safehost +
|
||||||
|
"', which is (obviously) different.</p>" +
|
||||||
|
"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
else if (safehost && !gl._skip_fronting_check) {
|
||||||
|
|
||||||
|
// We used to print a log message here, but it turns out that it's
|
||||||
|
// really common for IoT devices to not use SNI (as well as many bots
|
||||||
|
// and such).
|
||||||
|
// It was common for the log message to pop up as the first request
|
||||||
|
// to the server, and that was confusing. So instead now we do nothing.
|
||||||
|
|
||||||
|
//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
|
||||||
|
//gl._skip_fronting_check = true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// carry on
|
||||||
|
realNext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var warnDomainFronting = true;
|
||||||
|
var warnUnexpectedError = true;
|
||||||
|
SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
||||||
|
var servername = (tlsSocket.servername || "").toLowerCase();
|
||||||
|
|
||||||
|
// acceptable: older IoT devices may lack SNI support
|
||||||
|
if (!servername) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// acceptable: odd... but acceptable
|
||||||
|
if (!safeHost) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (safeHost === servername) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("function" !== typeof tlsSocket.getCertificate) {
|
||||||
|
// domain fronting attacks allowed
|
||||||
|
if (warnDomainFronting) {
|
||||||
|
// https://github.com/nodejs/node/issues/24095
|
||||||
|
console.warn(
|
||||||
|
"Warning: node " +
|
||||||
|
process.version +
|
||||||
|
" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater."
|
||||||
|
);
|
||||||
|
warnDomainFronting = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection established with servername and session is re-used for allowed name
|
||||||
|
// See https://github.com/nodejs/node/issues/24095
|
||||||
|
var cert = tlsSocket.getCertificate();
|
||||||
|
try {
|
||||||
|
// TODO optimize / cache?
|
||||||
|
// *should* always have a string, right?
|
||||||
|
// *should* always be lowercase already, right?
|
||||||
|
if (
|
||||||
|
(cert.subject.CN || "").toLowerCase() !== safeHost &&
|
||||||
|
!(cert.subjectaltname || "").split(/,\s+/).some(function(name) {
|
||||||
|
// always prefixed with "DNS:"
|
||||||
|
return safeHost === name.slice(4).toLowerCase();
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// not sure what else to do in this situation...
|
||||||
|
if (warnUnexpectedError) {
|
||||||
|
console.warn("Warning: encoutered error while performing domain fronting check: " + e.message);
|
||||||
|
warnUnexpectedError = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
334
index.js
334
index.js
|
@ -1,334 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var PromiseA;
|
|
||||||
try {
|
|
||||||
PromiseA = require("bluebird");
|
|
||||||
} catch (e) {
|
|
||||||
PromiseA = global.Promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
// opts.approveDomains(options, certs, cb)
|
|
||||||
module.exports.create = function(opts) {
|
|
||||||
// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
|
|
||||||
if (!opts._communityPackage) {
|
|
||||||
opts._communityPackage = "greenlock-express.js";
|
|
||||||
opts._communityPackageVersion = require("./package.json").version;
|
|
||||||
}
|
|
||||||
|
|
||||||
function explainError(e) {
|
|
||||||
console.error("Error:" + e.message);
|
|
||||||
if ("EACCES" === e.errno) {
|
|
||||||
console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'.");
|
|
||||||
console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ("EADDRINUSE" === e.errno) {
|
|
||||||
console.error("'" + e.address + ":" + e.port + "' is already being used by some other program.");
|
|
||||||
console.error("You probably need to stop that program or restart your computer.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.error(e.code + ": '" + e.address + ":" + e.port + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createPlain(plainPort) {
|
|
||||||
if (!plainPort) {
|
|
||||||
plainPort = 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = String(plainPort).split(":");
|
|
||||||
var p = parts.pop();
|
|
||||||
var addr = parts
|
|
||||||
.join(":")
|
|
||||||
.replace(/^\[/, "")
|
|
||||||
.replace(/\]$/, "");
|
|
||||||
var args = [];
|
|
||||||
var httpType;
|
|
||||||
var server;
|
|
||||||
var validHttpPort = parseInt(p, 10) >= 0;
|
|
||||||
|
|
||||||
if (addr) {
|
|
||||||
args[1] = addr;
|
|
||||||
}
|
|
||||||
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
|
|
||||||
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
|
|
||||||
}
|
|
||||||
|
|
||||||
var mw = greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")()));
|
|
||||||
server = require("http").createServer(function(req, res) {
|
|
||||||
req.on("error", function(err) {
|
|
||||||
console.error("Insecure Request Network Connection Error:");
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
mw(req, res);
|
|
||||||
});
|
|
||||||
httpType = "http";
|
|
||||||
|
|
||||||
return {
|
|
||||||
server: server,
|
|
||||||
listen: function() {
|
|
||||||
return new PromiseA(function(resolve, reject) {
|
|
||||||
args[0] = p;
|
|
||||||
args.push(function() {
|
|
||||||
if (!greenlock.servername) {
|
|
||||||
if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
|
|
||||||
greenlock.servername = greenlock.approvedDomains[0];
|
|
||||||
}
|
|
||||||
if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) {
|
|
||||||
greenlock.servername = greenlock.approvedDomains[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!greenlock.servername) {
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return greenlock
|
|
||||||
.check({ domains: [greenlock.servername] })
|
|
||||||
.then(function(certs) {
|
|
||||||
if (certs) {
|
|
||||||
return {
|
|
||||||
key: Buffer.from(certs.privkey, "ascii"),
|
|
||||||
cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
console.info(
|
|
||||||
"Fetching certificate for '%s' to use as default for HTTPS server...",
|
|
||||||
greenlock.servername
|
|
||||||
);
|
|
||||||
return new PromiseA(function(resolve, reject) {
|
|
||||||
// using SNICallback because all options will be set
|
|
||||||
greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return greenlock
|
|
||||||
.check({ domains: [greenlock.servername] })
|
|
||||||
.then(function(certs) {
|
|
||||||
resolve({
|
|
||||||
key: Buffer.from(certs.privkey, "ascii"),
|
|
||||||
cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
server.listen.apply(server, args).on("error", function(e) {
|
|
||||||
if (server.listenerCount("error") < 2) {
|
|
||||||
console.warn("Did not successfully create http server and bind to port '" + p + "':");
|
|
||||||
explainError(e);
|
|
||||||
process.exit(41);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _create(port) {
|
|
||||||
if (!port) {
|
|
||||||
port = 443;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = String(port).split(":");
|
|
||||||
var p = parts.pop();
|
|
||||||
var addr = parts
|
|
||||||
.join(":")
|
|
||||||
.replace(/^\[/, "")
|
|
||||||
.replace(/\]$/, "");
|
|
||||||
var args = [];
|
|
||||||
var httpType;
|
|
||||||
var server;
|
|
||||||
var validHttpPort = parseInt(p, 10) >= 0;
|
|
||||||
|
|
||||||
if (addr) {
|
|
||||||
args[1] = addr;
|
|
||||||
}
|
|
||||||
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
|
|
||||||
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
|
|
||||||
}
|
|
||||||
|
|
||||||
var https;
|
|
||||||
try {
|
|
||||||
https = require("spdy");
|
|
||||||
greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
|
|
||||||
httpType = "http2 (spdy/h2)";
|
|
||||||
} catch (e) {
|
|
||||||
https = require("https");
|
|
||||||
httpType = "https";
|
|
||||||
}
|
|
||||||
var sniCallback = greenlock.tlsOptions.SNICallback;
|
|
||||||
greenlock.tlsOptions.SNICallback = function(domain, cb) {
|
|
||||||
sniCallback(domain, function(err, context) {
|
|
||||||
cb(err, context);
|
|
||||||
|
|
||||||
if (!context || server._hasDefaultSecureContext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!domain) {
|
|
||||||
domain = greenlock.servername;
|
|
||||||
}
|
|
||||||
if (!domain) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return greenlock
|
|
||||||
.check({ domains: [domain] })
|
|
||||||
.then(function(certs) {
|
|
||||||
// ignore the case that check doesn't have all the right args here
|
|
||||||
// to get the same certs that it just got (eventually the right ones will come in)
|
|
||||||
if (!certs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (server.setSecureContext) {
|
|
||||||
// only available in node v11.0+
|
|
||||||
server.setSecureContext({
|
|
||||||
key: Buffer.from(certs.privkey, "ascii"),
|
|
||||||
cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
|
|
||||||
});
|
|
||||||
console.info("Using '%s' as default certificate", domain);
|
|
||||||
} else {
|
|
||||||
console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
|
|
||||||
}
|
|
||||||
server._hasDefaultSecureContext = true;
|
|
||||||
})
|
|
||||||
.catch(function(/*e*/) {
|
|
||||||
// this may be that the test.example.com was requested, but it's listed
|
|
||||||
// on the cert for demo.example.com which is in its own directory, not the other
|
|
||||||
//console.warn("Unusual error: couldn't get newly authorized certificate:");
|
|
||||||
//console.warn(e.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (greenlock.tlsOptions.cert) {
|
|
||||||
server._hasDefaultSecureContext = true;
|
|
||||||
if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) {
|
|
||||||
console.warn(
|
|
||||||
"Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mw = greenlock.middleware.sanitizeHost(function(req, res) {
|
|
||||||
try {
|
|
||||||
greenlock.app(req, res);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:");
|
|
||||||
console.error(e);
|
|
||||||
try {
|
|
||||||
res.statusCode = 500;
|
|
||||||
res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler.");
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
// (headers may have already been sent, etc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server = https.createServer(greenlock.tlsOptions, function(req, res) {
|
|
||||||
/*
|
|
||||||
// Don't do this yet
|
|
||||||
req.on("error", function(err) {
|
|
||||||
console.error("HTTPS Request Network Connection Error:");
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
mw(req, res);
|
|
||||||
});
|
|
||||||
server.type = httpType;
|
|
||||||
|
|
||||||
return {
|
|
||||||
server: server,
|
|
||||||
listen: function() {
|
|
||||||
return new PromiseA(function(resolve) {
|
|
||||||
args[0] = p;
|
|
||||||
args.push(function() {
|
|
||||||
resolve(/*server*/);
|
|
||||||
});
|
|
||||||
server.listen.apply(server, args).on("error", function(e) {
|
|
||||||
if (server.listenerCount("error") < 2) {
|
|
||||||
console.warn("Did not successfully create http server and bind to port '" + p + "':");
|
|
||||||
explainError(e);
|
|
||||||
process.exit(41);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: 'greenlock' is just 'opts' renamed
|
|
||||||
var greenlock = require("greenlock").create(opts);
|
|
||||||
|
|
||||||
if (!opts.app) {
|
|
||||||
opts.app = function(req, res) {
|
|
||||||
res.end("Hello, World!\nWith Love,\nGreenlock for Express.js");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.listen = function(plainPort, port, fnPlain, fn) {
|
|
||||||
var server;
|
|
||||||
var plainServer;
|
|
||||||
|
|
||||||
// If there is only one handler for the `listening` (i.e. TCP bound) event
|
|
||||||
// then we want to use it as HTTPS (backwards compat)
|
|
||||||
if (!fn) {
|
|
||||||
fn = fnPlain;
|
|
||||||
fnPlain = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj1 = _createPlain(plainPort, true);
|
|
||||||
var obj2 = _create(port, false);
|
|
||||||
|
|
||||||
plainServer = obj1.server;
|
|
||||||
server = obj2.server;
|
|
||||||
|
|
||||||
server.then = obj1.listen().then(function(tlsOptions) {
|
|
||||||
if (tlsOptions) {
|
|
||||||
if (server.setSecureContext) {
|
|
||||||
// only available in node v11.0+
|
|
||||||
server.setSecureContext(tlsOptions);
|
|
||||||
console.info("Using '%s' as default certificate", greenlock.servername);
|
|
||||||
} else {
|
|
||||||
console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
|
|
||||||
}
|
|
||||||
server._hasDefaultSecureContext = true;
|
|
||||||
}
|
|
||||||
return obj2.listen().then(function() {
|
|
||||||
// Report plain http status
|
|
||||||
if ("function" === typeof fnPlain) {
|
|
||||||
fnPlain.apply(plainServer);
|
|
||||||
} else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) {
|
|
||||||
console.info(
|
|
||||||
"[:" +
|
|
||||||
(plainServer.address().port || plainServer.address()) +
|
|
||||||
"] Handling ACME challenges and redirecting to " +
|
|
||||||
server.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report h2/https status
|
|
||||||
if ("function" === typeof fn) {
|
|
||||||
fn.apply(server);
|
|
||||||
} else if (!server.listenerCount("listening")) {
|
|
||||||
console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).then;
|
|
||||||
|
|
||||||
server.unencrypted = plainServer;
|
|
||||||
return server;
|
|
||||||
};
|
|
||||||
opts.middleware.acme = function(opts) {
|
|
||||||
return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts)));
|
|
||||||
};
|
|
||||||
opts.middleware.secure = function(app) {
|
|
||||||
return greenlock.middleware.sanitizeHost(app);
|
|
||||||
};
|
|
||||||
|
|
||||||
return greenlock;
|
|
||||||
};
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// this is the stuff that should run in the main foreground process,
|
||||||
|
// whether it's single or master
|
||||||
|
|
||||||
|
var major = process.versions.node.split(".")[0];
|
||||||
|
var minor = process.versions.node.split(".")[1];
|
||||||
|
var _hasSetSecureContext = false;
|
||||||
|
var shouldUpgrade = false;
|
||||||
|
|
||||||
|
// TODO can we trust earlier versions as well?
|
||||||
|
if (major >= 12) {
|
||||||
|
_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext;
|
||||||
|
} else {
|
||||||
|
_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO document in issues
|
||||||
|
if (!_hasSetSecureContext) {
|
||||||
|
// TODO this isn't necessary if greenlock options are set with options.cert
|
||||||
|
console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext().");
|
||||||
|
console.warn(" The default certificate may not be set.");
|
||||||
|
shouldUpgrade = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (major < 11 || (11 === major && minor < 2)) {
|
||||||
|
// https://github.com/nodejs/node/issues/24095
|
||||||
|
console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate().");
|
||||||
|
console.warn(" This is necessary to guard against domain fronting attacks.");
|
||||||
|
shouldUpgrade = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpgrade) {
|
||||||
|
console.warn("Warning: Please upgrade to node v11.2.0 or greater.");
|
||||||
|
console.warn();
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
require("./main.js");
|
||||||
|
|
||||||
|
var Master = module.exports;
|
||||||
|
|
||||||
|
var cluster = require("cluster");
|
||||||
|
var os = require("os");
|
||||||
|
var Greenlock = require("@root/greenlock");
|
||||||
|
var pkg = require("./package.json");
|
||||||
|
|
||||||
|
Master.create = function(opts) {
|
||||||
|
var workers = [];
|
||||||
|
var resolveCb;
|
||||||
|
var readyCb;
|
||||||
|
var _kicked = false;
|
||||||
|
|
||||||
|
var packageAgent = pkg.name + "/" + pkg.version;
|
||||||
|
if ("string" === typeof opts.packageAgent) {
|
||||||
|
opts.packageAgent += " ";
|
||||||
|
} else {
|
||||||
|
opts.packageAgent = "";
|
||||||
|
}
|
||||||
|
opts.packageAgent += packageAgent;
|
||||||
|
var greenlock = Greenlock.create(opts);
|
||||||
|
|
||||||
|
var ready = new Promise(function(resolve) {
|
||||||
|
resolveCb = resolve;
|
||||||
|
}).then(function(fn) {
|
||||||
|
readyCb = fn;
|
||||||
|
});
|
||||||
|
|
||||||
|
function kickoff() {
|
||||||
|
if (_kicked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_kicked = true;
|
||||||
|
|
||||||
|
console.log("TODO: start the workers and such...");
|
||||||
|
// handle messages from workers
|
||||||
|
workers.push(null);
|
||||||
|
ready.then(function(fn) {
|
||||||
|
// not sure what this API should be yet
|
||||||
|
fn({
|
||||||
|
//workers: workers.slice(0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var master = {
|
||||||
|
worker: function() {
|
||||||
|
kickoff();
|
||||||
|
return master;
|
||||||
|
},
|
||||||
|
master: function(fn) {
|
||||||
|
if (readyCb) {
|
||||||
|
throw new Error("can't call master twice");
|
||||||
|
}
|
||||||
|
kickoff();
|
||||||
|
resolveCb(fn);
|
||||||
|
return master;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// opts.approveDomains(options, certs, cb)
|
||||||
|
GLE.create = function(opts) {
|
||||||
|
GLE._spawnWorkers(opts);
|
||||||
|
|
||||||
|
gl.tlsOptions = {};
|
||||||
|
|
||||||
|
|
||||||
|
return master;
|
||||||
|
};
|
||||||
|
|
||||||
|
function range(n) {
|
||||||
|
return new Array(n).join(",").split(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
Master._spawnWorkers = function(opts) {
|
||||||
|
var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length;
|
||||||
|
|
||||||
|
var numWorkers = parseInt(opts.numWorkers, 10);
|
||||||
|
if (!numWorkers) {
|
||||||
|
if (numCpus <= 2) {
|
||||||
|
numWorkers = numCpus;
|
||||||
|
} else {
|
||||||
|
numWorkers = numCpus - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range(numWorkers).map(function() {
|
||||||
|
return cluster.fork();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,128 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Servers = module.exports;
|
||||||
|
|
||||||
|
var http = require("http");
|
||||||
|
var HttpMiddleware = require("./http-middleware.js");
|
||||||
|
var HttpsMiddleware = require("./https-middleware.js");
|
||||||
|
var sni = require("./sni.js");
|
||||||
|
|
||||||
|
Servers.create = function(greenlock, opts) {
|
||||||
|
var servers = {};
|
||||||
|
var _httpServer;
|
||||||
|
var _httpsServer;
|
||||||
|
|
||||||
|
function startError(e) {
|
||||||
|
explainError(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.httpServer = function(defaultApp) {
|
||||||
|
if (_httpServer) {
|
||||||
|
return _httpServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpServer = http.createServer(HttpMiddleware.create(opts.greenlock, defaultApp));
|
||||||
|
_httpServer.once("error", startError);
|
||||||
|
|
||||||
|
return _httpServer;
|
||||||
|
};
|
||||||
|
|
||||||
|
servers.httpsServer = function(secureOpts, defaultApp) {
|
||||||
|
if (_httpsServer) {
|
||||||
|
return _httpsServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secureOpts) {
|
||||||
|
secureOpts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpsServer = createSecureServer(
|
||||||
|
wrapDefaultSniCallback(opts, greenlock, secureOpts),
|
||||||
|
HttpsMiddleware.create(greenlock, defaultApp)
|
||||||
|
);
|
||||||
|
_httpsServer.once("error", startError);
|
||||||
|
|
||||||
|
return _httpsServer;
|
||||||
|
};
|
||||||
|
|
||||||
|
servers.serveApp = function(app) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
if ("function" !== typeof app) {
|
||||||
|
reject(new Error("glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var plainServer = servers.httpServer(require("redirect-https")());
|
||||||
|
var plainAddr = "0.0.0.0";
|
||||||
|
var plainPort = 80;
|
||||||
|
plainServer.listen(plainPort, plainAddr, function() {
|
||||||
|
console.info("Listening on", plainAddr + ":" + plainPort, "for ACME challenges, and redirecting to HTTPS");
|
||||||
|
|
||||||
|
// TODO fetch greenlock.servername
|
||||||
|
var secureServer = servers.httpsServer(app);
|
||||||
|
var secureAddr = "0.0.0.0";
|
||||||
|
var securePort = 443;
|
||||||
|
secureServer.listen(securePort, secureAddr, function() {
|
||||||
|
console.info("Listening on", secureAddr + ":" + securePort, "for secure traffic");
|
||||||
|
|
||||||
|
plainServer.removeListener("error", startError);
|
||||||
|
secureServer.removeListener("error", startError);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
function explainError(e) {
|
||||||
|
console.error();
|
||||||
|
console.error("Error: " + e.message);
|
||||||
|
if ("EACCES" === e.errno) {
|
||||||
|
console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'.");
|
||||||
|
console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
|
||||||
|
} else if ("EADDRINUSE" === e.errno) {
|
||||||
|
console.error("'" + e.address + ":" + e.port + "' is already being used by some other program.");
|
||||||
|
console.error("You probably need to stop that program or restart your computer.");
|
||||||
|
} else {
|
||||||
|
console.error(e.code + ": '" + e.address + ":" + e.port + "'");
|
||||||
|
}
|
||||||
|
console.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapDefaultSniCallback(opts, greenlock, secureOpts) {
|
||||||
|
// I'm not sure yet if the original SNICallback
|
||||||
|
// should be called before or after, so I'm just
|
||||||
|
// going to delay making that choice until I have the use case
|
||||||
|
/*
|
||||||
|
if (!secureOpts.SNICallback) {
|
||||||
|
secureOpts.SNICallback = function(servername, cb) {
|
||||||
|
cb(null, null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (secureOpts.SNICallback) {
|
||||||
|
console.warn();
|
||||||
|
console.warn("[warning] Ignoring the given tlsOptions.SNICallback function.");
|
||||||
|
console.warn();
|
||||||
|
console.warn(" We're very open to implementing support for this,");
|
||||||
|
console.warn(" we just don't understand the use case yet.");
|
||||||
|
console.warn(" Please open an issue to discuss. We'd love to help.");
|
||||||
|
console.warn();
|
||||||
|
}
|
||||||
|
|
||||||
|
secureOpts.SNICallback = sni.create(opts, greenlock, secureOpts);
|
||||||
|
return secureOpts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSecureServer(secureOpts, fn) {
|
||||||
|
var major = process.versions.node.split(".")[0];
|
||||||
|
|
||||||
|
// TODO can we trust earlier versions as well?
|
||||||
|
if (major >= 12) {
|
||||||
|
return require("http2").createSecureServer(secureOpts, fn);
|
||||||
|
} else {
|
||||||
|
return require("https").createServer(secureOpts, fn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
require("./main.js");
|
||||||
|
|
||||||
|
var Single = module.exports;
|
||||||
|
var Servers = require("./servers.js");
|
||||||
|
var Greenlock = require("@root/greenlock");
|
||||||
|
|
||||||
|
Single.create = function(opts) {
|
||||||
|
var greenlock = Greenlock.create(opts);
|
||||||
|
var servers = Servers.create(greenlock, opts);
|
||||||
|
//var master = Master.create(opts);
|
||||||
|
|
||||||
|
var single = {
|
||||||
|
worker: function(fn) {
|
||||||
|
fn(servers);
|
||||||
|
return single;
|
||||||
|
},
|
||||||
|
master: function(/*fn*/) {
|
||||||
|
// ignore
|
||||||
|
//fn(master);
|
||||||
|
return single;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return single;
|
||||||
|
};
|
|
@ -0,0 +1,184 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var sni = module.exports;
|
||||||
|
var tls = require("tls");
|
||||||
|
var servernameRe = /^[a-z0-9\.\-]+$/i;
|
||||||
|
|
||||||
|
// a nice, round, irrational number - about every 6¼ hours
|
||||||
|
var refreshOffset = Math.round(Math.PI * 2 * (60 * 60 * 1000));
|
||||||
|
// and another, about 15 minutes
|
||||||
|
var refreshStagger = Math.round(Math.PI * 5 * (60 * 1000));
|
||||||
|
// and another, about 30 seconds
|
||||||
|
var smallStagger = Math.round(Math.PI * (30 * 1000));
|
||||||
|
|
||||||
|
//secureOpts.SNICallback = sni.create(opts, greenlock, secureOpts);
|
||||||
|
sni.create = function(opts, greenlock, secureOpts) {
|
||||||
|
var _cache = {};
|
||||||
|
var defaultServername = opts.servername || greenlock.servername;
|
||||||
|
|
||||||
|
if (secureOpts.cert) {
|
||||||
|
// Note: it's fine if greenlock.servername is undefined,
|
||||||
|
// but if the caller wants this to auto-renew, they should define it
|
||||||
|
_cache[defaultServername] = {
|
||||||
|
refreshAt: 0,
|
||||||
|
secureContext: tls.createSecureContext(secureOpts)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSecureContext;
|
||||||
|
|
||||||
|
function notify(ev, args) {
|
||||||
|
try {
|
||||||
|
// TODO _notify() or notify()?
|
||||||
|
(opts.notify || greenlock.notify || greenlock._notify)(ev, args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(ev, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSecureContext(servername, cb) {
|
||||||
|
if ("string" !== typeof servername) {
|
||||||
|
// this will never happen... right? but stranger things have...
|
||||||
|
console.error("[sanity fail] non-string servername:", servername);
|
||||||
|
cb(new Error("invalid servername"), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var secureContext = getCachedContext(servername);
|
||||||
|
if (secureContext) {
|
||||||
|
cb(null, secureContext);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFreshContext(servername)
|
||||||
|
.then(function(secureContext) {
|
||||||
|
if (secureContext) {
|
||||||
|
cb(null, secureContext);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Note: this does not replace tlsSocket.setSecureContext()
|
||||||
|
// as it only works when SNI has been sent
|
||||||
|
cb(null, getDefaultContext());
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (!err.context) {
|
||||||
|
err.context = "sni_callback";
|
||||||
|
}
|
||||||
|
notify("error", err);
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedMeta(servername) {
|
||||||
|
var meta = _cache[servername];
|
||||||
|
if (!meta) {
|
||||||
|
if (!_cache[wildname(servername)]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedContext(servername) {
|
||||||
|
var meta = getCachedMeta(servername);
|
||||||
|
if (!meta) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta.refreshAt || Date.now() >= meta.refreshAt) {
|
||||||
|
getFreshContext(servername).catch(function(e) {
|
||||||
|
if (!e.context) {
|
||||||
|
e.context = "sni_background_refresh";
|
||||||
|
}
|
||||||
|
notify("error", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta.secureContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFreshContext(servername) {
|
||||||
|
var meta = getCachedMeta(servername);
|
||||||
|
if (!meta && !validServername(servername)) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
// prevent stampedes
|
||||||
|
meta.refreshAt = Date.now() + randomRefreshOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO greenlock.get({ servername: servername })
|
||||||
|
// TODO don't get unknown certs at all, rely on auto-updates from greenlock
|
||||||
|
// Note: greenlock.renew() will return an existing fresh cert or issue a new one
|
||||||
|
return greenlock.renew({ servername: servername }).then(function(matches) {
|
||||||
|
var meta = getCachedMeta(servername);
|
||||||
|
if (!meta) {
|
||||||
|
meta = _cache[servername] = { secureContext: {} };
|
||||||
|
}
|
||||||
|
// prevent from being punked by bot trolls
|
||||||
|
meta.refreshAt = Date.now() + smallStagger;
|
||||||
|
|
||||||
|
// nothing to do
|
||||||
|
if (!matches.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only care about the first one
|
||||||
|
var pems = matches[0].pems;
|
||||||
|
var site = matches[0].site;
|
||||||
|
var match = matches[0];
|
||||||
|
if (!pems || !pems.cert) {
|
||||||
|
// nothing to do
|
||||||
|
// (and the error should have been reported already)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
refreshAt: Date.now() + randomRefreshOffset(),
|
||||||
|
secureContext: tls.createSecureContext({
|
||||||
|
// TODO support passphrase-protected privkeys
|
||||||
|
key: pems.privkey,
|
||||||
|
cert: pems.cert + "\n" + pems.chain + "\n"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// copy this same object into every place
|
||||||
|
[match.altnames || site.altnames || [match.subject || site.subject]].forEach(function(altname) {
|
||||||
|
_cache[altname] = meta;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultContext() {
|
||||||
|
return getCachedContext(defaultServername);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// whenever we need to know when to refresh next
|
||||||
|
function randomRefreshOffset() {
|
||||||
|
var stagger = Math.round(refreshStagger / 2) - Math.round(Math.random() * refreshStagger);
|
||||||
|
return refreshOffset + stagger;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validServername(servername) {
|
||||||
|
// format and (lightly) sanitize sni so that users can be naive
|
||||||
|
// and not have to worry about SQL injection or fs discovery
|
||||||
|
|
||||||
|
servername = (servername || "").toLowerCase();
|
||||||
|
// hostname labels allow a-z, 0-9, -, and are separated by dots
|
||||||
|
// _ is sometimes allowed, but not as a "hostname", and not by Let's Encrypt ACME
|
||||||
|
// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
|
||||||
|
return servernameRe.test(servername) && -1 === servername.indexOf("..");
|
||||||
|
}
|
||||||
|
|
||||||
|
function wildname(servername) {
|
||||||
|
return (
|
||||||
|
"*." +
|
||||||
|
servername
|
||||||
|
.split(".")
|
||||||
|
.slice(1)
|
||||||
|
.join(".")
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Worker = module.exports;
|
||||||
|
|
||||||
|
Worker.create = function(opts) {
|
||||||
|
var greenlock = {
|
||||||
|
// rename presentChallenge?
|
||||||
|
getAcmeHttp01ChallengeResponse: presentChallenge,
|
||||||
|
notify: notifyMaster,
|
||||||
|
get: greenlockRenew
|
||||||
|
};
|
||||||
|
|
||||||
|
var worker = {
|
||||||
|
worker: function(fn) {
|
||||||
|
var servers = require("./servers.js").create(greenlock, opts);
|
||||||
|
fn(servers);
|
||||||
|
return worker;
|
||||||
|
},
|
||||||
|
master: function() {
|
||||||
|
// ignore
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return worker;
|
||||||
|
};
|
||||||
|
|
||||||
|
function greenlockRenew(args) {
|
||||||
|
return request("renew", {
|
||||||
|
servername: args.servername
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function presentChallenge(args) {
|
||||||
|
return request("challenge-response", {
|
||||||
|
servername: args.servername,
|
||||||
|
token: args.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(typename, msg) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var rnd = Math.random()
|
||||||
|
.slice(2)
|
||||||
|
.toString(16);
|
||||||
|
var id = "greenlock:" + rnd;
|
||||||
|
var timeout;
|
||||||
|
|
||||||
|
function getResponse(msg) {
|
||||||
|
if (msg.id !== id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("message", getResponse);
|
||||||
|
msg.id = msg;
|
||||||
|
msg.type = typename;
|
||||||
|
process.send(msg);
|
||||||
|
|
||||||
|
timeout = setTimeout(function() {
|
||||||
|
process.removeListener("message", getResponse);
|
||||||
|
reject(new Error("process message timeout"));
|
||||||
|
}, 30 * 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyMaster(ev, args) {
|
||||||
|
process.on("message", {
|
||||||
|
type: "notification",
|
||||||
|
event: ev,
|
||||||
|
parameters: args
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue