example server: add proxy and wildcard support

This commit is contained in:
AJ ONeal 2019-08-01 05:44:04 +00:00
parent 4d400a9828
commit ada2b9ccfe
4 changed files with 172 additions and 45 deletions

View File

@ -5,5 +5,16 @@ module.exports = {
email: "jon.doe@example.com", email: "jon.doe@example.com",
configDir: path.join(__dirname, "acme"), configDir: path.join(__dirname, "acme"),
srv: "/srv/www/", srv: "/srv/www/",
api: "/srv/api/" 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"
})
}
}; };

77
package-lock.json generated
View File

@ -4,16 +4,16 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@coolaj86/urequest": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz",
"integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA=="
},
"@root/mkdirp": { "@root/mkdirp": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
}, },
"@root/request": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw=="
},
"accepts": { "accepts": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@ -38,12 +38,12 @@
"integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" "integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w=="
}, },
"acme-v2": { "acme-v2": {
"version": "1.7.7", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.7.7.tgz", "resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.8.2.tgz",
"integrity": "sha512-Pg0EQ45h8N2e4K2goYedutCgWxAmtcruwDHr6hgPBgAWEORVb5SQEdXjtEhCrn+APtr7MyFPryyzXpYpDD5ecA==", "integrity": "sha512-uYGA+DuTnA44EsGXE413XnbTotGHCzkucXjMk23QRwGnaGlnr0lNBoYjByyeIVLSzj0W6Y9FqA9h+15+H+ltMw==",
"requires": { "requires": {
"@coolaj86/urequest": "^1.3.6", "@root/request": "^1.3.11",
"rsa-compat": "^2.0.6" "rsa-compat": "^2.0.8"
} }
}, },
"array-flatten": { "array-flatten": {
@ -175,6 +175,12 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true "dev": true
}, },
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true
},
"express": { "express": {
"version": "4.16.4", "version": "4.16.4",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
@ -237,6 +243,32 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"follow-redirects": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
"dev": true,
"requires": {
"debug": "^3.2.6"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -250,13 +282,13 @@
"dev": true "dev": true
}, },
"greenlock": { "greenlock": {
"version": "2.7.24", "version": "2.8.2",
"resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.7.24.tgz", "resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.8.2.tgz",
"integrity": "sha512-GQb2LMF6IiEzhp01F6eIN7HlPVlUWpWsBZZn7DOIo9upFAWhFpn2w1PStjGb17VmTkg+lgxzcajqcy6AJhCHUQ==", "integrity": "sha512-pCAYjgVova1ZoUHhuCfIw/3Rs5tE6DK1YF2LI7Cyh15QFBZJNU7pngMvDfeFft3It4WqnHezNgyDWAeV2pWFaw==",
"requires": { "requires": {
"acme": "^1.3.0", "acme": "^1.3.0",
"acme-dns-01-cli": "^3.0.0", "acme-dns-01-cli": "^3.0.0",
"acme-v2": "^1.7.7", "acme-v2": "^1.8.1",
"cert-info": "^1.5.1", "cert-info": "^1.5.1",
"greenlock-store-fs": "^3.0.2", "greenlock-store-fs": "^3.0.2",
"keypairs": "^1.2.14", "keypairs": "^1.2.14",
@ -287,6 +319,17 @@
"statuses": ">= 1.4.0 < 2" "statuses": ">= 1.4.0 < 2"
} }
}, },
"http-proxy": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
"dev": true,
"requires": {
"eventemitter3": "^3.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.23", "version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
@ -467,6 +510,12 @@
"escape-html": "^1.0.3" "escape-html": "^1.0.3"
} }
}, },
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"rsa-compat": { "rsa-compat": {
"version": "2.0.8", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", "resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz",

View File

@ -8,7 +8,7 @@
"example": "examples" "example": "examples"
}, },
"dependencies": { "dependencies": {
"greenlock": "^2.7.24", "greenlock": "^2.8.2",
"redirect-https": "^1.1.5" "redirect-https": "^1.1.5"
}, },
"files": [ "files": [
@ -18,6 +18,7 @@
"spdy": "^3.4.7" "spdy": "^3.4.7"
}, },
"devDependencies": { "devDependencies": {
"http-proxy": "^1.17.0",
"express": "^4.16.3", "express": "^4.16.3",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",

124
server.js
View File

@ -58,6 +58,31 @@ if (require.main === module) {
}); });
} }
function matchConfig(thing, domain) {
if (!thing) {
return false;
}
if (thing[domain]) {
return domain;
}
var keys = Object.keys(thing);
var result = null;
keys.some(function(k) {
if ("*" !== k[0]) {
return;
}
// "foo.whatever.com".endsWith("*.whatever.com".slice(1))
if (domain.endsWith(k.slice(1).toLowerCase())) {
result = k;
return true;
}
});
return result;
}
function myApproveDomains(opts) { function myApproveDomains(opts) {
console.info("SNI:", opts.domain); console.info("SNI:", opts.domain);
// In this example the filesystem is our "database". // In this example the filesystem is our "database".
@ -67,6 +92,52 @@ function myApproveDomains(opts) {
var domains = []; var domains = [];
var original = opts.domain; var original = opts.domain;
var bare = original.replace(/^(www|api)\./, ""); var bare = original.replace(/^(www|api)\./, "");
var challenger = matchConfig(config.challenges, original);
if (challenger) {
opts.challenges = {
"dns-01": config.challenges[challenger]
};
domains.push(challenger);
return approveThem();
}
if (matchConfig(config.proxy, original)) {
console.log("debug: found proxy for", original);
domains.push(original);
return approveThem();
}
function approveThem() {
console.info("Approved domains:", domains);
opts.domains = domains;
//opts.email = email;
opts.agreeTos = true;
// pick the shortest (bare) or latest (www. instead of api.) to be the subject
opts.subject = opts.domains.sort(function(a, b) {
var len = a.length - b.length;
if (0 !== len) {
return len;
}
if (a < b) {
return 1;
} else {
return -1;
}
})[0];
if (!opts.challenges) {
opts.challenges = {};
}
opts.challenges["http-01"] = require("le-challenge-fs");
//opts.challenges['dns-01'] = require('le-challenge-dns');
// explicitly set account id and certificate.id
opts.account = { id: opts.email };
opts.certificate = { id: opts.subject };
return Promise.resolve(opts);
}
// The goal here is to support both bare and www domains // The goal here is to support both bare and www domains
// //
// dns:example.com + fs:www.example.com => both // dns:example.com + fs:www.example.com => both
@ -77,7 +148,6 @@ function myApproveDomains(opts) {
// //
// dns:example.com + fs:example.com => example.com // dns:example.com + fs:example.com => example.com
// dns:www.example.com + fs:www.example.com => www.example.com // dns:www.example.com + fs:www.example.com => www.example.com
//
return checkWwws(bare) return checkWwws(bare)
.then(function(hostname) { .then(function(hostname) {
// hostname is either example.com or www.example.com // hostname is either example.com or www.example.com
@ -116,34 +186,7 @@ function myApproveDomains(opts) {
return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'"));
} }
console.info("Approved domains:", domains); return approveThem();
opts.domains = domains;
//opts.email = email;
opts.agreeTos = true;
// pick the shortest (bare) or latest (www. instead of api.) to be the subject
opts.subject = opts.domains.sort(function(a, b) {
var len = a.length - b.length;
if (0 !== len) {
return len;
}
if (a < b) {
return 1;
} else {
return -1;
}
})[0];
if (!opts.challenges) {
opts.challenges = {};
}
opts.challenges["http-01"] = require("le-challenge-fs");
//opts.challenges['dns-01'] = require('le-challenge-dns');
// explicitly set account id and certificate.id
opts.account = { id: opts.email };
opts.certificate = { id: opts.subject };
return Promise.resolve(opts);
}); });
} }
exports.myApproveDomains = myApproveDomains; exports.myApproveDomains = myApproveDomains;
@ -213,12 +256,35 @@ function checkWwws(_hostname) {
} }
exports.checkWwws = checkWwws; exports.checkWwws = checkWwws;
var httpProxy = require("http-proxy");
var proxy = httpProxy.createProxyServer({
xfwd: true
});
proxy.on("error", function(req, res) {
res.statusCode = 500;
res.end("500: Server Error");
});
function myVhostApp(req, res) { function myVhostApp(req, res) {
req.on("error", function(err) { req.on("error", function(err) {
console.error("HTTPS Request Network Connection Error:"); console.error("HTTPS Request Network Connection Error:");
console.error(err); console.error(err);
}); });
// this is protected by greenlock-express from domain fronting attacks
var host = req.headers.host;
// ex: example.com
// ex: example.com:4080
console.log("debug: host is", host);
var domain = matchConfig(config.proxy, host);
if (domain) {
console.log("debug: forwarding to", config.proxy[domain]);
proxy.web(req, res, { target: config.proxy[domain] });
return;
}
// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
// (also: only domains approved above will get here) // (also: only domains approved above will get here)
console.info(""); console.info("");