roster-server 2.2.5 → 2.2.7
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
|
@@ -130,6 +130,51 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
130
130
|
assert.strictEqual(payload.name, '_acme-challenge.sub');
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
it('prefers apex zone over www zone for www challenges', async () => {
|
|
134
|
+
const calls = [];
|
|
135
|
+
global.fetch = async (url, options = {}) => {
|
|
136
|
+
calls.push({ url, options });
|
|
137
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
status: 200,
|
|
141
|
+
json: async () => ({
|
|
142
|
+
data: [
|
|
143
|
+
{ id: 777, domain: 'www.tagnu.com' },
|
|
144
|
+
{ id: 888, domain: 'tagnu.com' }
|
|
145
|
+
]
|
|
146
|
+
})
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (url.endsWith('/domains/888/records?page_size=500')) {
|
|
150
|
+
return { ok: true, status: 200, json: async () => ({ data: [] }) };
|
|
151
|
+
}
|
|
152
|
+
if (url.endsWith('/domains/888/records') && options.method === 'POST') {
|
|
153
|
+
return { ok: true, status: 200, json: async () => ({ id: 333 }) };
|
|
154
|
+
}
|
|
155
|
+
return { ok: true, status: 204, json: async () => ({}) };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const challenger = wrapper.create({
|
|
159
|
+
provider: 'linode',
|
|
160
|
+
linodeApiKey: 'fake-token',
|
|
161
|
+
verifyDnsBeforeContinue: false,
|
|
162
|
+
propagationDelay: 0,
|
|
163
|
+
dryRunDelay: 0
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await challenger.set({
|
|
167
|
+
challenge: {
|
|
168
|
+
altname: 'www.tagnu.com',
|
|
169
|
+
dnsHost: '_greenlock-dryrun-abc.www.tagnu.com',
|
|
170
|
+
dnsAuthorization: 'www-token'
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
assert.ok(calls.some((c) => c.url.endsWith('/domains/888/records') && c.options.method === 'POST'));
|
|
175
|
+
assert.ok(!calls.some((c) => c.url.endsWith('/domains/777/records') && c.options.method === 'POST'));
|
|
176
|
+
});
|
|
177
|
+
|
|
133
178
|
it('falls back to manual when provider mode has no API key (default)', async () => {
|
|
134
179
|
const prevLinode = process.env.LINODE_API_KEY;
|
|
135
180
|
delete process.env.LINODE_API_KEY;
|
|
@@ -115,11 +115,12 @@ module.exports.create = function create(config = {}) {
|
|
|
115
115
|
? config.dnsResolvers.map((s) => String(s).trim()).filter(Boolean)
|
|
116
116
|
: parseResolvers(process.env.ROSTER_DNS_RESOLVERS);
|
|
117
117
|
const effectiveResolvers = configuredResolvers.length > 0 ? configuredResolvers : ['1.1.1.1', '8.8.8.8'];
|
|
118
|
-
const
|
|
118
|
+
const staticResolverClients = effectiveResolvers.map((server) => {
|
|
119
119
|
const resolver = new dns.Resolver();
|
|
120
120
|
resolver.setServers([server]);
|
|
121
121
|
return { server, resolver };
|
|
122
122
|
});
|
|
123
|
+
const authoritativeResolverClientsByHost = new Map();
|
|
123
124
|
const normalizeProvider = (value) => String(value || '').trim().toLowerCase();
|
|
124
125
|
const configuredProvider = normalizeProvider(
|
|
125
126
|
config.provider
|
|
@@ -149,11 +150,78 @@ module.exports.create = function create(config = {}) {
|
|
|
149
150
|
return candidate ? String(candidate).trim() : '';
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
function zoneCandidatesFromDnsHost(dnsHost) {
|
|
154
|
+
const normalized = String(dnsHost || '')
|
|
155
|
+
.replace(/^_acme-challenge\./, '')
|
|
156
|
+
.replace(/^_greenlock-[^.]+\./, '')
|
|
157
|
+
.replace(/\.$/, '')
|
|
158
|
+
.toLowerCase();
|
|
159
|
+
if (!normalized) return [];
|
|
160
|
+
|
|
161
|
+
const labels = normalized.split('.').filter(Boolean);
|
|
162
|
+
const candidates = [];
|
|
163
|
+
for (let i = 0; i <= labels.length - 2; i += 1) {
|
|
164
|
+
candidates.push(labels.slice(i).join('.'));
|
|
165
|
+
}
|
|
166
|
+
return candidates;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function resolveAuthoritativeResolverClients(dnsHost) {
|
|
170
|
+
const cached = authoritativeResolverClientsByHost.get(dnsHost);
|
|
171
|
+
if (cached) return cached;
|
|
172
|
+
|
|
173
|
+
const candidates = zoneCandidatesFromDnsHost(dnsHost);
|
|
174
|
+
for (const zone of candidates) {
|
|
175
|
+
let nsRecords = [];
|
|
176
|
+
try {
|
|
177
|
+
nsRecords = await dns.resolveNs(zone);
|
|
178
|
+
} catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(nsRecords) || nsRecords.length === 0) continue;
|
|
182
|
+
|
|
183
|
+
const clients = [];
|
|
184
|
+
for (const nsNameRaw of nsRecords) {
|
|
185
|
+
const nsName = String(nsNameRaw || '').replace(/\.$/, '');
|
|
186
|
+
if (!nsName) continue;
|
|
187
|
+
let ips = [];
|
|
188
|
+
try {
|
|
189
|
+
ips = await dns.resolve4(nsName);
|
|
190
|
+
} catch {}
|
|
191
|
+
if (ips.length === 0) {
|
|
192
|
+
try {
|
|
193
|
+
ips = await dns.resolve6(nsName);
|
|
194
|
+
} catch {}
|
|
195
|
+
}
|
|
196
|
+
for (const ip of ips) {
|
|
197
|
+
try {
|
|
198
|
+
const resolver = new dns.Resolver();
|
|
199
|
+
resolver.setServers([ip]);
|
|
200
|
+
clients.push({ server: `${nsName}/${ip}`, resolver });
|
|
201
|
+
} catch {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (clients.length > 0) {
|
|
206
|
+
authoritativeResolverClientsByHost.set(dnsHost, clients);
|
|
207
|
+
return clients;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
authoritativeResolverClientsByHost.set(dnsHost, []);
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
|
|
152
215
|
async function resolveTxtRecords(dnsHost) {
|
|
153
216
|
const records = [];
|
|
154
217
|
const errors = [];
|
|
218
|
+
const authClients = await resolveAuthoritativeResolverClients(dnsHost);
|
|
219
|
+
const resolverClients = [...staticResolverClients, ...authClients];
|
|
220
|
+
const seenServers = new Set();
|
|
155
221
|
|
|
156
222
|
for (const { server, resolver } of resolverClients) {
|
|
223
|
+
if (seenServers.has(server)) continue;
|
|
224
|
+
seenServers.add(server);
|
|
157
225
|
try {
|
|
158
226
|
const result = await resolver.resolveTxt(dnsHost);
|
|
159
227
|
if (Array.isArray(result)) {
|
|
@@ -212,6 +280,14 @@ module.exports.create = function create(config = {}) {
|
|
|
212
280
|
}
|
|
213
281
|
};
|
|
214
282
|
|
|
283
|
+
// Prefer apex zone when validating www.<domain> challenges so records are
|
|
284
|
+
// created in the commonly delegated parent zone (e.g. tagnu.com).
|
|
285
|
+
if (altname) {
|
|
286
|
+
const normalizedAltname = String(altname).replace(/^\*\./, '').replace(/\.$/, '').toLowerCase();
|
|
287
|
+
if (normalizedAltname.startsWith('www.')) {
|
|
288
|
+
add(normalizedAltname.slice(4));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
215
291
|
if (dnsHost) {
|
|
216
292
|
const normalizedDnsHost = String(dnsHost).replace(/^_acme-challenge\./, '').replace(/^_greenlock-[^.]+\./, '');
|
|
217
293
|
add(normalizedDnsHost);
|