roster-server 2.1.12 → 2.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +55 -8
- package/package.json +1 -1
- package/test/roster-server.test.js +45 -0
- package/vendor/acme-dns-01-cli-wrapper.js +8 -7
- package/vendor/greenlock-express/demo.js +6 -4
- package/vendor/greenlock-express/greenlock-shim.js +6 -16
- package/vendor/greenlock-express/http-middleware.js +16 -11
- package/vendor/greenlock-express/https-middleware.js +3 -5
- package/vendor/greenlock-express/lib/compat.js +9 -5
- package/vendor/greenlock-express/main.js +7 -6
- package/vendor/greenlock-express/master.js +6 -5
- package/vendor/greenlock-express/scripts/postinstall +2 -1
- package/vendor/greenlock-express/servers.js +17 -18
- package/vendor/greenlock-express/sni.js +10 -12
package/index.js
CHANGED
|
@@ -234,6 +234,10 @@ class Roster {
|
|
|
234
234
|
this.disableWildcard = options.disableWildcard !== undefined
|
|
235
235
|
? parseBooleanFlag(options.disableWildcard, false)
|
|
236
236
|
: parseBooleanFlag(process.env.ROSTER_DISABLE_WILDCARD, false);
|
|
237
|
+
const isBunRuntime = typeof Bun !== 'undefined' || process.release?.name === 'bun';
|
|
238
|
+
this.combineWildcardCerts = options.combineWildcardCerts !== undefined
|
|
239
|
+
? parseBooleanFlag(options.combineWildcardCerts, false)
|
|
240
|
+
: parseBooleanFlag(process.env.ROSTER_COMBINE_WILDCARD_CERTS, isBunRuntime);
|
|
237
241
|
|
|
238
242
|
const port = options.port === undefined ? 443 : options.port;
|
|
239
243
|
if (port === 80 && !this.local) {
|
|
@@ -379,10 +383,21 @@ class Roster {
|
|
|
379
383
|
if ((domain.match(/\./g) || []).length < 2) {
|
|
380
384
|
primaryAltnames.push(`www.${domain}`);
|
|
381
385
|
}
|
|
386
|
+
const shouldCombineWildcard = this.combineWildcardCerts && this.wildcardZones.has(domain) && this.dnsChallenge;
|
|
387
|
+
if (shouldCombineWildcard) {
|
|
388
|
+
primaryAltnames.push(`*.${domain}`);
|
|
389
|
+
}
|
|
382
390
|
const primarySite = {
|
|
383
391
|
subject: domain,
|
|
384
392
|
altnames: primaryAltnames
|
|
385
393
|
};
|
|
394
|
+
if (shouldCombineWildcard) {
|
|
395
|
+
const dns01 = { ...this.dnsChallenge };
|
|
396
|
+
if (dns01.propagationDelay === undefined) dns01.propagationDelay = 120000;
|
|
397
|
+
if (dns01.autoContinue === undefined) dns01.autoContinue = false;
|
|
398
|
+
if (dns01.dryRunDelay === undefined) dns01.dryRunDelay = dns01.propagationDelay;
|
|
399
|
+
primarySite.challenges = { 'dns-01': dns01 };
|
|
400
|
+
}
|
|
386
401
|
const existingPrimarySite = Array.isArray(existingConfig.sites)
|
|
387
402
|
? existingConfig.sites.find(site => site.subject === domain)
|
|
388
403
|
: null;
|
|
@@ -390,7 +405,7 @@ class Roster {
|
|
|
390
405
|
sitesConfig.push(primarySite);
|
|
391
406
|
|
|
392
407
|
// Wildcard cert is issued separately and uses dns-01 only.
|
|
393
|
-
if (this.wildcardZones.has(domain) && this.dnsChallenge) {
|
|
408
|
+
if (!shouldCombineWildcard && this.wildcardZones.has(domain) && this.dnsChallenge) {
|
|
394
409
|
const wildcardSubject = `*.${domain}`;
|
|
395
410
|
const dns01 = { ...this.dnsChallenge };
|
|
396
411
|
if (dns01.propagationDelay === undefined) {
|
|
@@ -853,8 +868,40 @@ class Roster {
|
|
|
853
868
|
}
|
|
854
869
|
return null;
|
|
855
870
|
};
|
|
871
|
+
const issueAndReloadPemsForServername = async (servername) => {
|
|
872
|
+
const host = normalizeHostInput(servername).trim().toLowerCase();
|
|
873
|
+
if (!host) return null;
|
|
874
|
+
|
|
875
|
+
let pems = resolvePemsForServername(host);
|
|
876
|
+
if (pems) return pems;
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
await greenlockRuntime.get({ servername: host });
|
|
880
|
+
} catch (error) {
|
|
881
|
+
log.warn(`⚠️ Greenlock issuance failed for ${host}: ${error?.message || error}`);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
pems = resolvePemsForServername(host);
|
|
885
|
+
if (pems) return pems;
|
|
886
|
+
|
|
887
|
+
// For wildcard zones, try a valid subdomain bootstrap host so Greenlock can
|
|
888
|
+
// resolve the wildcard site without relying on invalid "*.domain" servername input.
|
|
889
|
+
const wildcardSubject = wildcardSubjectForHost(host);
|
|
890
|
+
const zone = wildcardSubject ? wildcardRoot(wildcardSubject) : null;
|
|
891
|
+
if (zone) {
|
|
892
|
+
const bootstrapHost = `bun-bootstrap.${zone}`;
|
|
893
|
+
try {
|
|
894
|
+
await greenlockRuntime.get({ servername: bootstrapHost });
|
|
895
|
+
} catch (error) {
|
|
896
|
+
log.warn(`⚠️ Greenlock wildcard bootstrap failed for ${bootstrapHost}: ${error?.message || error}`);
|
|
897
|
+
}
|
|
898
|
+
pems = resolvePemsForServername(host);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return pems;
|
|
902
|
+
};
|
|
856
903
|
const ensureBunDefaultPems = async (primaryDomain) => {
|
|
857
|
-
let pems =
|
|
904
|
+
let pems = await issueAndReloadPemsForServername(primaryDomain);
|
|
858
905
|
if (pems) return pems;
|
|
859
906
|
|
|
860
907
|
const certSubject = primaryDomain.startsWith('*.') ? wildcardRoot(primaryDomain) : primaryDomain;
|
|
@@ -891,12 +938,12 @@ class Roster {
|
|
|
891
938
|
key: defaultPems.key,
|
|
892
939
|
cert: defaultPems.cert,
|
|
893
940
|
SNICallback: (servername, callback) => {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
941
|
+
issueAndReloadPemsForServername(servername)
|
|
942
|
+
.then((pems) => {
|
|
943
|
+
const selected = pems || defaultPems;
|
|
944
|
+
callback(null, tls.createSecureContext({ key: selected.key, cert: selected.cert }));
|
|
945
|
+
})
|
|
946
|
+
.catch(callback);
|
|
900
947
|
}
|
|
901
948
|
}, dispatcher);
|
|
902
949
|
log.warn(`⚠️ Bun runtime detected: using file-based TLS with SNI for ${primaryDomain} on port ${portNum}`);
|
package/package.json
CHANGED
|
@@ -313,6 +313,17 @@ describe('Roster', () => {
|
|
|
313
313
|
else process.env.ROSTER_DISABLE_WILDCARD = previous;
|
|
314
314
|
}
|
|
315
315
|
});
|
|
316
|
+
it('enables combined wildcard certs from env var', () => {
|
|
317
|
+
const previous = process.env.ROSTER_COMBINE_WILDCARD_CERTS;
|
|
318
|
+
process.env.ROSTER_COMBINE_WILDCARD_CERTS = '1';
|
|
319
|
+
try {
|
|
320
|
+
const roster = new Roster({ local: false });
|
|
321
|
+
assert.strictEqual(roster.combineWildcardCerts, true);
|
|
322
|
+
} finally {
|
|
323
|
+
if (previous === undefined) delete process.env.ROSTER_COMBINE_WILDCARD_CERTS;
|
|
324
|
+
else process.env.ROSTER_COMBINE_WILDCARD_CERTS = previous;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
316
327
|
});
|
|
317
328
|
|
|
318
329
|
describe('register (normal domain)', () => {
|
|
@@ -557,4 +568,38 @@ describe('Roster generateConfigJson', () => {
|
|
|
557
568
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
558
569
|
}
|
|
559
570
|
});
|
|
571
|
+
it('can combine apex+www+wildcard in one cert with dns-01', () => {
|
|
572
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'roster-config-'));
|
|
573
|
+
try {
|
|
574
|
+
const roster = new Roster({
|
|
575
|
+
local: false,
|
|
576
|
+
greenlockStorePath: tmpDir,
|
|
577
|
+
combineWildcardCerts: true,
|
|
578
|
+
dnsChallenge: {
|
|
579
|
+
module: 'acme-dns-01-cli',
|
|
580
|
+
propagationDelay: 120000,
|
|
581
|
+
autoContinue: false,
|
|
582
|
+
dryRunDelay: 120000
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
roster.domains = ['tagnu.com', 'www.tagnu.com', '*.tagnu.com'];
|
|
586
|
+
roster.wildcardZones.add('tagnu.com');
|
|
587
|
+
roster.generateConfigJson();
|
|
588
|
+
|
|
589
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
590
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
591
|
+
const apexSite = config.sites.find((site) => site.subject === 'tagnu.com');
|
|
592
|
+
const wildcardSite = config.sites.find((site) => site.subject === '*.tagnu.com');
|
|
593
|
+
|
|
594
|
+
assert.ok(apexSite);
|
|
595
|
+
assert.deepStrictEqual(
|
|
596
|
+
apexSite.altnames.sort(),
|
|
597
|
+
['tagnu.com', 'www.tagnu.com', '*.tagnu.com'].sort()
|
|
598
|
+
);
|
|
599
|
+
assert.ok(apexSite.challenges && apexSite.challenges['dns-01']);
|
|
600
|
+
assert.strictEqual(wildcardSite, undefined);
|
|
601
|
+
} finally {
|
|
602
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
603
|
+
}
|
|
604
|
+
});
|
|
560
605
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const legacyCli = require('acme-dns-01-cli');
|
|
4
|
+
const log = require('lemonlog')('acme-dns-01');
|
|
4
5
|
|
|
5
6
|
function toPromise(fn, context) {
|
|
6
7
|
if (typeof fn !== 'function') {
|
|
@@ -73,10 +74,10 @@ module.exports.create = function create(config = {}) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const ch = opts?.challenge || {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
log.info('');
|
|
78
|
+
log.info("[ACME dns-01 '" + (ch.altname || opts?.altname || 'unknown') + "' CHALLENGE]");
|
|
79
|
+
log.info("You're about to receive the following DNS query:");
|
|
80
|
+
log.info('');
|
|
80
81
|
const dnsHost = String(ch.dnsHost || '');
|
|
81
82
|
const dnsAuth = ch.dnsAuthorization || opts?.dnsAuthorization || null;
|
|
82
83
|
if (dnsHost && dnsAuth) {
|
|
@@ -87,15 +88,15 @@ module.exports.create = function create(config = {}) {
|
|
|
87
88
|
? Math.max(0, dryRunDelay)
|
|
88
89
|
: propagationDelay;
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
log.info(
|
|
91
92
|
'\tTXT\t' +
|
|
92
93
|
(ch.dnsHost || '_acme-challenge.<domain>') +
|
|
93
94
|
'\t' +
|
|
94
95
|
(ch.dnsAuthorization || '<dns-authorization-token>') +
|
|
95
96
|
'\tTTL 60'
|
|
96
97
|
);
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
log.info('');
|
|
99
|
+
log.info(
|
|
99
100
|
'Non-interactive mode (or autoContinue) detected. ' +
|
|
100
101
|
'Set the TXT record now. Continuing automatically in ' +
|
|
101
102
|
effectiveDelay +
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var log = require("lemonlog")("greenlock-demo");
|
|
4
|
+
|
|
3
5
|
require("./")
|
|
4
6
|
.init(initialize)
|
|
5
7
|
.serve(worker)
|
|
6
8
|
.master(function() {
|
|
7
|
-
|
|
9
|
+
log.info("Hello from master");
|
|
8
10
|
});
|
|
9
11
|
|
|
10
12
|
function initialize() {
|
|
@@ -19,15 +21,15 @@ function initialize() {
|
|
|
19
21
|
cluster: true,
|
|
20
22
|
|
|
21
23
|
notify: function(ev, params) {
|
|
22
|
-
|
|
24
|
+
log.info(ev, params);
|
|
23
25
|
}
|
|
24
26
|
};
|
|
25
27
|
return config;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
function worker(glx) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
log.info("");
|
|
32
|
+
log.info("Hello from worker #" + glx.id());
|
|
31
33
|
|
|
32
34
|
glx.serveApp(function(req, res) {
|
|
33
35
|
res.end("Hello, Encrypted World!");
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports.create = function(opts) {
|
|
4
4
|
var Greenlock = require("@root/greenlock");
|
|
5
|
+
var log = require("lemonlog")("greenlock-shim");
|
|
5
6
|
//var Init = require("@root/greenlock/lib/init.js");
|
|
6
7
|
var greenlock = opts.greenlock;
|
|
7
8
|
|
|
@@ -28,7 +29,7 @@ module.exports.create = function(opts) {
|
|
|
28
29
|
greenlock._defaults.notify = opts.notify;
|
|
29
30
|
}
|
|
30
31
|
} catch (e) {
|
|
31
|
-
|
|
32
|
+
log.error("Developer Error: notify not attached correctly");
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// re-export as top-level function to simplify rpc with workers
|
|
@@ -38,23 +39,12 @@ module.exports.create = function(opts) {
|
|
|
38
39
|
|
|
39
40
|
greenlock._find({}).then(function(sites) {
|
|
40
41
|
if (sites.length <= 0) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
log.warn("Warning: `find({})` returned 0 sites.");
|
|
43
|
+
log.warn(" Does `" + greenlock.manager._modulename + "` implement `find({})`?");
|
|
44
|
+
log.warn(" Did you add sites?");
|
|
45
|
+
log.warn(" npx greenlock add --subject example.com --altnames example.com");
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
|
-
// console.info("Ready to Serve:");
|
|
48
|
-
|
|
49
|
-
var max = 3;
|
|
50
|
-
if (sites.length >= 1) {
|
|
51
|
-
sites.slice(0, max).forEach(function(site) {
|
|
52
|
-
// console.info("\t", site.altnames.join(" "));
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (sites.length > max) {
|
|
56
|
-
// console.info("and %d others", sites.length - max);
|
|
57
|
-
}
|
|
58
48
|
});
|
|
59
49
|
|
|
60
50
|
return greenlock;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var HttpMiddleware = module.exports;
|
|
4
4
|
var servernameRe = /^[a-z0-9\.\-]+$/i;
|
|
5
5
|
var challengePrefix = "/.well-known/acme-challenge/";
|
|
6
|
+
var log = require("lemonlog")("greenlock-http");
|
|
6
7
|
|
|
7
8
|
HttpMiddleware.create = function(gl, defaultApp) {
|
|
8
9
|
if (defaultApp && "function" !== typeof defaultApp) {
|
|
@@ -49,7 +50,7 @@ HttpMiddleware.create = function(gl, defaultApp) {
|
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
if (done) {
|
|
52
|
-
|
|
53
|
+
log.error("Invariant violation: challenge middleware reached `done=true` and response path simultaneously");
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
56
|
// HEADERS SENT DEBUG NOTE #4b
|
|
@@ -60,16 +61,16 @@ HttpMiddleware.create = function(gl, defaultApp) {
|
|
|
60
61
|
// HEADERS SENT DEBUG NOTE #5
|
|
61
62
|
// I really don't see how this can be possible.
|
|
62
63
|
// Every case appears to be accounted for
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
log.error(
|
|
65
|
+
"Unhandled ACME challenge middleware error",
|
|
66
|
+
{
|
|
67
|
+
code: err.code || null,
|
|
68
|
+
context: err.context || null,
|
|
69
|
+
catchCountA: countA,
|
|
70
|
+
catchCountB: countB
|
|
71
|
+
}
|
|
69
72
|
);
|
|
70
|
-
|
|
71
|
-
console.error("visit: https://git.rootprojects.org/root/greenlock-express.js/issues/9");
|
|
72
|
-
console.error();
|
|
73
|
+
log.error(err.stack || err.message || err);
|
|
73
74
|
try {
|
|
74
75
|
res.end("Internal Server Error [1003]: See logs for details.");
|
|
75
76
|
} catch (e) {
|
|
@@ -114,7 +115,11 @@ function explainError(gl, err, ctx, hostname) {
|
|
|
114
115
|
err.context = ctx;
|
|
115
116
|
}
|
|
116
117
|
// leaving this in the build for now because it will help with existing error reports
|
|
117
|
-
|
|
118
|
+
log.error("Network connection error during ACME challenge handling", {
|
|
119
|
+
context: err.context || "",
|
|
120
|
+
servername: err.servername || hostname || "",
|
|
121
|
+
message: err.message
|
|
122
|
+
});
|
|
118
123
|
(gl.notify || gl._notify)("error", err);
|
|
119
124
|
return err;
|
|
120
125
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var SanitizeHost = module.exports;
|
|
4
4
|
var HttpMiddleware = require("./http-middleware.js");
|
|
5
|
+
var log = require("lemonlog")("greenlock-https");
|
|
5
6
|
|
|
6
7
|
SanitizeHost.create = function(gl, app) {
|
|
7
8
|
return function(req, res, next) {
|
|
@@ -61,8 +62,6 @@ SanitizeHost.create = function(gl, app) {
|
|
|
61
62
|
// and such).
|
|
62
63
|
// It was common for the log message to pop up as the first request
|
|
63
64
|
// to the server, and that was confusing. So instead now we do nothing.
|
|
64
|
-
|
|
65
|
-
//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
|
|
66
65
|
//gl._skip_fronting_check = true;
|
|
67
66
|
}
|
|
68
67
|
*/
|
|
@@ -94,7 +93,7 @@ SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
|
|
94
93
|
// domain fronting attacks allowed
|
|
95
94
|
if (warnDomainFronting) {
|
|
96
95
|
// https://github.com/nodejs/node/issues/24095
|
|
97
|
-
|
|
96
|
+
log.warn(
|
|
98
97
|
"Warning: node " +
|
|
99
98
|
process.version +
|
|
100
99
|
" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater."
|
|
@@ -111,7 +110,6 @@ SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
|
|
111
110
|
// TODO optimize / cache?
|
|
112
111
|
// *should* always have a string, right?
|
|
113
112
|
// *should* always be lowercase already, right?
|
|
114
|
-
//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
|
|
115
113
|
var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost;
|
|
116
114
|
if (isSubject) {
|
|
117
115
|
return true;
|
|
@@ -129,7 +127,7 @@ SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
|
|
129
127
|
} catch (e) {
|
|
130
128
|
// not sure what else to do in this situation...
|
|
131
129
|
if (warnUnexpectedError) {
|
|
132
|
-
|
|
130
|
+
log.warn("Encountered error while performing domain fronting check: " + e.message);
|
|
133
131
|
warnUnexpectedError = false;
|
|
134
132
|
}
|
|
135
133
|
return true;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var log = require("lemonlog")("greenlock-compat");
|
|
4
|
+
|
|
3
5
|
function requireBluebird() {
|
|
4
6
|
try {
|
|
5
7
|
return require("bluebird");
|
|
6
8
|
} catch (e) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
log.error("");
|
|
10
|
+
log.error("DON'T PANIC. You're running an old version of node with incomplete Promise support.");
|
|
11
|
+
log.error("EASY FIX: `npm install --save bluebird`");
|
|
12
|
+
log.error("");
|
|
11
13
|
throw e;
|
|
12
14
|
}
|
|
13
15
|
}
|
|
@@ -21,7 +23,9 @@ if ("function" !== typeof require("util").promisify) {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
if (!console.debug) {
|
|
24
|
-
console.debug =
|
|
26
|
+
console.debug = function() {
|
|
27
|
+
log.debug.apply(log, arguments);
|
|
28
|
+
};
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
var fs = require("fs");
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// this is the stuff that should run in the main foreground process,
|
|
4
4
|
// whether it's single or master
|
|
5
5
|
|
|
6
|
+
var log = require("lemonlog")("greenlock-main");
|
|
6
7
|
var major = parseInt(process.versions.node.split(".")[0], 10);
|
|
7
8
|
var minor = parseInt(process.versions.node.split(".")[1], 10) || 0;
|
|
8
9
|
var _hasSetSecureContext = false;
|
|
@@ -14,19 +15,19 @@ _hasSetSecureContext = major > 11 || (major === 11 && minor >= 2);
|
|
|
14
15
|
// TODO document in issues
|
|
15
16
|
if (!_hasSetSecureContext) {
|
|
16
17
|
// TODO this isn't necessary if greenlock options are set with options.cert
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
log.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext().");
|
|
19
|
+
log.warn(" The default certificate may not be set.");
|
|
19
20
|
shouldUpgrade = true;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
if (major < 11 || (11 === major && minor < 2)) {
|
|
23
24
|
// https://github.com/nodejs/node/issues/24095
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
log.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate().");
|
|
26
|
+
log.warn(" This is necessary to guard against domain fronting attacks.");
|
|
26
27
|
shouldUpgrade = true;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
if (shouldUpgrade) {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
log.warn("Warning: Please upgrade to node v11.2.0 or greater.");
|
|
32
|
+
log.warn("");
|
|
32
33
|
}
|
|
@@ -6,6 +6,7 @@ var Master = module.exports;
|
|
|
6
6
|
|
|
7
7
|
var cluster = require("cluster");
|
|
8
8
|
var os = require("os");
|
|
9
|
+
var log = require("lemonlog")("greenlock-master");
|
|
9
10
|
var msgPrefix = "greenlock:";
|
|
10
11
|
|
|
11
12
|
Master.create = function(opts) {
|
|
@@ -112,11 +113,11 @@ Master._spawnWorker = function(opts, greenlock) {
|
|
|
112
113
|
|
|
113
114
|
// For now just kill all when any die
|
|
114
115
|
if (signal) {
|
|
115
|
-
|
|
116
|
+
log.error("worker was killed by signal:", signal);
|
|
116
117
|
} else if (code !== 0) {
|
|
117
|
-
|
|
118
|
+
log.error("worker exited with error code:", code);
|
|
118
119
|
} else {
|
|
119
|
-
|
|
120
|
+
log.error("worker unexpectedly quit without exit code or signal");
|
|
120
121
|
}
|
|
121
122
|
process.exit(2);
|
|
122
123
|
|
|
@@ -155,8 +156,8 @@ Master._spawnWorker = function(opts, greenlock) {
|
|
|
155
156
|
try {
|
|
156
157
|
rpc();
|
|
157
158
|
} catch (e) {
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
log.error("Unexpected and uncaught greenlock." + msg._funcname + " error:");
|
|
160
|
+
log.error(e);
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var log = require("lemonlog")("greenlock-postinstall");
|
|
3
4
|
|
|
4
5
|
// BG WH \u001b[47m
|
|
5
6
|
// BOLD \u001b[1m
|
|
@@ -68,7 +69,7 @@ setTimeout(function() {
|
|
|
68
69
|
"================================================================================",
|
|
69
70
|
""
|
|
70
71
|
]).forEach(function(line) {
|
|
71
|
-
|
|
72
|
+
log.info(line);
|
|
72
73
|
});
|
|
73
74
|
}, 300);
|
|
74
75
|
|
|
@@ -7,6 +7,7 @@ var HttpMiddleware = require("./http-middleware.js");
|
|
|
7
7
|
var HttpsMiddleware = require("./https-middleware.js");
|
|
8
8
|
var sni = require("./sni.js");
|
|
9
9
|
var cluster = require("cluster");
|
|
10
|
+
var log = require("lemonlog")("greenlock-servers");
|
|
10
11
|
|
|
11
12
|
Servers.create = function(greenlock) {
|
|
12
13
|
var servers = {};
|
|
@@ -21,7 +22,7 @@ Servers.create = function(greenlock) {
|
|
|
21
22
|
servers.httpServer = function(defaultApp) {
|
|
22
23
|
if (_httpServer) {
|
|
23
24
|
if (defaultApp) {
|
|
24
|
-
|
|
25
|
+
log.error("Invalid API usage: `httpServer(app)` can only be called once");
|
|
25
26
|
process.exit(1);
|
|
26
27
|
}
|
|
27
28
|
return _httpServer;
|
|
@@ -104,7 +105,7 @@ Servers.create = function(greenlock) {
|
|
|
104
105
|
var plainAddr = "0.0.0.0";
|
|
105
106
|
var plainPort = 80;
|
|
106
107
|
plainServer.listen(plainPort, plainAddr, function() {
|
|
107
|
-
|
|
108
|
+
log.info(
|
|
108
109
|
idstr + "Listening on",
|
|
109
110
|
plainAddr + ":" + plainPort,
|
|
110
111
|
"for ACME challenges, and redirecting to HTTPS"
|
|
@@ -116,7 +117,7 @@ Servers.create = function(greenlock) {
|
|
|
116
117
|
var secureAddr = "0.0.0.0";
|
|
117
118
|
var securePort = 443;
|
|
118
119
|
secureServer.listen(securePort, secureAddr, function() {
|
|
119
|
-
|
|
120
|
+
log.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic");
|
|
120
121
|
|
|
121
122
|
plainServer.removeListener("error", startError);
|
|
122
123
|
secureServer.removeListener("error", startError);
|
|
@@ -130,18 +131,21 @@ Servers.create = function(greenlock) {
|
|
|
130
131
|
};
|
|
131
132
|
|
|
132
133
|
function explainError(e) {
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
log.error("Server startup error", {
|
|
135
|
+
code: e.code || e.errno || null,
|
|
136
|
+
address: e.address || null,
|
|
137
|
+
port: e.port || null,
|
|
138
|
+
message: e.message
|
|
139
|
+
});
|
|
135
140
|
if ("EACCES" === e.errno) {
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
log.error("Insufficient permission to bind " + e.address + ":" + e.port + ".");
|
|
142
|
+
log.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
|
|
138
143
|
} else if ("EADDRINUSE" === e.errno) {
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
log.error("Address already in use: " + e.address + ":" + e.port + ".");
|
|
145
|
+
log.error("You probably need to stop that program or restart your computer.");
|
|
141
146
|
} else {
|
|
142
|
-
|
|
147
|
+
log.error(e.code + ": '" + e.address + ":" + e.port + "'");
|
|
143
148
|
}
|
|
144
|
-
console.error();
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
function wrapDefaultSniCallback(greenlock, secureOpts) {
|
|
@@ -156,13 +160,8 @@ function wrapDefaultSniCallback(greenlock, secureOpts) {
|
|
|
156
160
|
}
|
|
157
161
|
*/
|
|
158
162
|
if (secureOpts.SNICallback) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.warn();
|
|
162
|
-
console.warn(" We're very open to implementing support for this,");
|
|
163
|
-
console.warn(" we just don't understand the use case yet.");
|
|
164
|
-
console.warn(" Please open an issue to discuss. We'd love to help.");
|
|
165
|
-
console.warn();
|
|
163
|
+
log.warn("Ignoring user-provided tlsOptions.SNICallback; Greenlock-managed SNI callback is required");
|
|
164
|
+
log.warn("If you need custom SNI behavior, open an issue to discuss integration support");
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
// TODO greenlock.servername for workers
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var sni = module.exports;
|
|
4
4
|
var tls = require("tls");
|
|
5
|
+
var log = require("lemonlog")("greenlock-sni");
|
|
5
6
|
var servernameRe = /^[a-z0-9\.\-]+$/i;
|
|
6
7
|
|
|
7
8
|
// a nice, round, irrational number - about every 6¼ hours
|
|
@@ -32,23 +33,21 @@ sni.create = function(greenlock, secureOpts) {
|
|
|
32
33
|
// TODO _notify() or notify()?
|
|
33
34
|
(greenlock.notify || greenlock._notify)(ev, args);
|
|
34
35
|
} catch (e) {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
log.error(e);
|
|
37
|
+
log.error(ev, args);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function getSecureContext(servername, cb) {
|
|
41
|
-
//console.log("debug sni", servername);
|
|
42
42
|
if ("string" !== typeof servername) {
|
|
43
43
|
// this will never happen... right? but stranger things have...
|
|
44
|
-
|
|
44
|
+
log.error("[sanity fail] non-string servername:", servername);
|
|
45
45
|
cb(new Error("invalid servername"), null);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
var secureContext = getCachedContext(servername);
|
|
50
50
|
if (secureContext) {
|
|
51
|
-
//console.log("debug sni got cached context", servername, getCachedMeta(servername));
|
|
52
51
|
cb(null, secureContext);
|
|
53
52
|
return;
|
|
54
53
|
}
|
|
@@ -56,20 +55,19 @@ sni.create = function(greenlock, secureOpts) {
|
|
|
56
55
|
getFreshContext(servername)
|
|
57
56
|
.then(function(secureContext) {
|
|
58
57
|
if (secureContext) {
|
|
59
|
-
//console.log("debug sni got fresh context", servername, getCachedMeta(servername));
|
|
60
58
|
cb(null, secureContext);
|
|
61
59
|
return;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
// Note: this does not replace tlsSocket.setSecureContext()
|
|
65
63
|
// as it only works when SNI has been sent
|
|
66
|
-
//console.log("debug sni got default context", servername, getCachedMeta(servername));
|
|
67
64
|
if (!/PROD/.test(process.env.ENV) || /DEV|STAG/.test(process.env.ENV)) {
|
|
68
65
|
// Change this once
|
|
69
66
|
// A) the 'notify' message passing is verified fixed in cluster mode
|
|
70
67
|
// B) we have a good way to let people know their server isn't configured
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
log.debug("SNI servername not configured; falling back to default context", {
|
|
69
|
+
servername: servername
|
|
70
|
+
});
|
|
73
71
|
notify("servername_unknown", {
|
|
74
72
|
servername: servername
|
|
75
73
|
});
|
|
@@ -81,7 +79,6 @@ sni.create = function(greenlock, secureOpts) {
|
|
|
81
79
|
err.context = "sni_callback";
|
|
82
80
|
}
|
|
83
81
|
notify("error", err);
|
|
84
|
-
//console.log("debug sni error", servername, err);
|
|
85
82
|
cb(err);
|
|
86
83
|
});
|
|
87
84
|
}
|
|
@@ -125,8 +122,9 @@ sni.create = function(greenlock, secureOpts) {
|
|
|
125
122
|
// Change this once
|
|
126
123
|
// A) the 'notify' message passing is verified fixed in cluster mode
|
|
127
124
|
// B) we have a good way to let people know their server isn't configured
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
log.debug("Invalid SNI servername received; skipping certificate lookup", {
|
|
126
|
+
servername: servername
|
|
127
|
+
});
|
|
130
128
|
notify("servername_invalid", {
|
|
131
129
|
servername: servername
|
|
132
130
|
});
|