pending-dns 1.1.1 → 1.2.1

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.
@@ -0,0 +1,37 @@
1
+ on:
2
+ push:
3
+ branches:
4
+ - master
5
+
6
+ name: Deploy instance
7
+
8
+ jobs:
9
+ deploy:
10
+ name: Deploy
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v2
16
+
17
+ - name: Install SSH key
18
+ uses: shimataro/ssh-key-action@v2
19
+ with:
20
+ key: ${{ secrets.SSH_KEY }}
21
+ known_hosts: ${{ secrets.KNOWN_HOSTS }}
22
+
23
+ - name: Deploy to server
24
+ env:
25
+ TARGET_HOST_01: dns-01.emailengine.app
26
+ TARGET_HOST_02: dns-02.emailengine.app
27
+ NODE_ENV: production
28
+ SERVICE_NAME: pending-dns
29
+ id: deploy
30
+ run: |
31
+ echo $GITHUB_SHA > commit.txt
32
+ npm install --production
33
+ tar czf /tmp/${SERVICE_NAME}.tar.gz --exclude .git .
34
+ scp /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_01}:
35
+ scp /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_02}:
36
+ ssh deploy@$TARGET_HOST_01 "/opt/deploy.sh ${SERVICE_NAME}"
37
+ ssh deploy@$TARGET_HOST_02 "/opt/deploy.sh ${SERVICE_NAME}"
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PendingDNS
2
2
 
3
- Lightweight API driven Authoritative DNS server. Extracted from [Project Pending](https://projectpending.com/).
3
+ Lightweight API driven Authoritative DNS server.
4
4
 
5
5
  ## Features
6
6
 
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /* eslint global-require: 0 */
3
+ 'use strict';
4
+
5
+ const packageData = require('../package.json');
6
+ const fs = require('fs');
7
+ const pathlib = require('path');
8
+ const argv = require('minimist')(process.argv.slice(2));
9
+
10
+ function run() {
11
+ let cmd = ((argv._ && argv._[0]) || '').toLowerCase();
12
+ if (!cmd) {
13
+ if (argv.version || argv.v) {
14
+ cmd = 'version';
15
+ }
16
+
17
+ if (argv.help || argv.h) {
18
+ cmd = 'help';
19
+ }
20
+ }
21
+
22
+ switch (cmd) {
23
+ case 'help':
24
+ // Show version
25
+ fs.readFile(pathlib.join(__dirname, '..', 'help.txt'), (err, helpText) => {
26
+ if (err) {
27
+ console.error('Failed to load help information');
28
+ console.error(err);
29
+ return process.exit(1);
30
+ }
31
+ console.error(helpText.toString().trim());
32
+ console.error('');
33
+ process.exit();
34
+ });
35
+ break;
36
+
37
+ case 'version':
38
+ // Show version
39
+ console.log(`EmailEngine v${packageData.version} (${packageData.license})`);
40
+ return process.exit();
41
+
42
+ default:
43
+ // run normally
44
+ require('../server');
45
+ break;
46
+ }
47
+ }
48
+
49
+ run();
@@ -1,143 +1,143 @@
1
1
 
2
2
  [log]
3
- level = "trace"
3
+ level = "trace"
4
4
 
5
5
  [dbs]
6
6
 
7
- # By default all redis commands are sent against the same instance
8
- redis = "redis://127.0.0.1:6379/2"
7
+ # By default all redis commands are sent against the same instance
8
+ redis = "redis://127.0.0.1:6379/2"
9
9
 
10
- # Alternatively you can separate write and read tasks
11
- # by writing to master and reading from a closer replica
10
+ # Alternatively you can separate write and read tasks
11
+ # by writing to master and reading from a closer replica
12
12
 
13
- # Redis master
14
- #redisRead = "redis://127.0.0.1:6379/2"
13
+ # Redis master
14
+ #redisRead = "redis://127.0.0.1:6379/2"
15
15
 
16
- # Redis replica, preferrably localhost for fastest responses
17
- #redisWrite = "redis://127.0.0.1:6379/2"
16
+ # Redis replica, preferrably localhost for fastest responses
17
+ #redisWrite = "redis://127.0.0.1:6379/2"
18
18
 
19
19
  [api]
20
- # If enabled=false then API server is not started and configuration is not used
21
- enabled = true
22
- workers = 2
20
+ # If enabled=false then API server is not started and configuration is not used
21
+ enabled = true
22
+ workers = 1
23
23
 
24
- # You want to keep this local or firewalled otherwise anyone would be able to change DNS records
25
- # Do not use ports 80 or 443 as these are needed for the redirect interface
26
- port = 5080
27
- host = "127.0.0.1"
24
+ # You want to keep this local or firewalled otherwise anyone would be able to change DNS records
25
+ # Do not use ports 80 or 443 as these are needed for the redirect interface
26
+ port = 5080
27
+ host = "127.0.0.1"
28
28
 
29
29
  [dns]
30
- # If enabled=false then DNS server is not started and configuration is not used
31
- enabled = true
32
- workers = 2
30
+ # If enabled=false then DNS server is not started and configuration is not used
31
+ enabled = true
32
+ workers = 2
33
33
 
34
- # Default TTL value for all records
35
- ttl = 300 # 5 min
34
+ # Default TTL value for all records
35
+ ttl = 300 # 5 min
36
36
 
37
- # Use 53 on production.
38
- # This port is used both for TCP and UDP so make sure both are allowed by the firewall
39
- # ufw allow 53/tcp
40
- # ufw allow 53/udp
41
- port = 5053
37
+ # Use 53 on production.
38
+ # This port is used both for TCP and UDP so make sure both are allowed by the firewall
39
+ # ufw allow 53/tcp
40
+ # ufw allow 53/udp
41
+ port = 5053
42
42
 
43
- # In most cases you have to set actual interface IP address here
44
- # instead of using 0.0.0.0 as there might be already some other DNS
45
- # handlers running (eg. SystemD stub resolver) on some local address
46
- host = "127.0.0.1"
43
+ # In most cases you have to set actual interface IP address here
44
+ # instead of using 0.0.0.0 as there might be already some other DNS
45
+ # handlers running (eg. SystemD stub resolver) on some local address
46
+ host = "127.0.0.1"
47
47
 
48
48
  # List of Name Servers running this system
49
49
  # 1) ACME certificates are created only for domains that have NS records set to these values
50
50
  # 2) These values are reported as NS records for all domains
51
51
  [[ns]]
52
- # First NS in the list is also reported as the master in SOA record
53
- domain = "testdns01.pendingdns.com"
54
- ip = "188.165.168.22"
52
+ # First NS in the list is also reported as the master in SOA record
53
+ domain = "testdns01.pendingdns.com"
54
+ ip = "188.165.168.22"
55
55
  [[ns]]
56
- domain = "testdns02.pendingdns.com"
57
- ip = "51.38.177.242"
56
+ domain = "testdns02.pendingdns.com"
57
+ ip = "51.38.177.242"
58
58
 
59
59
  # SOA info
60
60
  # 1) This is reported as the SOA record for all domains
61
61
  [soa]
62
- admin = "hostmaster.pendingdns.com"
63
- serial = 2020050501
64
- refresh = 3600
65
- retry = 600
66
- expiration = 604800
67
- minimum = 60
62
+ admin = "hostmaster.pendingdns.com"
63
+ serial = 2020050501
64
+ refresh = 3600
65
+ retry = 600
66
+ expiration = 604800
67
+ minimum = 60
68
68
 
69
69
  # Resolver for external DNS queries, set ns=false to use system default
70
70
  # Mostly used for ANAME resolving
71
71
  [resolver]
72
- ns = ["8.8.8.8", "1.1.1.1"]
72
+ ns = ["8.8.8.8", "1.1.1.1"]
73
73
 
74
74
  # Settings for Let's Encrypt certificate generation
75
75
  [acme]
76
- # Local identifier for the ACME account
77
- key = "staging"
78
- # Defaults to Let's Encrypt staging environment
79
- directoryUrl = "https://acme-staging-v02.api.letsencrypt.org/directory"
80
-
81
- #key = "production"
82
- #directoryUrl = "https://acme-v02.api.letsencrypt.org/directory"
83
-
84
- # Email address to recive account related notifications
85
- # This value must be set as this is the person who agrees to LE TOS
86
- #email = "hostmaster@example.com"
87
- email = ""
76
+ # Local identifier for the ACME account
77
+ key = "staging"
78
+ # Defaults to Let's Encrypt staging environment
79
+ directoryUrl = "https://acme-staging-v02.api.letsencrypt.org/directory"
80
+
81
+ #key = "production"
82
+ #directoryUrl = "https://acme-v02.api.letsencrypt.org/directory"
83
+
84
+ # Email address to recive account related notifications
85
+ # This value must be set as this is the person who agrees to LE TOS
86
+ #email = "hostmaster@example.com"
87
+ email = ""
88
88
 
89
89
  [public]
90
- # If enabled=false then URL redirect server is not started and configuration is not used
91
- enabled = true
92
- server = "PendingDNS/1.0"
93
- workers = 2
94
-
95
- # path to error files, you can use your own error pages instead of the default ones
96
- # make sure though that the service user is able to read these
97
- # 1) All paths are relative to working directory, eg. /opt/pending-dns
98
- [public.errors]
99
- error404 = "./views/errors/404.hbs"
100
- error500 = "./views/errors/500.hbs"
101
-
102
- [public.http]
103
- # URL record handling over HTTP
104
- # Set to 80 in production
105
- port = 6080
106
- host = "0.0.0.0"
107
-
108
- [public.https]
109
- # URL record handling over HTTPS/HTTP2
110
- # Set to 443 in production
111
- port = 6443
112
- host = "0.0.0.0"
113
-
114
- # Path to default certificate files
115
- # These are used only for unknown domains, so can leave as is to use self-signed certs
116
- #key = "/path/to/default-privkey.pem"
117
- #cert = "/path/to/default-cert.pem"
118
-
119
- # Path to dhparam file
120
- # Generate using: openssl dhparam -out /path/to/dhparam.pem 4096
121
- #dhParam = "/path/to/dhparam.pem"
122
-
123
- # Allowed ciphers list. Leave empty for Node.js default set of ciphers
124
- ciphers = "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384"
125
-
126
- # List redirect server IP addresses here
127
- # These are returned as A/AAAA query responses for the URL domain
128
- # It should include the IP addresses for all servers where you have PendingDNS installed and "public.enabled=true"
129
- [public.hosts]
130
- A = ["127.0.0.1", "127.0.0.2"]
131
- AAAA = []
90
+ # If enabled=false then URL redirect server is not started and configuration is not used
91
+ enabled = true
92
+ server = "PendingDNS/1.0"
93
+ workers = 2
94
+
95
+ # path to error files, you can use your own error pages instead of the default ones
96
+ # make sure though that the service user is able to read these
97
+ # 1) All paths are relative to working directory, eg. /opt/pending-dns
98
+ [public.errors]
99
+ error404 = "./views/errors/404.hbs"
100
+ error500 = "./views/errors/500.hbs"
101
+
102
+ [public.http]
103
+ # URL record handling over HTTP
104
+ # Set to 80 in production
105
+ port = 6080
106
+ host = "0.0.0.0"
107
+
108
+ [public.https]
109
+ # URL record handling over HTTPS/HTTP2
110
+ # Set to 443 in production
111
+ port = 6443
112
+ host = "0.0.0.0"
113
+
114
+ # Path to default certificate files
115
+ # These are used only for unknown domains, so can leave as is to use self-signed certs
116
+ #key = "/path/to/default-privkey.pem"
117
+ #cert = "/path/to/default-cert.pem"
118
+
119
+ # Path to dhparam file
120
+ # Generate using: openssl dhparam -out /path/to/dhparam.pem 4096
121
+ #dhParam = "/path/to/dhparam.pem"
122
+
123
+ # Allowed ciphers list. Leave empty for Node.js default set of ciphers
124
+ ciphers = "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384"
125
+
126
+ # List redirect server IP addresses here
127
+ # These are returned as A/AAAA query responses for the URL domain
128
+ # It should include the IP addresses for all servers where you have PendingDNS installed and "public.enabled=true"
129
+ [public.hosts]
130
+ A = ["127.0.0.1", "127.0.0.2"]
131
+ AAAA = []
132
132
 
133
133
  [process]
134
- # Change user for child processes once privileged ports have been bound
135
- #user="www-data"
136
- #group="www-data"
134
+ # Change user for child processes once privileged ports have been bound
135
+ #user="www-data"
136
+ #group="www-data"
137
137
 
138
138
  [health]
139
- enabled = true # If enabled=false then health checks are not performed
140
- workers = 1
141
- handlers = 1 # How many health checks to run in parallel
142
- ttl = 30000 # Time in milliseconds until pending health check is considered failing
143
- delay = 60000 # Time in milliseconds between health checks against same target
139
+ enabled = true # If enabled=false then health checks are not performed
140
+ workers = 1
141
+ handlers = 1 # How many health checks to run in parallel
142
+ ttl = 30000 # Time in milliseconds until pending health check is considered failing
143
+ delay = 60000 # Time in milliseconds between health checks against same target
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
10
+ <true/>
11
+ <key>com.apple.security.cs.disable-library-validation</key>
12
+ <true/>
13
+ <key>com.apple.security.network.client</key>
14
+ <true/>
15
+ <key>com.apple.security.network.server</key>
16
+ <true/>
17
+ </dict>
18
+ </plist>
package/help.txt ADDED
@@ -0,0 +1,23 @@
1
+ pending-dns [command] [options]
2
+
3
+ Lightweight API driven Authoritative DNS server.
4
+
5
+ Commands:
6
+ pending-dns Run the application
7
+ pending-dns help Show this information
8
+
9
+ Options:
10
+ -h, --help Show help
11
+
12
+ General options:
13
+ --dbs.redis Database connection URL [string]
14
+ --log.level Log level [string] [default: "trace"]
15
+
16
+ API options:
17
+ --api.host Host to bind to [string] [default: "127.0.0.1"]
18
+ --api.port Port to bind to [number] [default: 5080]
19
+
20
+ DNS options:
21
+ --dns.host Host to bind to [string] [default: "127.0.0.1"]
22
+ --dns.port Port to bind to [number] [default: 5053]
23
+
package/lib/api-server.js CHANGED
@@ -18,28 +18,26 @@ const hostnameSchema = Joi.string().hostname({
18
18
  minDomainSegments: 1
19
19
  });
20
20
 
21
- const subdomainValidator = opts => {
22
- return (value, helpers) => {
23
- if (!value) {
24
- return value;
25
- }
21
+ const subdomainValidator = opts => (value, helpers) => {
22
+ if (!value) {
23
+ return value;
24
+ }
26
25
 
27
- let valueToCheck = value;
28
- if (opts.allowUnderscore) {
29
- valueToCheck = valueToCheck.replace(/\b_/g, 'x');
30
- }
26
+ let valueToCheck = value;
27
+ if (opts.allowUnderscore) {
28
+ valueToCheck = valueToCheck.replace(/\b_/g, 'x');
29
+ }
31
30
 
32
- if (opts.allowWildcard) {
33
- valueToCheck = valueToCheck.replace(/^\*\./, 'x.');
34
- }
31
+ if (opts.allowWildcard) {
32
+ valueToCheck = valueToCheck.replace(/^\*\./, 'x.');
33
+ }
35
34
 
36
- let result = hostnameSchema.validate(valueToCheck);
37
- if (result.error) {
38
- return helpers.error('any.invalid');
39
- }
35
+ let result = hostnameSchema.validate(valueToCheck);
36
+ if (result.error) {
37
+ return helpers.error('any.invalid');
38
+ }
40
39
 
41
- return value;
42
- };
40
+ return value;
43
41
  };
44
42
 
45
43
  const recordScheme = Joi.object({
@@ -2,7 +2,7 @@
2
2
 
3
3
  const config = require('wild-config');
4
4
  const db = require('./db');
5
- const punycode = require('punycode');
5
+ const punycode = require('punycode/');
6
6
  const { Resolver } = require('dns').promises;
7
7
  const resolver = new Resolver();
8
8
  const logger = require('./logger').child({ component: 'cached-resolver' });
package/lib/certs.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const config = require('wild-config');
4
4
  const pkg = require('../package.json');
5
5
  const db = require('./db');
6
- const punycode = require('punycode');
6
+ const punycode = require('punycode/');
7
7
  const { zoneStore } = require('./zone-store');
8
8
  const crypto = require('crypto');
9
9
  const { checkNSStatus, normalizeDomain } = require('./tools');
@@ -34,11 +34,9 @@ const acme = ACME.create({
34
34
  },
35
35
  dns01(query) {
36
36
  return localResolver.resolveTxt(query.dnsHost).then(records => ({
37
- answer: records.map(rr => {
38
- return {
39
- data: rr
40
- };
41
- })
37
+ answer: records.map(rr => ({
38
+ data: rr
39
+ }))
42
40
  }));
43
41
  }
44
42
  });
@@ -2,12 +2,13 @@
2
2
 
3
3
  const config = require('wild-config');
4
4
  const dns2 = require('dns2');
5
- const punycode = require('punycode');
5
+ const punycode = require('punycode/');
6
6
  const { zoneStore } = require('./zone-store');
7
7
  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 { v4: uuidv4 } = require('uuid');
11
12
 
12
13
  // Split long string values into character chunks
13
14
  const formatTXTData = data => {
@@ -19,11 +20,7 @@ const formatTXTData = data => {
19
20
  };
20
21
 
21
22
  // Helps to convert DNS type integer into a string (0x01 -> 'A')
22
- const reversedTypes = new Map(
23
- Object.keys(dns2.Packet.TYPE).map(key => {
24
- return [dns2.Packet.TYPE[key], key];
25
- })
26
- );
23
+ const reversedTypes = new Map(Object.keys(dns2.Packet.TYPE).map(key => [dns2.Packet.TYPE[key], key]));
27
24
 
28
25
  const shuffle = array => {
29
26
  let currentIndex = array.length,
@@ -89,7 +86,7 @@ const processQuestion = async (response, question, domain, depth) => {
89
86
  let dnsEntries = (
90
87
  await Promise.all(
91
88
  Array.from(types).map(async type => {
92
- let records = await zoneStore.resolve(domain, type, true);
89
+ let records = await zoneStore.resolve(domain, type, false);
93
90
  if (records && records.length > 1) {
94
91
  switch (type) {
95
92
  case 'A':
@@ -187,7 +184,13 @@ const processQuestion = async (response, question, domain, depth) => {
187
184
  break;
188
185
  }
189
186
  } catch (err) {
190
- logger.error({ msg: 'Failed resolving ANAME', domain: dnsEntry.domain, type: questionTypeStr, err });
187
+ logger.error({
188
+ msg: 'Failed resolving ANAME',
189
+ id: response._id,
190
+ domain: dnsEntry.domain,
191
+ type: questionTypeStr,
192
+ err
193
+ });
191
194
  }
192
195
 
193
196
  [].concat(value || []).forEach(value => {
@@ -258,7 +261,12 @@ const processQuestion = async (response, question, domain, depth) => {
258
261
  try {
259
262
  entry.domain = punycode.toASCII(entry.domain);
260
263
  } catch (err) {
261
- logger.error({ msg: 'Failed to punycode', domain: entry.domain, err });
264
+ logger.error({
265
+ msg: 'Failed to punycode',
266
+ id: response._id,
267
+ domain: entry.domain,
268
+ err
269
+ });
262
270
  }
263
271
  break;
264
272
 
@@ -267,7 +275,12 @@ const processQuestion = async (response, question, domain, depth) => {
267
275
  try {
268
276
  entry.ns = punycode.toASCII(entry.ns);
269
277
  } catch (err) {
270
- logger.error({ msg: 'Failed to punycode', domain: entry.ns, err });
278
+ logger.error({
279
+ msg: 'Failed to punycode',
280
+ id: response._id,
281
+ domain: entry.ns,
282
+ err
283
+ });
271
284
  }
272
285
  break;
273
286
 
@@ -284,7 +297,12 @@ const processQuestion = async (response, question, domain, depth) => {
284
297
  try {
285
298
  entry.exchange = punycode.toASCII(entry.exchange);
286
299
  } catch (err) {
287
- logger.error({ msg: 'Failed to punycode', domain: entry.exchange, err });
300
+ logger.error({
301
+ msg: 'Failed to punycode',
302
+ id: response._id,
303
+ domain: entry.exchange,
304
+ err
305
+ });
288
306
  }
289
307
  break;
290
308
 
@@ -316,6 +334,7 @@ const processQuestion = async (response, question, domain, depth) => {
316
334
  const dnsHandler = async request => {
317
335
  let startTime = Date.now();
318
336
  const response = new dns2.Packet(request);
337
+ request._id = response._id = uuidv4();
319
338
 
320
339
  response.header.qr = 1;
321
340
  response.header.aa = 1;
@@ -323,18 +342,25 @@ const dnsHandler = async request => {
323
342
  await Promise.all(
324
343
  request.questions.map(question => {
325
344
  logger.info({
345
+ id: request._id,
326
346
  msg: 'DNS query',
327
347
  type: request.source.type,
328
348
  port: request.source.port,
329
349
  address: request.source.address,
330
- name: question.name,
350
+ question: question.name,
331
351
  rr: reversedTypes.get(question.type) || question.type
332
352
  });
333
353
  return processQuestion(response, question);
334
354
  })
335
355
  );
336
356
 
337
- logger.info({ msg: 'DNS response', dnsTime: Date.now() - startTime, questions: request.questions, answers: response.answers });
357
+ logger.info({
358
+ msg: 'DNS response',
359
+ id: request._id,
360
+ dnsTime: Date.now() - startTime,
361
+ questions: request.questions,
362
+ answers: response.answers
363
+ });
338
364
 
339
365
  // normalize answers for the DNS library
340
366
  response.answers.forEach(answer => {
package/lib/dns-server.js CHANGED
@@ -15,7 +15,7 @@ const SUPPORTED_TYPES = new Set(
15
15
 
16
16
  const init = async () => {
17
17
  // create UDP server
18
- createDNSUdpServer(function (request, send) {
18
+ createDNSUdpServer((request, send) => {
19
19
  // filter out unsupported requests (eg. EDNS)
20
20
  if (request.additionals && request.additionals.length) {
21
21
  request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
@@ -43,7 +43,7 @@ const init = async () => {
43
43
  });
44
44
 
45
45
  // create TCP server
46
- createDNSTcpServer(function (request, send) {
46
+ createDNSTcpServer((request, send) => {
47
47
  // filter out unsupported requests (eg. EDNS)
48
48
  if (request.additionals && request.additionals.length) {
49
49
  request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
@@ -52,9 +52,7 @@ class DNSTcpServer extends EventEmitter {
52
52
  port: socket.remotePort,
53
53
  address: socket.remoteAddress
54
54
  };
55
- this.emit('request', request, message => {
56
- return this.send(socket, message);
57
- });
55
+ this.emit('request', request, message => this.send(socket, message));
58
56
  };
59
57
 
60
58
  socket.on('readable', () => {
@@ -10,8 +10,8 @@ const tls = require('tls');
10
10
  const http = require('http');
11
11
  const https = require('https');
12
12
 
13
- const tcpHealthCheck = async url => {
14
- return new Promise((resolve, reject) => {
13
+ const tcpHealthCheck = async url =>
14
+ new Promise((resolve, reject) => {
15
15
  let connectTimeout;
16
16
 
17
17
  let conn;
@@ -57,10 +57,9 @@ const tcpHealthCheck = async url => {
57
57
  client.on('timeout', onTimeout);
58
58
  client.setTimeout(config.health.ttl);
59
59
  });
60
- };
61
60
 
62
- const httpHealthCheck = async url => {
63
- return new Promise((resolve, reject) => {
61
+ const httpHealthCheck = async url =>
62
+ new Promise((resolve, reject) => {
64
63
  let connectTimeout;
65
64
 
66
65
  let conn;
@@ -111,7 +110,6 @@ const httpHealthCheck = async url => {
111
110
  client.on('timeout', onTimeout);
112
111
  client.setTimeout(config.health.ttl);
113
112
  });
114
- };
115
113
 
116
114
  const healthCheck = async target => {
117
115
  try {
@@ -17,7 +17,7 @@ const httpProxy = require('http-proxy');
17
17
 
18
18
  const proxyServer = httpProxy.createProxyServer({});
19
19
 
20
- proxyServer.on('proxyReq', function (proxyReq, req /*, res, options*/) {
20
+ proxyServer.on('proxyReq', (proxyReq, req /*, res, options*/) => {
21
21
  proxyReq.setHeader('X-Forwarded-Proto', req.proto);
22
22
  proxyReq.setHeader('X-Connecting-IP', req.ip);
23
23
  proxyReq.setHeader('X-CDN-Loop', 'PendingDNS');
@@ -181,11 +181,7 @@ const handler = async (req, res) => {
181
181
  const url = new URL(req.url, `${req.proto}://${domain}/`);
182
182
  const route = url.pathname;
183
183
 
184
- const target =
185
- records &&
186
- records.find(rr => {
187
- return rr && rr.type === 'URL' && rr.value && rr.value.length;
188
- });
184
+ const target = records && records.find(rr => rr && rr.type === 'URL' && rr.value && rr.value.length);
189
185
 
190
186
  if (target) {
191
187
  // redirect to target URL
@@ -238,8 +234,8 @@ const handler = async (req, res) => {
238
234
  );
239
235
  };
240
236
 
241
- const setupHttps = () => {
242
- return new Promise((resolve, reject) => {
237
+ const setupHttps = () =>
238
+ new Promise((resolve, reject) => {
243
239
  const server = https.createServer(
244
240
  {
245
241
  key: defaultKey,
@@ -249,9 +245,7 @@ const setupHttps = () => {
249
245
  allowHTTP1: true,
250
246
  SNICallback(servername, cb) {
251
247
  getSNIContext(servername)
252
- .then(ctx => {
253
- return cb(null, ctx || defaultCtx);
254
- })
248
+ .then(ctx => cb(null, ctx || defaultCtx))
255
249
  .catch(err => {
256
250
  logger.error({ msg: 'SNI failed', servername, err });
257
251
  return cb(null, defaultCtx);
@@ -325,10 +319,9 @@ const setupHttps = () => {
325
319
  reject(err);
326
320
  });
327
321
  });
328
- };
329
322
 
330
- const setupHttp = () => {
331
- return new Promise((resolve, reject) => {
323
+ const setupHttp = () =>
324
+ new Promise((resolve, reject) => {
332
325
  const server = http.createServer((req, res) => {
333
326
  req.proto = 'http';
334
327
  middleware(req, res);
@@ -362,7 +355,6 @@ const setupHttp = () => {
362
355
  reject(err);
363
356
  });
364
357
  });
365
- };
366
358
 
367
359
  const init = async () => {
368
360
  await Promise.all([setupHttps(), setupHttp()]);
package/lib/tools.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const cachedResolver = require('./cached-resolver');
4
- const punycode = require('punycode');
4
+ const punycode = require('punycode/');
5
5
  const Joi = require('@hapi/joi');
6
6
 
7
7
  const emailSchema = Joi.string().email({}).required();
package/lib/zone-store.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const punycode = require('punycode');
3
+ const punycode = require('punycode/');
4
4
  const shortid = require('shortid');
5
5
  const db = require('./db');
6
6
  const logger = require('./logger').child({ component: 'zone-store' });
@@ -196,6 +196,7 @@ class ZoneStore {
196
196
  if (short) {
197
197
  return {
198
198
  id: this.getFullId(name, type, hid),
199
+ zone: zoneDomain,
199
200
  domain,
200
201
  type,
201
202
  value: JSON.parse(value)
@@ -225,9 +226,7 @@ class ZoneStore {
225
226
 
226
227
  return Object.keys(record)
227
228
  .map(key => [key, record[key]])
228
- .sort((a, b) => {
229
- return a[1].localeCompare(b[1]);
230
- })
229
+ .sort((a, b) => a[1].localeCompare(b[1]))
231
230
  .map(entry => {
232
231
  let hid = entry[0];
233
232
  let value = entry[1];
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "pending-dns",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Lightweight API driven DNS server",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node server.js",
8
8
  "test": "grunt",
9
- "licenses": "npm-license-crawler --production --csv ./licenses.csv"
9
+ "build-source": "rm -rf node_modules package-lock.json && npm install && npm run licenses && rm -rf node_modules package-lock.json && npm install --production && rm -rf package-lock.json",
10
+ "build-dist-fast": "npx pkg --debug package.json && rm -rf package-lock.json && npm install",
11
+ "build-dist": "npx pkg --compress Brotli package.json && rm -rf package-lock.json && npm install",
12
+ "licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt"
10
13
  },
11
14
  "repository": {
12
15
  "type": "git",
@@ -25,34 +28,55 @@
25
28
  },
26
29
  "homepage": "https://github.com/postalsys/pending-dns#readme",
27
30
  "devDependencies": {
28
- "eslint": "7.1.0",
31
+ "eslint": "8.45.0",
29
32
  "eslint-config-nodemailer": "1.2.0",
30
- "eslint-config-prettier": "6.11.0",
31
- "grunt": "1.1.0",
32
- "grunt-cli": "1.3.2",
33
- "grunt-eslint": "23.0.0",
34
- "npm-license-crawler": "0.2.1"
33
+ "eslint-config-prettier": "8.8.0",
34
+ "grunt": "1.6.1",
35
+ "grunt-cli": "1.4.3",
36
+ "grunt-eslint": "24.3.0",
37
+ "license-report": "6.4.0"
35
38
  },
36
39
  "dependencies": {
40
+ "@bugsnag/js": "^7.20.2",
37
41
  "@fidm/x509": "1.2.1",
38
- "@hapi/boom": "9.1.0",
39
- "@hapi/hapi": "19.1.1",
40
- "@hapi/inert": "6.0.1",
42
+ "@hapi/boom": "10.0.1",
43
+ "@hapi/hapi": "21.3.2",
44
+ "@hapi/inert": "7.1.0",
41
45
  "@hapi/joi": "17.1.1",
42
- "@hapi/vision": "6.0.0",
43
- "@root/acme": "3.0.10",
46
+ "@hapi/vision": "7.0.2",
47
+ "@root/acme": "3.1.0",
44
48
  "@root/csr": "0.8.1",
45
- "dns2": "1.3.2",
46
- "hapi-pino": "8.0.1",
47
- "hapi-swagger": "13.0.2",
49
+ "dns2": "2.1.0",
50
+ "handlebars": "4.7.7",
51
+ "hapi-pino": "12.1.0",
52
+ "hapi-swagger": "17.1.0",
48
53
  "http-proxy": "1.18.1",
49
- "ioredfour": "1.0.2-ioredis-03",
50
- "ioredis": "4.17.3",
51
- "ipaddr.js": "1.9.1",
52
- "node-rsa": "1.0.8",
54
+ "ioredfour": "1.2.0-ioredis-07",
55
+ "ioredis": "5.3.2",
56
+ "ipaddr.js": "2.1.0",
57
+ "minimist": "1.2.8",
58
+ "node-rsa": "1.1.1",
53
59
  "pem-jwk": "2.0.0",
54
- "pino": "6.3.2",
55
- "shortid": "2.2.15",
56
- "wild-config": "1.5.1"
60
+ "pino": "8.14.1",
61
+ "punycode": "2.3.0",
62
+ "shortid": "2.2.16",
63
+ "uuid": "9.0.0",
64
+ "wild-config": "1.7.0"
65
+ },
66
+ "bin": {
67
+ "pending-dns": "bin/pending-dns.js"
68
+ },
69
+ "pkg": {
70
+ "assets": [
71
+ "licenses.txt",
72
+ "LICENSE.txt",
73
+ "help.txt"
74
+ ],
75
+ "targets": [
76
+ "node16-linux-x64",
77
+ "node16-macos-x64",
78
+ "node16-win-x64"
79
+ ],
80
+ "outputPath": "ee-dist"
57
81
  }
58
82
  }
package/server.js CHANGED
@@ -7,6 +7,7 @@ const argv = process.argv.slice(2);
7
7
  const config = require('wild-config');
8
8
  const logger = require('./lib/logger').child({ component: 'server' });
9
9
  const pathlib = require('path');
10
+ const packageData = require('./package.json');
10
11
  const { Worker, SHARE_ENV } = require('worker_threads');
11
12
  const { isemail } = require('./lib/tools');
12
13
 
@@ -15,6 +16,29 @@ if (!config.acme || !isemail(config.acme.email)) {
15
16
  process.exit(51);
16
17
  }
17
18
 
19
+ const Bugsnag = require('@bugsnag/js');
20
+ if (process.env.BUGSNAG_API_KEY) {
21
+ Bugsnag.start({
22
+ apiKey: process.env.BUGSNAG_API_KEY,
23
+ appVersion: packageData.version,
24
+ logger: {
25
+ debug(...args) {
26
+ logger.debug({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
27
+ },
28
+ info(...args) {
29
+ logger.debug({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
30
+ },
31
+ warn(...args) {
32
+ logger.warn({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
33
+ },
34
+ error(...args) {
35
+ logger.error({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
36
+ }
37
+ }
38
+ });
39
+ logger.notifyError = Bugsnag.notify.bind(Bugsnag);
40
+ }
41
+
18
42
  let closing = false;
19
43
 
20
44
  let workers = new Map();
@@ -67,17 +91,30 @@ if (config.health.enabled) {
67
91
  spawnWorker('health');
68
92
  }
69
93
 
70
- const closeProcess = code => {
94
+ const closeProcess = (code, errType, err) => {
71
95
  if (closing) {
72
96
  return;
73
97
  }
74
98
  closing = true;
75
- setTimeout(() => {
76
- process.exit(code);
77
- }, 10);
99
+
100
+ if (!code) {
101
+ return setTimeout(() => {
102
+ process.exit(code);
103
+ }, 10);
104
+ }
105
+
106
+ logger.fatal({
107
+ msg: errType,
108
+ _msg: errType,
109
+ err
110
+ });
111
+
112
+ if (!logger.notifyError) {
113
+ setTimeout(() => process.exit(code), 10);
114
+ }
78
115
  };
79
116
 
80
- process.on('uncaughtException', () => closeProcess(1));
81
- process.on('unhandledRejection', () => closeProcess(2));
117
+ process.on('uncaughtException', err => closeProcess(1, 'uncaughtException', err));
118
+ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', err));
82
119
  process.on('SIGTERM', () => closeProcess(0));
83
120
  process.on('SIGINT', () => closeProcess(0));
package/workers/api.js CHANGED
@@ -9,18 +9,31 @@ const config = require('wild-config');
9
9
  const workerName = 'api';
10
10
 
11
11
  let closing = false;
12
- const closeProcess = code => {
12
+ const closeProcess = (code, errType, err) => {
13
13
  if (closing) {
14
14
  return;
15
15
  }
16
16
  closing = true;
17
- setTimeout(() => {
18
- process.exit(code);
19
- }, 10);
17
+
18
+ if (!code) {
19
+ return setTimeout(() => {
20
+ process.exit(code);
21
+ }, 10);
22
+ }
23
+
24
+ logger.fatal({
25
+ msg: errType,
26
+ _msg: errType,
27
+ err
28
+ });
29
+
30
+ if (!logger.notifyError) {
31
+ setTimeout(() => process.exit(code), 10);
32
+ }
20
33
  };
21
34
 
22
- process.on('uncaughtException', () => closeProcess(1));
23
- process.on('unhandledRejection', () => closeProcess(2));
35
+ process.on('uncaughtException', err => closeProcess(1, 'uncaughtException', err));
36
+ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', err));
24
37
  process.on('SIGTERM', () => closeProcess(0));
25
38
  process.on('SIGINT', () => closeProcess(0));
26
39
 
package/workers/dns.js CHANGED
@@ -9,21 +9,58 @@ const config = require('wild-config');
9
9
  const workerName = 'dns';
10
10
 
11
11
  let closing = false;
12
- const closeProcess = code => {
12
+ const closeProcess = (code, errType, err) => {
13
13
  if (closing) {
14
14
  return;
15
15
  }
16
16
  closing = true;
17
- setTimeout(() => {
18
- process.exit(code);
19
- }, 10);
17
+
18
+ if (!code) {
19
+ return setTimeout(() => {
20
+ process.exit(code);
21
+ }, 10);
22
+ }
23
+
24
+ logger.fatal({
25
+ msg: errType,
26
+ _msg: errType,
27
+ err
28
+ });
29
+
30
+ if (!logger.notifyError) {
31
+ setTimeout(() => process.exit(code), 10);
32
+ }
20
33
  };
21
34
 
22
- process.on('uncaughtException', () => closeProcess(1));
23
- process.on('unhandledRejection', () => closeProcess(2));
35
+ process.on('uncaughtException', err => closeProcess(1, 'uncaughtException', err));
36
+ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', err));
24
37
  process.on('SIGTERM', () => closeProcess(0));
25
38
  process.on('SIGINT', () => closeProcess(0));
26
39
 
40
+ const packageData = require('../package.json');
41
+ const Bugsnag = require('@bugsnag/js');
42
+ if (process.env.BUGSNAG_API_KEY) {
43
+ Bugsnag.start({
44
+ apiKey: process.env.BUGSNAG_API_KEY,
45
+ appVersion: packageData.version,
46
+ logger: {
47
+ debug(...args) {
48
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
49
+ },
50
+ info(...args) {
51
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
52
+ },
53
+ warn(...args) {
54
+ logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
55
+ },
56
+ error(...args) {
57
+ logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
58
+ }
59
+ }
60
+ });
61
+ logger.notifyError = Bugsnag.notify.bind(Bugsnag);
62
+ }
63
+
27
64
  const run = () => {
28
65
  require(`../lib/${workerName}-server.js`)()
29
66
  .then(() => {
package/workers/health.js CHANGED
@@ -9,21 +9,59 @@ const config = require('wild-config');
9
9
  const workerName = 'health';
10
10
 
11
11
  let closing = false;
12
- const closeProcess = code => {
12
+ const closeProcess = (code, errType, err) => {
13
13
  if (closing) {
14
14
  return;
15
15
  }
16
16
  closing = true;
17
- setTimeout(() => {
18
- process.exit(code);
19
- }, 10);
17
+
18
+ if (!code) {
19
+ return setTimeout(() => {
20
+ process.exit(code);
21
+ }, 10);
22
+ }
23
+
24
+ logger.fatal({
25
+ msg: errType,
26
+ _msg: errType,
27
+ err
28
+ });
29
+
30
+ if (!logger.notifyError) {
31
+ setTimeout(() => process.exit(code), 10);
32
+ }
20
33
  };
21
34
 
22
- process.on('uncaughtException', () => closeProcess(1));
23
- process.on('unhandledRejection', () => closeProcess(2));
35
+ process.on('uncaughtException', err => closeProcess(1, 'uncaughtException', err));
36
+ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', err));
24
37
  process.on('SIGTERM', () => closeProcess(0));
25
38
  process.on('SIGINT', () => closeProcess(0));
26
39
 
40
+ const packageData = require('../package.json');
41
+ const Bugsnag = require('@bugsnag/js');
42
+
43
+ if (process.env.BUGSNAG_API_KEY) {
44
+ Bugsnag.start({
45
+ apiKey: process.env.BUGSNAG_API_KEY,
46
+ appVersion: packageData.version,
47
+ logger: {
48
+ debug(...args) {
49
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
50
+ },
51
+ info(...args) {
52
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
53
+ },
54
+ warn(...args) {
55
+ logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
56
+ },
57
+ error(...args) {
58
+ logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
59
+ }
60
+ }
61
+ });
62
+ logger.notifyError = Bugsnag.notify.bind(Bugsnag);
63
+ }
64
+
27
65
  const run = () => {
28
66
  require(`../lib/${workerName}-worker.js`)()
29
67
  .then(() => {
package/workers/public.js CHANGED
@@ -9,21 +9,58 @@ const config = require('wild-config');
9
9
  const workerName = 'public';
10
10
 
11
11
  let closing = false;
12
- const closeProcess = code => {
12
+ const closeProcess = (code, errType, err) => {
13
13
  if (closing) {
14
14
  return;
15
15
  }
16
16
  closing = true;
17
- setTimeout(() => {
18
- process.exit(code);
19
- }, 10);
17
+
18
+ if (!code) {
19
+ return setTimeout(() => {
20
+ process.exit(code);
21
+ }, 10);
22
+ }
23
+
24
+ logger.fatal({
25
+ msg: errType,
26
+ _msg: errType,
27
+ err
28
+ });
29
+
30
+ if (!logger.notifyError) {
31
+ setTimeout(() => process.exit(code), 10);
32
+ }
20
33
  };
21
34
 
22
- process.on('uncaughtException', () => closeProcess(1));
23
- process.on('unhandledRejection', () => closeProcess(2));
35
+ process.on('uncaughtException', err => closeProcess(1, 'uncaughtException', err));
36
+ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', err));
24
37
  process.on('SIGTERM', () => closeProcess(0));
25
38
  process.on('SIGINT', () => closeProcess(0));
26
39
 
40
+ const packageData = require('../package.json');
41
+ const Bugsnag = require('@bugsnag/js');
42
+ if (process.env.BUGSNAG_API_KEY) {
43
+ Bugsnag.start({
44
+ apiKey: process.env.BUGSNAG_API_KEY,
45
+ appVersion: packageData.version,
46
+ logger: {
47
+ debug(...args) {
48
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
49
+ },
50
+ info(...args) {
51
+ logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
52
+ },
53
+ warn(...args) {
54
+ logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
55
+ },
56
+ error(...args) {
57
+ logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
58
+ }
59
+ }
60
+ });
61
+ logger.notifyError = Bugsnag.notify.bind(Bugsnag);
62
+ }
63
+
27
64
  const run = () => {
28
65
  require(`../lib/${workerName}-server.js`)()
29
66
  .then(() => {