From 3e97142bf47b6307d63b5167cfb5cd035e9954c5 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 1 Nov 2019 14:31:41 -0600 Subject: [PATCH] for unscoped npm package --- config.js | 20 -- demo.js | 35 ---- .../systemd/system/greenlock-express.service | 53 ----- examples/cluster/server.js | 39 ---- examples/express/my-express-app.js | 17 -- examples/express/server.js | 27 --- examples/http-proxy/server.js | 44 ---- examples/http/server.js | 42 ---- examples/http2/server.js | 48 ----- examples/https/server.js | 49 ----- examples/quickstart/README.md | 22 -- examples/quickstart/server.js | 32 --- examples/socket.io/server.js | 49 ----- examples/spdy/server.js | 3 - examples/websockets/server.js | 42 ---- greenlock-express.js | 45 +--- greenlock.js | 77 ------- http-middleware.js | 106 ---------- https-middleware.js | 139 ------------- install.sh | 14 -- lib/compat.js | 37 ---- main.js | 36 ---- master.js | 160 --------------- package-lock.json | 14 +- publish.sh | 18 ++ scripts/postinstall | 77 ------- servers.js | 157 -------------- single.js | 25 --- sni.js | 194 ------------------ test/greenlock.js | 85 -------- worker.js | 62 ------ 31 files changed, 27 insertions(+), 1741 deletions(-) delete mode 100644 config.js delete mode 100644 demo.js delete mode 100644 dist/etc/systemd/system/greenlock-express.service delete mode 100644 examples/cluster/server.js delete mode 100644 examples/express/my-express-app.js delete mode 100644 examples/express/server.js delete mode 100644 examples/http-proxy/server.js delete mode 100644 examples/http/server.js delete mode 100644 examples/http2/server.js delete mode 100644 examples/https/server.js delete mode 100644 examples/quickstart/README.md delete mode 100644 examples/quickstart/server.js delete mode 100644 examples/socket.io/server.js delete mode 100644 examples/spdy/server.js delete mode 100644 examples/websockets/server.js delete mode 100644 greenlock.js delete mode 100644 http-middleware.js delete mode 100644 https-middleware.js delete mode 100644 install.sh delete mode 100644 lib/compat.js delete mode 100644 main.js delete mode 100644 master.js create mode 100644 publish.sh delete mode 100755 scripts/postinstall delete mode 100644 servers.js delete mode 100644 single.js delete mode 100644 sni.js delete mode 100644 test/greenlock.js delete mode 100644 worker.js diff --git a/config.js b/config.js deleted file mode 100644 index 005abd0..0000000 --- a/config.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict"; - -var path = require("path"); -module.exports = { - email: "jon.doe@example.com", - configDir: path.join(__dirname, "acme"), - srv: "/srv/www/", - api: "/srv/api/", - proxy: { - "example.com": "http://localhost:4080", - "*.example.com": "http://localhost:4080" - }, - - // DNS-01 challenges only - challenges: { - "*.example.com": require("acme-dns-01-YOUR_DNS_HOST").create({ - token: "xxxx" - }) - } -}; diff --git a/demo.js b/demo.js deleted file mode 100644 index 9e33bed..0000000 --- a/demo.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; - -require("./") - .init(initialize) - .serve(worker) - .master(function() { - console.log("Hello from master"); - }); - -function initialize() { - var pkg = require("./package.json"); - var config = { - package: { - name: "Greenlock_Express_Demo", - version: pkg.version, - author: pkg.author - }, - staging: true, - cluster: true, - - notify: function(ev, params) { - console.info(ev, params); - } - }; - return config; -} - -function worker(glx) { - console.info(); - console.info("Hello from worker #" + glx.id()); - - glx.serveApp(function(req, res) { - res.end("Hello, Encrypted World!"); - }); -} diff --git a/dist/etc/systemd/system/greenlock-express.service b/dist/etc/systemd/system/greenlock-express.service deleted file mode 100644 index 7cd34d2..0000000 --- a/dist/etc/systemd/system/greenlock-express.service +++ /dev/null @@ -1,53 +0,0 @@ -# sudo systemctl daemon-reload -# sudo systemctl restart greenlock-express -# sudo journalctl -xefu greenlock-express -[Unit] -Description=Greenlock Static Server -Documentation=https://git.coolaj86.com/coolaj86/greenlock-express.js/ -After=network.target -Wants=network.target systemd-networkd-wait-online.service - -[Service] -# Restart on crash (bad signal), 'clean' failure (error exit code), everything -# Allow up to 3 restarts within 10 seconds -# (it's unlikely that a user or properly-running script will do this) -Restart=always -StartLimitInterval=10 -StartLimitBurst=3 - -# User and group the process will run as -# (git is the de facto standard on most systems) -User=ubuntu -Group=ubuntu - -WorkingDirectory=/srv/www -# custom directory cannot be set and will be the place where gitea exists, not the working directory -ExecStart=/opt/node/bin/node /opt/greenlock-express.js/server.js /opt/greenlock-express.js/config.js - -# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. -# greenlock is not expected to use more than this. -LimitNOFILE=1048576 -LimitNPROC=64 - -# Use private /tmp and /var/tmp, which are discarded after gitea stops. -PrivateTmp=true -# Use a minimal /dev -PrivateDevices=true -# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. -ProtectHome=true -# Make /usr, /boot, /etc and possibly some more folders read-only. -ProtectSystem=full -# ... except /opt/greenlock-express.js/acme because we want a place for the database -# and /opt/greenlock-express.js/var because we want a place where logs can go. -# This merely retains r/w access rights, it does not add any new. -# Must still be writable on the host! -ReadWriteDirectories=/srv/www /opt/greenlock-express.js - -# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories -; ReadWritePaths=/opt/gitea /var/log/gitea - -# The following additional security directives only work with systemd v229 or later. -# They further retrict privileges that can be gained by gitea. -# Note that you may have to add capabilities required by any plugins in use. -CapabilityBoundingSet=CAP_NET_BIND_SERVICE -AmbientCapabilities=CAP_NET_BIND_SERVICE diff --git a/examples/cluster/server.js b/examples/cluster/server.js deleted file mode 100644 index 575b602..0000000 --- a/examples/cluster/server.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict"; - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "websocket-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - - // When you're ready to go full cloud scale, you just change this to true: - // Note: in cluster you CANNOT use in-memory state (see below) - cluster: true, - - // This will default to the number of workers being equal to - // n-1 cpus, with a minimum of 2 - workers: 4 - }; - }) - .serve(httpsWorker); - -function httpsWorker(glx) { - // WRONG - // This won't work like you - // think because EACH worker - // has ITS OWN `count`. - var count = 0; - - var app = function(req, res) { - res.end("Hello... how many times now? Oh, " + count + " times"); - count += 1; - }; - - // Serves on 80 and 443... for each worker - // Get's SSL certificates magically! - glx.serveApp(app); -} diff --git a/examples/express/my-express-app.js b/examples/express/my-express-app.js deleted file mode 100644 index fada117..0000000 --- a/examples/express/my-express-app.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; - -var express = require("express"); -var app = express(); - -app.use("/", function(req, res) { - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end("Hello, World!\n\nπŸ’š πŸ”’.js"); -}); - -// DO NOT DO app.listen() unless we're testing this directly -if (require.main === module) { - app.listen(3000); -} - -// Instead do export the app: -module.exports = app; diff --git a/examples/express/server.js b/examples/express/server.js deleted file mode 100644 index 97f5752..0000000 --- a/examples/express/server.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; - -function httpsWorker(glx) { - var app = require("./my-express-app.js"); - - app.get("/hello", function(req, res) { - res.end("Hello, Encrypted World!"); - }); - - // Serves on 80 and 443 - // Get's SSL certificates magically! - glx.serveApp(app); -} - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "http2-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/http-proxy/server.js b/examples/http-proxy/server.js deleted file mode 100644 index 3d4cc77..0000000 --- a/examples/http-proxy/server.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; - -function httpsWorker(glx) { - // we need the raw https server - var server = glx.httpsServer(); - var proxy = require("http-proxy").createProxyServer({ xfwd: true }); - - // catches error events during proxying - proxy.on("error", function(err, req, res) { - console.error(err); - res.statusCode = 500; - res.end(); - return; - }); - - // We'll proxy websockets too - server.on("upgrade", function(req, socket, head) { - proxy.ws(req, socket, head, { - ws: true, - target: "ws://localhost:3000" - }); - }); - - // servers a node app that proxies requests to a localhost - glx.serveApp(function(req, res) { - proxy.web(req, res, { - target: "http://localhost:3000" - }); - }); -} - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "http-proxy-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/http/server.js b/examples/http/server.js deleted file mode 100644 index 28b3c47..0000000 --- a/examples/http/server.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -var pkg = require("../../package.json"); - -// The WRONG way: -//var http = require('http'); -//var httpServer = https.createSecureServer(redirectToHttps); -// -// Why is that wrong? -// Greenlock needs to change some low-level http and https options. -// Use glx.httpServer(redirectToHttps) instead. - -function httpsWorker(glx) { - // - // HTTP can only be used for ACME HTTP-01 Challenges - // (and it is not required for DNS-01 challenges) - // - - // Get the raw http server: - var httpServer = glx.httpServer(function(req, res) { - res.statusCode = 301; - res.setHeader("Location", "https://" + req.headers.host + req.path); - res.end("Insecure connections are not allowed. Redirecting..."); - }); - - httpServer.listen(80, "0.0.0.0", function() { - console.info("Listening on ", httpServer.address()); - }); -} - -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "plain-http-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/http2/server.js b/examples/http2/server.js deleted file mode 100644 index 6518b4d..0000000 --- a/examples/http2/server.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -var pkg = require("../../package.json"); - -// The WRONG way: -//var http2 = require('http2'); -//var http2Server = https.createSecureServer(tlsOptions, app); -// -// Why is that wrong? -// Greenlock needs to change some low-level http and https options. -// Use glx.httpsServer(tlsOptions, app) instead. - -function httpsWorker(glx) { - // - // HTTP2 is the default httpsServer for node v12+ - // (HTTPS/1.1 is used for node <= v11) - // - - // Get the raw http2 server: - var http2Server = glx.httpsServer(function(req, res) { - res.end("Hello, Encrypted World!"); - }); - - http2Server.listen(443, "0.0.0.0", function() { - console.info("Listening on ", http2Server.address()); - }); - - // Note: - // You must ALSO listen on port 80 for ACME HTTP-01 Challenges - // (the ACME and http->https middleware are loaded by glx.httpServer) - var httpServer = glx.httpServer(); - httpServer.listen(80, "0.0.0.0", function() { - console.info("Listening on ", httpServer.address()); - }); -} - -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "http2-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/https/server.js b/examples/https/server.js deleted file mode 100644 index d4a2c58..0000000 --- a/examples/https/server.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; - -var pkg = require("../../package.json"); - -// The WRONG way: -//var https = require('https'); -//var httpsServer = https.createServer(tlsOptions, app); -// -// Why is that wrong? -// Greenlock needs to change some low-level http and https options. -// Use glx.httpsServer(tlsOptions, app) instead. - -function httpsWorker(glx) { - // - // HTTPS/1.1 is only used for node v11 or lower - // (HTTP2 is used for node v12+) - // - // Why not just require('https')? - - // Get the raw https server: - var httpsServer = glx.httpsServer(null, function(req, res) { - res.end("Hello, Encrypted World!"); - }); - - httpsServer.listen(443, "0.0.0.0", function() { - console.info("Listening on ", httpsServer.address()); - }); - - // Note: - // You must ALSO listen on port 80 for ACME HTTP-01 Challenges - // (the ACME and http->https middleware are loaded by glx.httpServer) - var httpServer = glx.httpServer(); - httpServer.listen(80, "0.0.0.0", function() { - console.info("Listening on ", httpServer.address()); - }); -} - -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "https1-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/quickstart/README.md b/examples/quickstart/README.md deleted file mode 100644 index 0ebffad..0000000 --- a/examples/quickstart/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Quick Start for Let's Encrypt with Node.js - -```js -npm install --save greenlock-express -``` - -Manage via API or the config file: - -`~/.config/greenlock/manage.json`: (default filesystem config) - -```json -{ - "subscriberEmail": "letsencrypt-test@therootcompany.com", - "agreeToTerms": true, - "sites": { - "example.com": { - "subject": "example.com", - "altnames": ["example.com", "www.example.com"] - } - } -} -``` diff --git a/examples/quickstart/server.js b/examples/quickstart/server.js deleted file mode 100644 index 82192f2..0000000 --- a/examples/quickstart/server.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -function httpsWorker(glx) { - // This can be a node http app (shown), - // an Express app, or Hapi, Koa, Rill, etc - var app = function(req, res) { - res.end("Hello, Encrypted World!"); - }; - - // Serves on 80 and 443 - // Get's SSL certificates magically! - glx.serveApp(app); -} - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - // Package name+version is used for ACME client user agent - package: { name: "websocket-example", version: pkg.version }, - - // Maintainer email is the contact for critical bug and security notices - maintainerEmail: "jon@example.com", - - // Change to true when you're ready to make your app cloud-scale - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/socket.io/server.js b/examples/socket.io/server.js deleted file mode 100644 index fb8be4d..0000000 --- a/examples/socket.io/server.js +++ /dev/null @@ -1,49 +0,0 @@ -// First and foremost: -// I'm not a fan of `socket.io` because it's huge and complex. -// I much prefer `ws` because it's very simple and easy. -// That said, it's popular....... -"use strict"; - -// Note: You DO NOT NEED socket.io -// You can just use WebSockets -// (see the websocket example) - -function httpsWorker(glx) { - var socketio = require("socket.io"); - var io; - - // we need the raw https server - var server = glx.httpsServer(); - - io = socketio(server); - - // Then you do your socket.io stuff - io.on("connection", function(socket) { - console.log("a user connected"); - socket.emit("Welcome"); - - socket.on("chat message", function(msg) { - socket.broadcast.emit("chat message", msg); - }); - }); - - // servers a node app that proxies requests to a localhost - glx.serveApp(function(req, res) { - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end("Hello, World!\n\nπŸ’š πŸ”’.js"); - }); -} - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "socket-io-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/examples/spdy/server.js b/examples/spdy/server.js deleted file mode 100644 index 0e9c26d..0000000 --- a/examples/spdy/server.js +++ /dev/null @@ -1,3 +0,0 @@ -// SPDY is dead. It was replaced by HTTP2, which is a native node module -// -// Greenlock uses HTTP2 as the default https server in node v12+ diff --git a/examples/websockets/server.js b/examples/websockets/server.js deleted file mode 100644 index c5694ad..0000000 --- a/examples/websockets/server.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -function httpsWorker(glx) { - // we need the raw https server - var server = glx.httpsServer(); - var WebSocket = require("ws"); - var ws = new WebSocket.Server({ server: server }); - ws.on("connection", function(ws, req) { - // inspect req.headers.authorization (or cookies) for session info - ws.send( - "[Secure Echo Server] Hello!\nAuth: '" + - (req.headers.authorization || "none") + - "'\n" + - "Cookie: '" + - (req.headers.cookie || "none") + - "'\n" - ); - ws.on("message", function(data) { - ws.send(data); - }); - }); - - // servers a node app that proxies requests to a localhost - glx.serveApp(function(req, res) { - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end("Hello, World!\n\nπŸ’š πŸ”’.js"); - }); -} - -var pkg = require("../../package.json"); -//require("greenlock-express") -require("../../") - .init(function getConfig() { - // Greenlock Config - - return { - package: { name: "websocket-example", version: pkg.version }, - maintainerEmail: "jon@example.com", - cluster: false - }; - }) - .serve(httpsWorker); diff --git a/greenlock-express.js b/greenlock-express.js index 2000c36..287fdd8 100644 --- a/greenlock-express.js +++ b/greenlock-express.js @@ -1,44 +1,3 @@ -"use strict"; +'use strict'; -require("./lib/compat"); -var cluster = require("cluster"); - -// Greenlock Express -var GLE = module.exports; - -// Node's cluster is awesome, because it encourages writing scalable services. -// -// The point of this provide an API that is consistent between single-process -// and multi-process services so that beginners can more easily take advantage -// of what cluster has to offer. -// -// This API provides just enough abstraction to make it easy, but leaves just -// enough hoopla so that there's not a large gap in understanding what happens -// under the hood. That's the hope, anyway. - -GLE.init = function(fn) { - if (cluster.isWorker) { - // ignore the init function and launch the worker - return require("./worker.js").create(); - } - - var opts = fn(); - if (!opts || "object" !== typeof opts) { - throw new Error( - "the `Greenlock.init(fn)` function should return an object `{ maintainerEmail, packageAgent, notify }`" - ); - } - - // just for ironic humor - ["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) { - if (opts[k]) { - opts.cluster = true; - } - }); - - if (opts.cluster) { - return require("./master.js").create(opts); - } - - return require("./single.js").create(opts); -}; +module.exports = require('@root/greenlock-express'); diff --git a/greenlock.js b/greenlock.js deleted file mode 100644 index 6770cbb..0000000 --- a/greenlock.js +++ /dev/null @@ -1,77 +0,0 @@ -"use strict"; - -module.exports.create = function(opts) { - opts = parsePackage(opts); - opts.packageAgent = addGreenlockAgent(opts); - - var Greenlock = require("@root/greenlock"); - var greenlock = Greenlock.create(opts); - - // re-export as top-level function to simplify rpc with workers - greenlock.getAcmeHttp01ChallengeResponse = function(opts) { - return greenlock.challenges.get(opts); - }; - - return greenlock; -}; - -function addGreenlockAgent(opts) { - // Add greenlock as part of Agent, unless this is greenlock - var packageAgent = opts.packageAgent || ""; - if (!/greenlock(-express|-pro)?/i.test(packageAgent)) { - var pkg = require("./package.json"); - packageAgent += " Greenlock_Express/" + pkg.version; - } - - return packageAgent.trim(); -} - -// ex: "John Doe (https://john.doe)" -// ex: "John Doe " -// ex: "" -// ex: "john@example.com" -var looseEmailRe = /(^|[\s<])([^'" <>:;`]+@[^'" <>:;`]+\.[^'" <>:;`]+)/; -function parsePackage(opts) { - // 'package' is sometimes a reserved word - var pkg = opts.package || opts.pkg; - if (!pkg) { - opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); - return opts; - } - - if (!opts.packageAgent) { - var err = "missing `package.THING`, which is used for the ACME client user agent string"; - if (!pkg.name) { - throw new Error(err.replace("THING", "name")); - } - if (!pkg.version) { - throw new Error(err.replace("THING", "version")); - } - opts.packageAgent = pkg.name + "/" + pkg.version; - } - - if (!opts.maintainerEmail) { - try { - opts.maintainerEmail = pkg.author.email || pkg.author.match(looseEmailRe)[2]; - } catch (e) {} - } - if (!opts.maintainerEmail) { - throw new Error("missing or malformed `package.author`, which is used as the contact for support notices"); - } - opts.package = undefined; - opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); - - return opts; -} - -function parseMaintainer(maintainerEmail) { - try { - maintainerEmail = maintainerEmail.match(looseEmailRe)[2]; - } catch (e) { - maintainerEmail = null; - } - if (!maintainerEmail) { - throw new Error("missing or malformed `maintainerEmail`, which is used as the contact for support notices"); - } - return maintainerEmail; -} diff --git a/http-middleware.js b/http-middleware.js deleted file mode 100644 index 2608f67..0000000 --- a/http-middleware.js +++ /dev/null @@ -1,106 +0,0 @@ -"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 }) - .catch(function(err) { - respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); - return { __done: true }; - }) - .then(function(result) { - if (result && result.__done) { - return; - } - return respondWithGrace(res, result, hostname, token); - }); - }; -}; - -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 && 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) || ""; -}; diff --git a/https-middleware.js b/https-middleware.js deleted file mode 100644 index 80be005..0000000 --- a/https-middleware.js +++ /dev/null @@ -1,139 +0,0 @@ -"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( - "

Domain Fronting Error

" + - "

This connection was secured using TLS/SSL for '" + - (req.socket.servername || "").toLowerCase() + - "'

" + - "

The HTTP request specified 'Host: " + - safehost + - "', which is (obviously) different.

" + - "

Because this looks like a domain fronting attack, the connection has been terminated.

" - ); - 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? - //console.log(safeHost, cert.subject.CN, cert.subjectaltname); - var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost; - if (isSubject) { - return true; - } - - var dnsnames = (cert.subjectaltname || "").split(/,\s+/); - var inSanList = dnsnames.some(function(name) { - // always prefixed with "DNS:" - return safeHost === name.slice(4).toLowerCase(); - }); - - if (inSanList) { - return true; - } - } 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; -}; diff --git a/install.sh b/install.sh deleted file mode 100644 index e6a50c3..0000000 --- a/install.sh +++ /dev/null @@ -1,14 +0,0 @@ -# This is just an example (but it works) -export NODE_PATH=$NPM_CONFIG_PREFIX/lib/node_modules -export NPM_CONFIG_PREFIX=/opt/node -curl -fsSL https://bit.ly/node-installer | bash - -/opt/node/bin/node /opt/node/bin/npm config set scripts-prepend-node-path true -/opt/node/bin/node /opt/node/bin/npm ci -sudo setcap 'cap_net_bind_service=+ep' /opt/node/bin/node -/opt/node/bin/node /opt/node/bin/npm start - -sudo rsync -av dist/etc/systemd/system/greenlock-express.service /etc/systemd/system/ -sudo systemctl daemon-reload - -sudo systemctl restart greenlock-express diff --git a/lib/compat.js b/lib/compat.js deleted file mode 100644 index 3e5e7bc..0000000 --- a/lib/compat.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; - -function requireBluebird() { - try { - return require("bluebird"); - } catch (e) { - console.error(""); - console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); - console.error("EASY FIX: `npm install --save bluebird`"); - console.error(""); - throw e; - } -} - -if ("undefined" === typeof Promise) { - global.Promise = requireBluebird(); -} - -if ("function" !== typeof require("util").promisify) { - require("util").promisify = requireBluebird().promisify; -} - -if (!console.debug) { - console.debug = console.log; -} - -var fs = require("fs"); -var fsAsync = {}; -Object.keys(fs).forEach(function(key) { - var fn = fs[key]; - if ("function" !== typeof fn || !/[a-z]/.test(key[0])) { - return; - } - fsAsync[key] = require("util").promisify(fn); -}); - -exports.fsAsync = fsAsync; diff --git a/main.js b/main.js deleted file mode 100644 index 661b672..0000000 --- a/main.js +++ /dev/null @@ -1,36 +0,0 @@ -"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(); -} diff --git a/master.js b/master.js deleted file mode 100644 index 261f124..0000000 --- a/master.js +++ /dev/null @@ -1,160 +0,0 @@ -"use strict"; - -require("./main.js"); - -var Master = module.exports; - -var cluster = require("cluster"); -var os = require("os"); -var msgPrefix = "greenlock:"; - -Master.create = function(opts) { - var resolveCb; - var _readyCb; - var _kicked = false; - - var greenlock = require("./greenlock.js").create(opts); - - var ready = new Promise(function(resolve) { - resolveCb = resolve; - }).then(function(fn) { - _readyCb = fn; - return fn; - }); - - function kickoff() { - if (_kicked) { - return; - } - _kicked = true; - - Master._spawnWorkers(opts, greenlock); - - ready.then(function(fn) { - // not sure what this API should be yet - fn(); - }); - } - - var master = { - serve: function() { - kickoff(); - return master; - }, - master: function(fn) { - if (_readyCb) { - throw new Error("can't call master twice"); - } - kickoff(); - resolveCb(fn); - return master; - } - }; - return master; -}; - -function range(n) { - n = parseInt(n, 10); - if (!n) { - return []; - } - return new Array(n).join(",").split(","); -} - -Master._spawnWorkers = function(opts, greenlock) { - var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length; - - // process rpc messages - // start when dead - var numWorkers = parseInt(opts.workers || opts.numWorkers, 10); - if (!numWorkers) { - if (numCpus <= 2) { - numWorkers = 2; - } else { - numWorkers = numCpus - 1; - } - } - - cluster.once("exit", function() { - setTimeout(function() { - process.exit(3); - }, 100); - }); - - var workers = range(numWorkers); - function next() { - if (!workers.length) { - return; - } - workers.pop(); - - // for a nice aesthetic - setTimeout(function() { - Master._spawnWorker(opts, greenlock); - next(); - }, 250); - } - - next(); -}; - -Master._spawnWorker = function(opts, greenlock) { - var w = cluster.fork(); - // automatically added to master's `cluster.workers` - w.once("exit", function(code, signal) { - // TODO handle failures - // Should test if the first starts successfully - // Should exit if failures happen too quickly - - // For now just kill all when any die - if (signal) { - console.error("worker was killed by signal:", signal); - } else if (code !== 0) { - console.error("worker exited with error code:", code); - } else { - console.error("worker unexpectedly quit without exit code or signal"); - } - process.exit(2); - - //addWorker(); - }); - - function handleMessage(msg) { - if (0 !== (msg._id || "").indexOf(msgPrefix)) { - return; - } - if ("string" !== typeof msg._funcname) { - // TODO developer error - return; - } - - function rpc() { - return greenlock[msg._funcname](msg._input) - .then(function(result) { - w.send({ - _id: msg._id, - _result: result - }); - }) - .catch(function(e) { - var error = new Error(e.message); - Object.getOwnPropertyNames(e).forEach(function(k) { - error[k] = e[k]; - }); - w.send({ - _id: msg._id, - _error: error - }); - }); - } - - try { - rpc(); - } catch (e) { - console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:"); - console.error(e); - } - } - - w.on("message", handleMessage); -}; diff --git a/package-lock.json b/package-lock.json index 2164bbe..7ba3ea7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@root/greenlock-express", - "version": "3.0.7", + "version": "3.0.10", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -40,9 +40,9 @@ "integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" }, "@root/greenlock": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.17.tgz", - "integrity": "sha512-1XKhcLFEx1WFdn1Bc2rkAE/SL1ZUJYYMZdbnehTrfhCr5Y+9U1gdkNZnR/jInhoUvcicF/PXuZkGVucU50RNUg==", + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.24.tgz", + "integrity": "sha512-uJgHIdWEzZ1QeFN+Ydc2vKs91RDlZQTUF2R2WcklayWivXvBnr7QiyLDVtI5VZuJN6y5RQeWXmDQub/On+8wbg==", "requires": { "@root/acme": "^3.0.8", "@root/csr": "^0.8.1", @@ -77,9 +77,9 @@ "integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" }, "@root/request": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", - "integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.2.tgz", + "integrity": "sha512-J8FM4+SJuc7WRC+Jz17m+VT2lgI7HtatHhxN1F2ck5aIKUAxJEaR4u/gLBsgT60mVHevKCjKN0O8115UtJjwLw==" }, "@root/x509": { "version": "0.7.2", diff --git a/publish.sh b/publish.sh new file mode 100644 index 0000000..ed9c976 --- /dev/null +++ b/publish.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +set -u + +git fetch --all +git checkout master +git pull origin master + +git checkout npm +git checkout master -- package.json +git checkout master -- README.md +sed -i '' -e 's|"name": ".root.greenlock"|"name": "greenlock"|' package.json +npm install --save @root/greenlock-express@latest +git add package* README.md || true +git commit -m "bump" || true +npm publish ./ +git reset --hard diff --git a/scripts/postinstall b/scripts/postinstall deleted file mode 100755 index 2ddc503..0000000 --- a/scripts/postinstall +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -// BG WH \u001b[47m -// BOLD \u001b[1m -// RED \u001b[31m -// GREEN \u001b[32m -// RESET \u001b[0m - -var grabbers = [ - [ - "", - "================================================================================", - "", - " πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯", - "πŸ”₯ πŸ”₯", - "πŸ”₯ Do you rely on Greenlock? πŸ”₯", - "πŸ”₯ πŸ”₯", - " πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯πŸ”₯" - ], - - [ - "", - "================================================================================", - "", - " πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’", - "πŸ’ πŸ’", - "πŸ’ Do you rely on Greenlock? πŸ’", - "πŸ’ πŸ’", - " πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’πŸ’" - ], - - [ - "", - "================================================================================", - "", - " πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡", - "πŸ‘‰ πŸ‘ˆ", - "πŸ‘‰ Do you rely on Greenlock? πŸ‘ˆ", - "πŸ‘‰ πŸ‘ˆ", - " πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘† " - ], - - [ - "", - "================================================================================", - "", - " πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ ", - "πŸ‘€ πŸ‘€", - "πŸ‘€ Do you rely on Greenlock? πŸ‘€", - "πŸ‘€ πŸ‘€", - " πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ πŸ‘€ ", - ] -]; - -setTimeout(function() { - grabbers[Math.floor(Math.random() * grabbers.length)].concat([ - "", - "Hey! Let's Encrypt will \u001b[31mSTOP WORKING\u001b[0m with Greenlock v2 at the end of October,", - "and \u001b[31mWITHOUT YOUR HELP\u001b[0m we won't get the next release out in time.", - "", - "If Greenlock has saved you time and money, and taken stress out of your life,", - "or you just love it, please reach out to return the favor today:", - "", - "\u001b[31mSAVE GREENLOCK:\u001b[0m", - "https://indiegogo.com/at/greenlock", - "", - "================================================================================", - "" - ]).forEach(function(line) { - console.info(line); - }); -}, 300); - -setTimeout(function() { - // give time to read -}, 1500); diff --git a/servers.js b/servers.js deleted file mode 100644 index 9209fcc..0000000 --- a/servers.js +++ /dev/null @@ -1,157 +0,0 @@ -"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"); -var cluster = require("cluster"); - -Servers.create = function(greenlock) { - 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(greenlock, defaultApp)); - _httpServer.once("error", startError); - - return _httpServer; - }; - - var _middlewareApp; - - servers.httpsServer = function(secureOpts, defaultApp) { - if (defaultApp) { - // TODO guard against being set twice? - _middlewareApp = defaultApp; - } - - if (_httpsServer) { - if (secureOpts && Object.keys(secureOpts).length) { - throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)"); - } - return _httpsServer; - } - - if (!secureOpts) { - secureOpts = {}; - } - - _httpsServer = createSecureServer( - wrapDefaultSniCallback(greenlock, secureOpts), - HttpsMiddleware.create(greenlock, function(req, res) { - if (!_middlewareApp) { - throw new Error("Set app with `glx.serveApp(app)` or `glx.httpsServer(tlsOptions, app)`"); - } - _middlewareApp(req, res); - }) - ); - _httpsServer.once("error", startError); - - return _httpsServer; - }; - - servers.id = function() { - return (cluster.isWorker && cluster.worker.id) || "0"; - }; - 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 id = cluster.isWorker && cluster.worker.id; - var idstr = (id && "#" + id + " ") || ""; - var plainServer = servers.httpServer(require("redirect-https")()); - var plainAddr = "0.0.0.0"; - var plainPort = 80; - plainServer.listen(plainPort, plainAddr, function() { - console.info( - idstr + "Listening on", - plainAddr + ":" + plainPort, - "for ACME challenges, and redirecting to HTTPS" - ); - - // TODO fetch greenlock.servername - _middlewareApp = app || _middlewareApp; - var secureServer = servers.httpsServer(null, app); - var secureAddr = "0.0.0.0"; - var securePort = 443; - secureServer.listen(securePort, secureAddr, function() { - console.info(idstr + "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(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(); - } - - // TODO greenlock.servername for workers - secureOpts.SNICallback = sni.create(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) { - secureOpts.allowHTTP1 = true; - return require("http2").createSecureServer(secureOpts, fn); - } else { - return require("https").createServer(secureOpts, fn); - } -} diff --git a/single.js b/single.js deleted file mode 100644 index 9d529c1..0000000 --- a/single.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -require("./main.js"); - -var Single = module.exports; -var Servers = require("./servers.js"); - -Single.create = function(opts) { - var greenlock = require("./greenlock.js").create(opts); - - var servers = Servers.create(greenlock); - - var single = { - serve: function(fn) { - fn(servers); - return single; - }, - master: function(/*fn*/) { - // ignore - //fn(master); - return single; - } - }; - return single; -}; diff --git a/sni.js b/sni.js deleted file mode 100644 index 069882c..0000000 --- a/sni.js +++ /dev/null @@ -1,194 +0,0 @@ -"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(greenlock, secureOpts); -sni.create = function(greenlock, secureOpts) { - var _cache = {}; - var defaultServername = 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()? - (greenlock.notify || greenlock._notify)(ev, args); - } catch (e) { - console.error(e); - console.error(ev, args); - } - } - - function getSecureContext(servername, cb) { - //console.log("debug sni", servername); - 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) { - //console.log("debug sni got cached context", servername, getCachedMeta(servername)); - cb(null, secureContext); - return; - } - - getFreshContext(servername) - .then(function(secureContext) { - if (secureContext) { - //console.log("debug sni got fresh context", servername, getCachedMeta(servername)); - cb(null, secureContext); - return; - } - // Note: this does not replace tlsSocket.setSecureContext() - // as it only works when SNI has been sent - //console.log("debug sni got default context", servername, getCachedMeta(servername)); - cb(null, getDefaultContext()); - }) - .catch(function(err) { - if (!err.context) { - err.context = "sni_callback"; - } - notify("error", err); - //console.log("debug sni error", servername, 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; - } - - // always renew in background - if (!meta.refreshAt || Date.now() >= meta.refreshAt) { - getFreshContext(servername).catch(function(e) { - if (!e.context) { - e.context = "sni_background_refresh"; - } - notify("error", e); - }); - } - - // under normal circumstances this would never be expired - // and, if it is expired, something is so wrong it's probably - // not worth wating for the renewal - it has probably failed - 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 don't get unknown certs at all, rely on auto-updates from greenlock - // Note: greenlock.get() will return an existing fresh cert or issue a new one - return greenlock.get({ servername: servername }).then(function(result) { - var meta = getCachedMeta(servername); - if (!meta) { - meta = _cache[servername] = { secureContext: { _valid: false } }; - } - // prevent from being punked by bot trolls - meta.refreshAt = Date.now() + smallStagger; - - // nothing to do - if (!result) { - return null; - } - - // we only care about the first one - var pems = result.pems; - var site = result.site; - 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" - }) - }; - meta.secureContext._valid = true; - - // copy this same object into every place - (result.altnames || site.altnames || [result.subject || site.subject]).forEach(function(altname) { - _cache[altname] = meta; - }); - - return meta.secureContext; - }); - } - - 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(".") - ); -} diff --git a/test/greenlock.js b/test/greenlock.js deleted file mode 100644 index b2908ee..0000000 --- a/test/greenlock.js +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env node -var Greenlock = require("../"); -var greenlock = Greenlock.create({ - version: "draft-11", - server: "https://acme-staging-v02.api.letsencrypt.org/directory", - agreeTos: true, - approvedDomains: ["example.com", "www.example.com"], - configDir: require("path").join(require("os").tmpdir(), "acme"), - - app: require("express")().use("/", function(req, res) { - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end("Hello, World!\n\nπŸ’š πŸ”’.js"); - }) -}); - -var server1 = greenlock.listen(5080, 5443); -server1.on("listening", function() { - console.log("### THREE 3333 - All is well server1", this.address()); - setTimeout(function() { - // so that the address() object doesn't disappear - server1.close(); - server1.unencrypted.close(); - }, 10); -}); -setTimeout(function() { - var server2 = greenlock.listen(6080, 6443, function() { - console.log("### FIVE 55555 - Started server 2!"); - setTimeout(function() { - server2.close(); - server2.unencrypted.close(); - server6.close(); - server6.unencrypted.close(); - server7.close(); - server7.unencrypted.close(); - setTimeout(function() { - // TODO greenlock needs a close event (and to listen to its server's close event) - process.exit(0); - }, 1000); - }, 1000); - }); - server2.on("listening", function() { - console.log("### FOUR 44444 - All is well server2", server2.address()); - }); -}, 1000); - -var server3 = greenlock.listen( - 22, - 22, - function() { - console.error("Error: expected to get an error when launching plain server on port 22"); - }, - function() { - console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); - } -); -server3.unencrypted.on("error", function() { - console.log("Success: caught expected (plain) error"); -}); -server3.on("error", function() { - console.log("Success: caught expected " + server3.type + " error"); - //server3.close(); -}); - -var server4 = greenlock.listen( - 7080, - 7443, - function() { - console.log("Success: server4: plain"); - server4.unencrypted.close(); - }, - function() { - console.log("Success: server4: " + server4.type); - server4.close(); - } -); - -var server5 = greenlock.listen(10080, 10443, function() { - console.log("Server 5 with one fn", this.address()); - server5.close(); - server5.unencrypted.close(); -}); - -var server6 = greenlock.listen("[::]:11080", "[::1]:11443"); - -var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock"); diff --git a/worker.js b/worker.js deleted file mode 100644 index 5f6a8fa..0000000 --- a/worker.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; - -var Worker = module.exports; -// *very* generous, but well below the http norm of 120 -var messageTimeout = 30 * 1000; -var msgPrefix = "greenlock:"; - -Worker.create = function() { - var greenlock = {}; - ["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) { - greenlock[k] = function(args) { - return rpc(k, args); - }; - }); - - var worker = { - serve: function(fn) { - var servers = require("./servers.js").create(greenlock); - fn(servers); - return worker; - }, - master: function() { - // ignore - return worker; - } - }; - return worker; -}; - -function rpc(funcname, msg) { - return new Promise(function(resolve, reject) { - var rnd = Math.random() - .toString() - .slice(2) - .toString(16); - var id = msgPrefix + rnd; - var timeout; - - function getResponse(msg) { - if (msg._id !== id) { - return; - } - process.removeListener("message", getResponse); - clearTimeout(timeout); - resolve(msg._result); - } - - // TODO keep a single listener than just responds - // via a collection of callbacks? or leave as is? - process.on("message", getResponse); - process.send({ - _id: id, - _funcname: funcname, - _input: msg - }); - - timeout = setTimeout(function() { - process.removeListener("message", getResponse); - reject(new Error("worker rpc request timeout")); - }, messageTimeout); - }); -}