roster-server 2.1.20 → 2.1.23
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/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const legacyCli = require('acme-dns-01-cli');
|
|
4
4
|
const log = require('lemonlog')('acme-dns-01');
|
|
5
|
+
const dns = require('node:dns').promises;
|
|
5
6
|
|
|
6
7
|
function toPromise(fn, context) {
|
|
7
8
|
if (typeof fn !== 'function') {
|
|
@@ -60,16 +61,72 @@ module.exports.create = function create(config = {}) {
|
|
|
60
61
|
: Number.isFinite(Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS))
|
|
61
62
|
? Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS)
|
|
62
63
|
: propagationDelay;
|
|
64
|
+
const parseBool = (value, fallback) => {
|
|
65
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
66
|
+
const normalized = String(value).trim().toLowerCase();
|
|
67
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
68
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
69
|
+
return fallback;
|
|
70
|
+
};
|
|
71
|
+
const verifyDnsBeforeContinue = config.verifyDnsBeforeContinue !== undefined
|
|
72
|
+
? parseBool(config.verifyDnsBeforeContinue, true)
|
|
73
|
+
: parseBool(process.env.ROSTER_DNS_VERIFY_BEFORE_CONTINUE, true);
|
|
74
|
+
const dnsPollIntervalMs = Number.isFinite(config.dnsPollIntervalMs)
|
|
75
|
+
? config.dnsPollIntervalMs
|
|
76
|
+
: Number.isFinite(Number(process.env.ROSTER_DNS_POLL_INTERVAL_MS))
|
|
77
|
+
? Number(process.env.ROSTER_DNS_POLL_INTERVAL_MS)
|
|
78
|
+
: 5000;
|
|
79
|
+
const dnsPollTimeoutMs = Number.isFinite(config.dnsPollTimeoutMs)
|
|
80
|
+
? config.dnsPollTimeoutMs
|
|
81
|
+
: Number.isFinite(Number(process.env.ROSTER_DNS_POLL_TIMEOUT_MS))
|
|
82
|
+
? Number(process.env.ROSTER_DNS_POLL_TIMEOUT_MS)
|
|
83
|
+
: null;
|
|
63
84
|
|
|
64
85
|
function sleep(ms) {
|
|
65
86
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
87
|
}
|
|
67
88
|
|
|
89
|
+
function normalizeTxtChunk(value) {
|
|
90
|
+
return String(value || '').replace(/^"+|"+$/g, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveExpectedToken(opts, ch) {
|
|
94
|
+
const candidate = ch?.dnsAuthorization || opts?.dnsAuthorization;
|
|
95
|
+
return candidate ? String(candidate).trim() : '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function hasDnsTxtToken(dnsHost, expectedToken) {
|
|
99
|
+
if (!dnsHost || !expectedToken) return false;
|
|
100
|
+
try {
|
|
101
|
+
const records = await dns.resolveTxt(dnsHost);
|
|
102
|
+
for (const recordParts of records || []) {
|
|
103
|
+
const joined = (recordParts || []).map(normalizeTxtChunk).join('').trim();
|
|
104
|
+
if (joined === expectedToken) return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function waitForDnsTxtPropagation(dnsHost, expectedToken, timeoutMs) {
|
|
113
|
+
const started = Date.now();
|
|
114
|
+
const maxWait = Math.max(0, Number.isFinite(timeoutMs) ? timeoutMs : 0);
|
|
115
|
+
if (maxWait === 0) return hasDnsTxtToken(dnsHost, expectedToken);
|
|
116
|
+
|
|
117
|
+
while ((Date.now() - started) <= maxWait) {
|
|
118
|
+
if (await hasDnsTxtToken(dnsHost, expectedToken)) return true;
|
|
119
|
+
await sleep(Math.max(1000, dnsPollIntervalMs));
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
const presentedByHost = new Map();
|
|
125
|
+
const presentedByAltname = new Map();
|
|
69
126
|
|
|
70
127
|
async function setChallenge(opts) {
|
|
71
128
|
const isInteractive = Boolean(process.stdin && process.stdin.isTTY);
|
|
72
|
-
if (isInteractive && !autoContinue) {
|
|
129
|
+
if (isInteractive && !autoContinue && !verifyDnsBeforeContinue) {
|
|
73
130
|
return toPromise(challenger.set, challenger)(opts);
|
|
74
131
|
}
|
|
75
132
|
|
|
@@ -83,10 +140,16 @@ module.exports.create = function create(config = {}) {
|
|
|
83
140
|
if (dnsHost && dnsAuth) {
|
|
84
141
|
presentedByHost.set(dnsHost, dnsAuth);
|
|
85
142
|
}
|
|
143
|
+
const altname = String(ch.altname || opts?.altname || '');
|
|
144
|
+
if (altname && dnsAuth) {
|
|
145
|
+
presentedByAltname.set(altname, { dnsHost, dnsAuthorization: dnsAuth });
|
|
146
|
+
}
|
|
86
147
|
const isDryRunChallenge = dnsHost.includes('_greenlock-dryrun-');
|
|
87
148
|
const effectiveDelay = isDryRunChallenge
|
|
88
149
|
? Math.max(0, dryRunDelay)
|
|
89
150
|
: propagationDelay;
|
|
151
|
+
const effectiveTimeoutMs = dnsPollTimeoutMs === null ? effectiveDelay : dnsPollTimeoutMs;
|
|
152
|
+
const expectedToken = resolveExpectedToken(opts, ch);
|
|
90
153
|
|
|
91
154
|
log.info(
|
|
92
155
|
'\tTXT\t' +
|
|
@@ -96,6 +159,28 @@ module.exports.create = function create(config = {}) {
|
|
|
96
159
|
'\tTTL 60'
|
|
97
160
|
);
|
|
98
161
|
log.info('');
|
|
162
|
+
if (verifyDnsBeforeContinue && dnsHost && expectedToken) {
|
|
163
|
+
log.info(
|
|
164
|
+
'DNS verification enabled. Continuing automatically when TXT appears at ' +
|
|
165
|
+
dnsHost +
|
|
166
|
+
' (timeout ' +
|
|
167
|
+
effectiveTimeoutMs +
|
|
168
|
+
'ms, poll ' +
|
|
169
|
+
dnsPollIntervalMs +
|
|
170
|
+
'ms).'
|
|
171
|
+
);
|
|
172
|
+
const propagated = await waitForDnsTxtPropagation(dnsHost, expectedToken, effectiveTimeoutMs);
|
|
173
|
+
if (propagated) {
|
|
174
|
+
log.info(`DNS TXT detected for ${dnsHost}; continuing ACME flow.`);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
log.warn(
|
|
178
|
+
`DNS TXT not detected for ${dnsHost} within ${effectiveTimeoutMs}ms; ` +
|
|
179
|
+
'continuing anyway (ACME preflight may still fail).'
|
|
180
|
+
);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
99
184
|
log.info(
|
|
100
185
|
'Non-interactive mode (or autoContinue) detected. ' +
|
|
101
186
|
'Set the TXT record now. Continuing automatically in ' +
|
|
@@ -108,11 +193,18 @@ module.exports.create = function create(config = {}) {
|
|
|
108
193
|
|
|
109
194
|
async function getChallenge(opts) {
|
|
110
195
|
const ch = opts?.challenge || {};
|
|
111
|
-
const
|
|
196
|
+
const altname = String(ch.altname || opts?.altname || '');
|
|
197
|
+
const wildcardZone = altname.startsWith('*.') ? altname.slice(2) : '';
|
|
198
|
+
const dnsHostFromAltname = wildcardZone ? `_acme-challenge.${wildcardZone}` : '';
|
|
199
|
+
const dnsHost = String(ch.dnsHost || opts?.dnsHost || dnsHostFromAltname || '');
|
|
200
|
+
|
|
201
|
+
const byAltname = altname ? presentedByAltname.get(altname) : null;
|
|
112
202
|
const dnsAuthorization =
|
|
113
203
|
ch.dnsAuthorization ||
|
|
114
204
|
opts?.dnsAuthorization ||
|
|
115
|
-
(dnsHost ? presentedByHost.get(dnsHost) : null)
|
|
205
|
+
(dnsHost ? presentedByHost.get(dnsHost) : null) ||
|
|
206
|
+
byAltname?.dnsAuthorization ||
|
|
207
|
+
null;
|
|
116
208
|
|
|
117
209
|
if (!dnsAuthorization) {
|
|
118
210
|
return null;
|
|
@@ -120,6 +212,8 @@ module.exports.create = function create(config = {}) {
|
|
|
120
212
|
|
|
121
213
|
return {
|
|
122
214
|
...(typeof ch === 'object' ? ch : {}),
|
|
215
|
+
...(altname ? { altname } : {}),
|
|
216
|
+
...(dnsHost ? { dnsHost } : {}),
|
|
123
217
|
dnsAuthorization
|
|
124
218
|
};
|
|
125
219
|
}
|