for unscoped npm package

This commit is contained in:
AJ ONeal 2019-11-01 14:31:41 -06:00
parent ce5e31bbf7
commit 3e97142bf4
31 changed files with 27 additions and 1741 deletions

View File

@ -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"
})
}
};

35
demo.js
View File

@ -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!");
});
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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"]
}
}
}
```

View File

@ -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);

View File

@ -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);

View File

@ -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+

View File

@ -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);

View File

@ -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');

View File

@ -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 <john@example.com> (https://john.doe)"
// ex: "John Doe <john@example.com>"
// ex: "<john@example.com>"
// 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;
}

View File

@ -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) || "";
};

View File

@ -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(
"<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?
//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;
};

View File

@ -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

View File

@ -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;

36
main.js
View File

@ -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();
}

160
master.js
View File

@ -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);
};

14
package-lock.json generated
View File

@ -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",

18
publish.sh Normal file
View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
};

194
sni.js
View File

@ -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(".")
);
}

View File

@ -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");

View File

@ -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);
});
}