pending-dns 1.2.4 → 1.3.0
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/.github/codeql/codeql-config.yml +11 -0
- package/.github/workflows/codeql.yml +52 -0
- package/.github/workflows/deploy.yml +16 -3
- package/.github/workflows/release.yaml +43 -0
- package/.github/workflows/test.yml +75 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +97 -0
- package/README.md +28 -5
- package/SECURITY.md +88 -0
- package/SECURITY.txt +27 -0
- package/bin/pending-dns.js +1 -1
- package/config/default.toml +13 -0
- package/config/test.toml +25 -0
- package/eslint.config.js +38 -0
- package/lib/api-server.js +13 -6
- package/lib/cached-resolver.js +5 -3
- package/lib/certs.js +11 -4
- package/lib/dns-handler.js +46 -14
- package/lib/dns-server.js +30 -18
- package/lib/dns-tcp-server.js +1 -1
- package/lib/dns-udp-server.js +1 -1
- package/lib/logger.js +3 -0
- package/lib/public-server.js +20 -2
- package/lib/sentry.js +72 -0
- package/lib/tools.js +1 -1
- package/lib/zone-store.js +4 -4
- package/package.json +43 -33
- package/release-please-config.json +13 -0
- package/server.js +5 -24
- package/systemd/pending-dns.service +4 -4
- package/test/api.test.js +139 -0
- package/test/cached-resolver.test.js +57 -0
- package/test/certs.test.js +34 -0
- package/test/dns-handler.test.js +140 -0
- package/test/dns-server.test.js +69 -0
- package/test/helpers.js +25 -0
- package/test/sentry.test.js +21 -0
- package/test/tools.test.js +48 -0
- package/test/zone-store.test.js +209 -0
- package/workers/api.js +3 -1
- package/workers/dns.js +2 -24
- package/workers/health.js +3 -26
- package/workers/public.js +3 -25
- package/.eslintrc +0 -14
- package/Gruntfile.js +0 -16
package/lib/api-server.js
CHANGED
|
@@ -8,7 +8,7 @@ const Inert = require('@hapi/inert');
|
|
|
8
8
|
const Vision = require('@hapi/vision');
|
|
9
9
|
const HapiSwagger = require('hapi-swagger');
|
|
10
10
|
const packageData = require('../package.json');
|
|
11
|
-
const Joi = require('
|
|
11
|
+
const Joi = require('joi');
|
|
12
12
|
const logger = require('./logger').child({ component: 'api-server' });
|
|
13
13
|
const { zoneStore, allowedTypes, allowedTags } = require('./zone-store');
|
|
14
14
|
const { getCertificate } = require('./certs');
|
|
@@ -192,7 +192,7 @@ const recordScheme = Joi.object({
|
|
|
192
192
|
.description('Certificate authority domain for CAA')
|
|
193
193
|
.label('CADomain'),
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
tag: Joi.any()
|
|
196
196
|
.example('issue')
|
|
197
197
|
.when('type', {
|
|
198
198
|
is: 'CAA',
|
|
@@ -207,7 +207,7 @@ const recordScheme = Joi.object({
|
|
|
207
207
|
.example(0)
|
|
208
208
|
.when('type', {
|
|
209
209
|
is: 'CAA',
|
|
210
|
-
then: Joi.
|
|
210
|
+
then: Joi.number().integer().min(0).max(255).default(0)
|
|
211
211
|
})
|
|
212
212
|
.description('Certificate authority flags for CAA')
|
|
213
213
|
.label('Flags'),
|
|
@@ -273,7 +273,7 @@ const failAction = async (request, h, err) => {
|
|
|
273
273
|
throw error;
|
|
274
274
|
};
|
|
275
275
|
|
|
276
|
-
const
|
|
276
|
+
const createServer = async () => {
|
|
277
277
|
const server = Hapi.server({
|
|
278
278
|
port: (process.env.API_PORT && Number(process.env.API_PORT)) || config.api.port,
|
|
279
279
|
host: process.env.API_HOST || config.api.host
|
|
@@ -478,8 +478,8 @@ const init = async () => {
|
|
|
478
478
|
throw new Error('Unknown type');
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
-
let
|
|
482
|
-
return { zone: request.params.zone,
|
|
481
|
+
let record = await zoneStore.update(request.params.zone, request.params.record, request.payload.subdomain, request.payload.type, value);
|
|
482
|
+
return { zone: request.params.zone, record };
|
|
483
483
|
} catch (err) {
|
|
484
484
|
if (Boom.isBoom(err)) {
|
|
485
485
|
throw err;
|
|
@@ -639,7 +639,14 @@ const init = async () => {
|
|
|
639
639
|
}
|
|
640
640
|
});
|
|
641
641
|
|
|
642
|
+
return server;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const init = async () => {
|
|
646
|
+
const server = await createServer();
|
|
642
647
|
await server.start();
|
|
648
|
+
return server;
|
|
643
649
|
};
|
|
644
650
|
|
|
645
651
|
module.exports = init;
|
|
652
|
+
module.exports.createServer = createServer;
|
package/lib/cached-resolver.js
CHANGED
|
@@ -32,12 +32,13 @@ function formatResult(record) {
|
|
|
32
32
|
* @param {Object} [options]
|
|
33
33
|
* @param {Number} [options.minTtl=10min] Cache timeout for successful resolving operations. If cached response is older than this value then resolving is retried. If resolving fails then cached value is used.
|
|
34
34
|
* @param {Number} [options.maxTtl=8h] Time after cached data of a successful resolving is permanently deleted
|
|
35
|
-
* @param {Number} [options.
|
|
35
|
+
* @param {Number} [options.errorMinTtl=1min] Cache timeout for errored resolving operations. If cached response is older than this value then resolving is retried
|
|
36
|
+
* @param {Number} [options.errorMaxTtl=1h] Time after cached error data is permanently deleted
|
|
36
37
|
* @returns {Array|Boolean} Resolve response or `false` if query failed for whatever reason
|
|
37
38
|
*/
|
|
38
39
|
const cachedResolver = async (target, type, options) => {
|
|
39
40
|
try {
|
|
40
|
-
target = punycode.toASCII(target.trim().toLowerCase()
|
|
41
|
+
target = punycode.toASCII(target.trim().toLowerCase());
|
|
41
42
|
} catch (err) {
|
|
42
43
|
return false;
|
|
43
44
|
}
|
|
@@ -120,12 +121,13 @@ const cachedResolver = async (target, type, options) => {
|
|
|
120
121
|
.set(
|
|
121
122
|
cacheKey,
|
|
122
123
|
JSON.stringify({
|
|
124
|
+
expires: Date.now() + options.errorMinTtl,
|
|
123
125
|
data: false,
|
|
124
126
|
error: err.message,
|
|
125
127
|
code: err.code || err.errno
|
|
126
128
|
})
|
|
127
129
|
)
|
|
128
|
-
.expire(cacheKey, Math.round(options.
|
|
130
|
+
.expire(cacheKey, Math.round(options.errorMaxTtl / 1000))
|
|
129
131
|
.exec();
|
|
130
132
|
throw err;
|
|
131
133
|
}
|
package/lib/certs.js
CHANGED
|
@@ -9,7 +9,6 @@ const crypto = require('crypto');
|
|
|
9
9
|
const { checkNSStatus, normalizeDomain } = require('./tools');
|
|
10
10
|
const ACME = require('@root/acme');
|
|
11
11
|
const { pem2jwk } = require('pem-jwk');
|
|
12
|
-
const NodeRSA = require('node-rsa');
|
|
13
12
|
const logger = require('./logger').child({ component: 'certs' });
|
|
14
13
|
const CSR = require('@root/csr');
|
|
15
14
|
const { Certificate } = require('@fidm/x509');
|
|
@@ -17,6 +16,8 @@ const { Resolver } = require('dns').promises;
|
|
|
17
16
|
const Lock = require('ioredfour');
|
|
18
17
|
const util = require('util');
|
|
19
18
|
|
|
19
|
+
const generateKeyPairAsync = util.promisify(crypto.generateKeyPair);
|
|
20
|
+
|
|
20
21
|
const certLock = new Lock({
|
|
21
22
|
redis: db.redisWrite,
|
|
22
23
|
namespace: 'd:lock:'
|
|
@@ -78,9 +79,12 @@ const ensureAcme = async () => {
|
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
const generateKey = async bits => {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const { privateKey } = await generateKeyPairAsync('rsa', {
|
|
83
|
+
modulusLength: bits || 2048,
|
|
84
|
+
publicExponent: 65537,
|
|
85
|
+
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
|
86
|
+
});
|
|
87
|
+
return privateKey;
|
|
84
88
|
};
|
|
85
89
|
|
|
86
90
|
class AcmeDNSPlugin {
|
|
@@ -472,3 +476,6 @@ module.exports = {
|
|
|
472
476
|
getCertificate,
|
|
473
477
|
loadCertificate
|
|
474
478
|
};
|
|
479
|
+
|
|
480
|
+
// Exposed for unit testing
|
|
481
|
+
module.exports.testables = { generateKey };
|
package/lib/dns-handler.js
CHANGED
|
@@ -8,15 +8,16 @@ const cachedResolver = require('./cached-resolver');
|
|
|
8
8
|
const ipaddr = require('ipaddr.js');
|
|
9
9
|
const logger = require('./logger').child({ component: 'dns-handler' });
|
|
10
10
|
const { normalizeDomain } = require('./tools');
|
|
11
|
-
const {
|
|
11
|
+
const { randomUUID } = require('crypto');
|
|
12
12
|
|
|
13
|
-
// Split
|
|
13
|
+
// Split a TXT value into DNS character-strings (max 255 bytes each).
|
|
14
|
+
// Always returns an array, which is what the dns2 packet builder expects.
|
|
14
15
|
const formatTXTData = data => {
|
|
15
16
|
data = (data || '').toString();
|
|
16
|
-
if (data.length
|
|
17
|
-
return
|
|
17
|
+
if (!data.length) {
|
|
18
|
+
return [''];
|
|
18
19
|
}
|
|
19
|
-
return Array.from(data.match(/.{1,
|
|
20
|
+
return Array.from(data.match(/.{1,255}/g));
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
// Helps to convert DNS type integer into a string (0x01 -> 'A')
|
|
@@ -91,7 +92,7 @@ const processQuestion = async (response, question, domain, depth) => {
|
|
|
91
92
|
if (records && records.length > 1) {
|
|
92
93
|
switch (type) {
|
|
93
94
|
case 'A':
|
|
94
|
-
case '
|
|
95
|
+
case 'AAAA':
|
|
95
96
|
// randomize A/AAAA records
|
|
96
97
|
records = shuffle(filterUnhealthy(records));
|
|
97
98
|
break;
|
|
@@ -164,6 +165,31 @@ const processQuestion = async (response, question, domain, depth) => {
|
|
|
164
165
|
response.answers.push(entry);
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
// Chaos responses
|
|
169
|
+
if (
|
|
170
|
+
question.class === dns2.Packet.CLASS.CH &&
|
|
171
|
+
questionTypeStr === 'TXT' &&
|
|
172
|
+
question.class === dns2.Packet.CLASS.CH &&
|
|
173
|
+
config.chaos &&
|
|
174
|
+
domain in config.chaos
|
|
175
|
+
) {
|
|
176
|
+
for (let entry of [].concat(config.chaos[domain] || [])) {
|
|
177
|
+
response.answers.push({
|
|
178
|
+
name: domain,
|
|
179
|
+
type: 'TXT',
|
|
180
|
+
data: formatTXTData(entry),
|
|
181
|
+
ttl: 0,
|
|
182
|
+
class: dns2.Packet.CLASS.CH
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
response.authorities.push({
|
|
186
|
+
name: domain,
|
|
187
|
+
type: 'NS',
|
|
188
|
+
ns: domain,
|
|
189
|
+
ttl: 0,
|
|
190
|
+
class: dns2.Packet.CLASS.CH
|
|
191
|
+
});
|
|
192
|
+
}
|
|
167
193
|
return;
|
|
168
194
|
}
|
|
169
195
|
|
|
@@ -250,7 +276,8 @@ const processQuestion = async (response, question, domain, depth) => {
|
|
|
250
276
|
try {
|
|
251
277
|
entry.address = ipaddr.parse(value[0]).toNormalizedString();
|
|
252
278
|
} catch (err) {
|
|
253
|
-
|
|
279
|
+
// skip just this malformed record, keep processing the rest
|
|
280
|
+
continue;
|
|
254
281
|
}
|
|
255
282
|
break;
|
|
256
283
|
|
|
@@ -335,7 +362,7 @@ const processQuestion = async (response, question, domain, depth) => {
|
|
|
335
362
|
const dnsHandler = async request => {
|
|
336
363
|
let startTime = Date.now();
|
|
337
364
|
const response = new dns2.Packet(request);
|
|
338
|
-
request._id = response._id =
|
|
365
|
+
request._id = response._id = randomUUID();
|
|
339
366
|
|
|
340
367
|
response.header.qr = 1;
|
|
341
368
|
response.header.aa = 1;
|
|
@@ -366,14 +393,19 @@ const dnsHandler = async request => {
|
|
|
366
393
|
});
|
|
367
394
|
|
|
368
395
|
// normalize answers for the DNS library
|
|
369
|
-
response.answers.
|
|
370
|
-
answer
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
for (let responseType of [response.answers, response.authorities]) {
|
|
397
|
+
responseType.forEach(answer => {
|
|
398
|
+
answer.type = dns2.Packet.TYPE[answer.type];
|
|
399
|
+
answer.class = typeof answer.class === 'number' ? answer.class : dns2.Packet.CLASS.IN;
|
|
400
|
+
answer.name = punycode.toASCII(answer.name);
|
|
401
|
+
answer.ttl = typeof answer.ttl === 'number' && answer.ttl >= 0 ? answer.ttl : config.dns.ttl;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
375
404
|
|
|
376
405
|
return response;
|
|
377
406
|
};
|
|
378
407
|
|
|
379
408
|
module.exports = dnsHandler;
|
|
409
|
+
|
|
410
|
+
// Exposed for unit testing
|
|
411
|
+
module.exports.testables = { formatTXTData, shuffle, filterUnhealthy, reversedTypes, processQuestion };
|
package/lib/dns-server.js
CHANGED
|
@@ -19,7 +19,7 @@ const SUPPORTED_TYPES = new Set(
|
|
|
19
19
|
|
|
20
20
|
const init = async () => {
|
|
21
21
|
// create UDP server
|
|
22
|
-
createDNSUdpServer((request, send) => {
|
|
22
|
+
const udpServer = createDNSUdpServer((request, send) => {
|
|
23
23
|
// filter out unsupported requests (eg. EDNS)
|
|
24
24
|
if (request.additionals && request.additionals.length) {
|
|
25
25
|
request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
|
|
@@ -38,16 +38,14 @@ const init = async () => {
|
|
|
38
38
|
}
|
|
39
39
|
logger.error({ msg: 'Failed to send DNS response', protocol: 'udp', err });
|
|
40
40
|
});
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
logger.error({ msg: 'DNS server error', protocol: 'udp', err });
|
|
47
|
-
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
udpServer.on('error', err => {
|
|
44
|
+
logger.error({ msg: 'DNS server error', protocol: 'udp', err });
|
|
45
|
+
});
|
|
48
46
|
|
|
49
47
|
// create TCP server
|
|
50
|
-
createDNSTcpServer((request, send) => {
|
|
48
|
+
const tcpServer = createDNSTcpServer((request, send) => {
|
|
51
49
|
// filter out unsupported requests (eg. EDNS)
|
|
52
50
|
if (request.additionals && request.additionals.length) {
|
|
53
51
|
request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
|
|
@@ -58,17 +56,31 @@ const init = async () => {
|
|
|
58
56
|
.catch(err => {
|
|
59
57
|
logger.error({ msg: 'Failed to send DNS response', protocol: 'tcp', err });
|
|
60
58
|
});
|
|
61
|
-
})
|
|
62
|
-
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
tcpServer.on('error', err => {
|
|
62
|
+
let method = 'error';
|
|
63
|
+
if (err && ['ECONNRESET'].includes(err.code)) {
|
|
64
|
+
method = 'trace';
|
|
65
|
+
}
|
|
66
|
+
logger[method]({ msg: 'DNS server error', protocol: 'tcp', err });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await new Promise(resolve => {
|
|
70
|
+
udpServer.listen(config.dns.port, config.dns.host, () => {
|
|
71
|
+
logger.info({ msg: 'DNS server listening', protocol: 'udp', host: config.dns.host, port: config.dns.port });
|
|
72
|
+
resolve();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await new Promise(resolve => {
|
|
77
|
+
tcpServer.listen(config.dns.port, config.dns.host, () => {
|
|
63
78
|
logger.info({ msg: 'DNS server listening', protocol: 'tcp', host: config.dns.host, port: config.dns.port });
|
|
64
|
-
|
|
65
|
-
.on('error', err => {
|
|
66
|
-
let method = 'error';
|
|
67
|
-
if (err && ['ECONNRESET'].includes(err.code)) {
|
|
68
|
-
method = 'trace';
|
|
69
|
-
}
|
|
70
|
-
logger[method]({ msg: 'DNS server error', protocol: 'tcp', err });
|
|
79
|
+
resolve();
|
|
71
80
|
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { udpServer, tcpServer };
|
|
72
84
|
};
|
|
73
85
|
|
|
74
86
|
module.exports = init;
|
package/lib/dns-tcp-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const net = require('net');
|
|
4
4
|
const EventEmitter = require('events');
|
|
5
|
-
const Packet = require('dns2
|
|
5
|
+
const { Packet } = require('dns2');
|
|
6
6
|
const logger = require('./logger').child({ component: 'dns-tcp-server' });
|
|
7
7
|
|
|
8
8
|
class DNSTcpServer extends EventEmitter {
|
package/lib/dns-udp-server.js
CHANGED
package/lib/logger.js
CHANGED
|
@@ -11,6 +11,9 @@ if (threadId) {
|
|
|
11
11
|
logger = logger.child({ tid: threadId });
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// No-op by default; lib/sentry.js overrides this with a real reporter when a DSN is configured
|
|
15
|
+
logger.notifyError = () => false;
|
|
16
|
+
|
|
14
17
|
process.on('uncaughtException', err => {
|
|
15
18
|
logger.fatal({
|
|
16
19
|
msg: 'uncaughtException',
|
package/lib/public-server.js
CHANGED
|
@@ -254,7 +254,16 @@ const setupHttps = () =>
|
|
|
254
254
|
},
|
|
255
255
|
(req, res) => {
|
|
256
256
|
req.proto = 'https';
|
|
257
|
-
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
middleware(req, res);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
logger.error({ msg: 'Failed to process request', err });
|
|
262
|
+
res.statusCode = 500;
|
|
263
|
+
res.setHeader('Content-Type', 'text/html');
|
|
264
|
+
return res.end(errors.error500({}));
|
|
265
|
+
}
|
|
266
|
+
|
|
258
267
|
handler(req, res).catch(err => {
|
|
259
268
|
res.statusCode = 500;
|
|
260
269
|
res.setHeader('Content-Type', 'text/html');
|
|
@@ -334,7 +343,16 @@ const setupHttp = () =>
|
|
|
334
343
|
new Promise((resolve, reject) => {
|
|
335
344
|
const server = http.createServer((req, res) => {
|
|
336
345
|
req.proto = 'http';
|
|
337
|
-
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
middleware(req, res);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
logger.error({ msg: 'Failed to process request', err });
|
|
351
|
+
res.statusCode = 500;
|
|
352
|
+
res.setHeader('Content-Type', 'text/html');
|
|
353
|
+
return res.end(errors.error500({}));
|
|
354
|
+
}
|
|
355
|
+
|
|
338
356
|
handler(req, res).catch(err => {
|
|
339
357
|
res.statusCode = 500;
|
|
340
358
|
res.setHeader('Content-Type', 'text/html');
|
package/lib/sentry.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* eslint global-require: 0 */
|
|
4
|
+
|
|
5
|
+
const config = require('wild-config');
|
|
6
|
+
const packageData = require('../package.json');
|
|
7
|
+
const logger = require('./logger');
|
|
8
|
+
|
|
9
|
+
// Initialize Sentry error tracking. With no DSN configured, error reporting stays
|
|
10
|
+
// disabled and logger.notifyError keeps its no-op default from lib/logger.js.
|
|
11
|
+
function initSentry(worker) {
|
|
12
|
+
// The SENTRY_DSN environment variable overrides the configured value, otherwise
|
|
13
|
+
// fall back to the wild-config value. An empty DSN disables error reporting.
|
|
14
|
+
const dsn = (process.env.SENTRY_DSN || (config.sentry && config.sentry.dsn) || '').trim();
|
|
15
|
+
if (!dsn) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// require lazily, the SDK loads several hundred modules in every worker,
|
|
20
|
+
// so only pay that cost when error tracking is actually enabled
|
|
21
|
+
const Sentry = require('@sentry/node');
|
|
22
|
+
|
|
23
|
+
Sentry.init({
|
|
24
|
+
dsn,
|
|
25
|
+
release: packageData.version,
|
|
26
|
+
environment: process.env.NODE_ENV || 'development',
|
|
27
|
+
// Error capture only: skip the OpenTelemetry setup and the default
|
|
28
|
+
// integrations that patch http/fetch/console on hot paths. The uncaught
|
|
29
|
+
// exception / unhandled rejection integrations are added back explicitly
|
|
30
|
+
// so crashes are still reported (Bugsnag's autoDetectErrors did this).
|
|
31
|
+
skipOpenTelemetrySetup: true,
|
|
32
|
+
defaultIntegrations: false,
|
|
33
|
+
integrations: [
|
|
34
|
+
Sentry.eventFiltersIntegration(),
|
|
35
|
+
Sentry.functionToStringIntegration(),
|
|
36
|
+
Sentry.linkedErrorsIntegration(),
|
|
37
|
+
Sentry.contextLinesIntegration(),
|
|
38
|
+
Sentry.nodeContextIntegration(),
|
|
39
|
+
Sentry.modulesIntegration(),
|
|
40
|
+
// captures, flushes and exits the worker so the supervisor restarts it
|
|
41
|
+
Sentry.onUncaughtExceptionIntegration(),
|
|
42
|
+
// captures and warns, but does not exit (matches the previous behaviour)
|
|
43
|
+
Sentry.onUnhandledRejectionIntegration({ mode: 'warn' })
|
|
44
|
+
],
|
|
45
|
+
initialScope: {
|
|
46
|
+
tags: { worker, app: packageData.name }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Signals to the worker bootstraps that an error reporter with its own
|
|
51
|
+
// crash handler is active, so closeProcess() should let Sentry flush and
|
|
52
|
+
// exit instead of exiting immediately (which would drop the in-flight event).
|
|
53
|
+
logger.errorReportingEnabled = true;
|
|
54
|
+
|
|
55
|
+
logger.notifyError = (err, opts) => {
|
|
56
|
+
let captureContext = {};
|
|
57
|
+
if (opts && opts.level) {
|
|
58
|
+
captureContext.level = opts.level;
|
|
59
|
+
}
|
|
60
|
+
if (opts && opts.context) {
|
|
61
|
+
captureContext.tags = { context: opts.context };
|
|
62
|
+
}
|
|
63
|
+
if (opts && opts.meta && Object.keys(opts.meta).length) {
|
|
64
|
+
captureContext.contexts = { error: opts.meta };
|
|
65
|
+
}
|
|
66
|
+
Sentry.captureException(err, captureContext);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
logger.info({ msg: 'Enabled Sentry error reporting', worker });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { initSentry };
|
package/lib/tools.js
CHANGED
package/lib/zone-store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const punycode = require('punycode/');
|
|
4
|
-
const
|
|
4
|
+
const { nanoid } = require('nanoid');
|
|
5
5
|
const db = require('./db');
|
|
6
6
|
const logger = require('./logger').child({ component: 'zone-store' });
|
|
7
7
|
const { normalizeDomain } = require('./tools');
|
|
@@ -70,8 +70,8 @@ class ZoneStore {
|
|
|
70
70
|
let recordKey = `d:${name}:r:${type}`;
|
|
71
71
|
let res = await this.db.redisWrite.multi().hdel(recordKey, hid).exists(recordKey).exec();
|
|
72
72
|
|
|
73
|
-
if (res[res.length - 1] && !res[res.length - 1][0] && !res[res.length - 1][
|
|
74
|
-
// key
|
|
73
|
+
if (res[res.length - 1] && !res[res.length - 1][0] && !res[res.length - 1][1]) {
|
|
74
|
+
// the record key no longer exists (no entries left), remove it from the zone
|
|
75
75
|
await this.db.redisWrite.srem(`d:${zone}:z`, recordKey);
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -356,7 +356,7 @@ class ZoneStore {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
let recordKey = `d:${name}:r:${type}`;
|
|
359
|
-
let hid =
|
|
359
|
+
let hid = nanoid();
|
|
360
360
|
|
|
361
361
|
let id = this.getFullId(name, type, hid);
|
|
362
362
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pending-dns",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Lightweight API driven DNS server",
|
|
5
|
+
"productTitle": "PendingDNS",
|
|
5
6
|
"main": "index.js",
|
|
7
|
+
"private": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
6
11
|
"scripts": {
|
|
7
12
|
"start": "node server.js",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"build-
|
|
11
|
-
"build-dist": "
|
|
12
|
-
"
|
|
13
|
+
"lint": "eslint .",
|
|
14
|
+
"test": "NODE_ENV=test node --test --test-force-exit --test-concurrency=1 test/*.test.js",
|
|
15
|
+
"build-source": "rm -rf node_modules && npm install && npm run licenses && rm -rf node_modules && npm ci --omit=dev",
|
|
16
|
+
"build-dist-fast": "pkg --debug package.json && npm install",
|
|
17
|
+
"build-dist": "pkg --compress Brotli package.json && npm install",
|
|
18
|
+
"licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt",
|
|
19
|
+
"update": "rm -rf node_modules package-lock.json && ncu -u && npm install"
|
|
13
20
|
},
|
|
14
21
|
"repository": {
|
|
15
22
|
"type": "git",
|
|
@@ -28,55 +35,58 @@
|
|
|
28
35
|
},
|
|
29
36
|
"homepage": "https://github.com/postalsys/pending-dns#readme",
|
|
30
37
|
"devDependencies": {
|
|
31
|
-
"eslint": "
|
|
38
|
+
"@eslint/eslintrc": "3.3.5",
|
|
39
|
+
"@eslint/js": "9.39.4",
|
|
40
|
+
"eslint": "9.39.4",
|
|
32
41
|
"eslint-config-nodemailer": "1.2.0",
|
|
33
|
-
"eslint-config-prettier": "
|
|
34
|
-
"
|
|
35
|
-
"grunt-cli": "1.4.3",
|
|
36
|
-
"grunt-eslint": "24.3.0",
|
|
37
|
-
"license-report": "6.4.0"
|
|
42
|
+
"eslint-config-prettier": "10.1.8",
|
|
43
|
+
"license-report": "6.8.5"
|
|
38
44
|
},
|
|
39
45
|
"dependencies": {
|
|
40
|
-
"@bugsnag/js": "7.21.0",
|
|
41
46
|
"@fidm/x509": "1.2.1",
|
|
42
47
|
"@hapi/boom": "10.0.1",
|
|
43
|
-
"@hapi/hapi": "21.
|
|
44
|
-
"@hapi/inert": "7.1.
|
|
45
|
-
"@hapi/joi": "17.1.1",
|
|
48
|
+
"@hapi/hapi": "21.4.9",
|
|
49
|
+
"@hapi/inert": "7.1.2",
|
|
46
50
|
"@hapi/vision": "7.0.3",
|
|
47
51
|
"@root/acme": "3.1.0",
|
|
48
52
|
"@root/csr": "0.8.1",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"hapi-
|
|
53
|
+
"@sentry/node": "10.57.0",
|
|
54
|
+
"dns2": "3.0.0",
|
|
55
|
+
"handlebars": "4.7.9",
|
|
56
|
+
"hapi-pino": "13.0.0",
|
|
57
|
+
"hapi-swagger": "17.3.2",
|
|
53
58
|
"http-proxy": "1.18.1",
|
|
54
|
-
"ioredfour": "1.
|
|
55
|
-
"ioredis": "5.
|
|
56
|
-
"ipaddr.js": "2.
|
|
59
|
+
"ioredfour": "1.4.1",
|
|
60
|
+
"ioredis": "5.11.1",
|
|
61
|
+
"ipaddr.js": "2.4.0",
|
|
62
|
+
"joi": "17.13.4",
|
|
57
63
|
"minimist": "1.2.8",
|
|
58
|
-
"
|
|
64
|
+
"nanoid": "3.3.12",
|
|
59
65
|
"pem-jwk": "2.0.0",
|
|
60
|
-
"pino": "
|
|
61
|
-
"punycode": "2.3.
|
|
62
|
-
"
|
|
63
|
-
"uuid": "9.0.0",
|
|
64
|
-
"wild-config": "1.7.0"
|
|
66
|
+
"pino": "10.3.1",
|
|
67
|
+
"punycode": "2.3.1",
|
|
68
|
+
"wild-config": "1.7.1"
|
|
65
69
|
},
|
|
66
70
|
"bin": {
|
|
67
71
|
"pending-dns": "bin/pending-dns.js"
|
|
68
72
|
},
|
|
69
73
|
"pkg": {
|
|
74
|
+
"scripts": [
|
|
75
|
+
"lib/**/*.js",
|
|
76
|
+
"workers/**/*.js"
|
|
77
|
+
],
|
|
70
78
|
"assets": [
|
|
79
|
+
"lib/lua/**/*",
|
|
80
|
+
"config/*.pem",
|
|
71
81
|
"licenses.txt",
|
|
72
82
|
"LICENSE.txt",
|
|
73
83
|
"help.txt"
|
|
74
84
|
],
|
|
75
85
|
"targets": [
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
86
|
+
"node24-linux-x64",
|
|
87
|
+
"node24-macos-x64",
|
|
88
|
+
"node24-macos-arm64",
|
|
89
|
+
"node24-win-x64"
|
|
80
90
|
],
|
|
81
91
|
"outputPath": "ee-dist"
|
|
82
92
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
3
|
+
"packages": {
|
|
4
|
+
".": {
|
|
5
|
+
"release-type": "node",
|
|
6
|
+
"package-name": "pending-dns",
|
|
7
|
+
"changelog-path": "CHANGELOG.md",
|
|
8
|
+
"bump-minor-pre-major": false,
|
|
9
|
+
"draft": false,
|
|
10
|
+
"prerelease": false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|