@tryghost/domain-validator 2.0.0 → 3.0.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/lib/caa.js ADDED
@@ -0,0 +1,37 @@
1
+ const dns = require('@tryghost/dns');
2
+ const debug = require('@tryghost/debug')('dv:caa');
3
+
4
+ /**
5
+ * Check if the domain's CAA records allow the given issuer.
6
+ *
7
+ * @param {string} customdomain
8
+ * @param {string} issuer - CA identity to check (e.g. 'certainly.com')
9
+ * @returns {Promise<{isCaaValid: boolean, caaSource: string|null, caaAllowedIssuers: string[], caaError: string|null}>}
10
+ */
11
+ module.exports.validate = (customdomain, issuer) => {
12
+ debug('Checking CAA for ' + customdomain + ' against ' + issuer);
13
+ return dns.caa(customdomain, {issuer}).then((result) => {
14
+ return {
15
+ isCaaValid: result.allowed,
16
+ caaSource: result.source,
17
+ caaAllowedIssuers: result.allowedIssuers,
18
+ caaError: result.error
19
+ };
20
+ }).catch((err) => {
21
+ // Fail open: dns.caa fails open internally on DNS errors, but guard
22
+ // against unexpected throws (bad arguments, future regressions) so
23
+ // CAA can never take down the rest of the validity report. Tolerate
24
+ // non-Error rejections (null/undefined/strings) so this handler
25
+ // itself can never throw.
26
+ const message = (err && typeof err.message === 'string')
27
+ ? err.message
28
+ : String(err || 'UNKNOWN');
29
+ debug('Unexpected CAA error: ' + message);
30
+ return {
31
+ isCaaValid: true,
32
+ caaSource: null,
33
+ caaAllowedIssuers: [],
34
+ caaError: message
35
+ };
36
+ });
37
+ };
package/lib/validate.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const dns = require('./dns');
3
3
  const pro = require('./pro');
4
+ const caa = require('./caa');
4
5
  const debug = require('@tryghost/debug')('dv:validate');
5
6
 
6
7
  const _private = {};
@@ -22,20 +23,28 @@ const validityReportObject = {
22
23
  dnsRating: null,
23
24
  isDnsValidForSSL: false,
24
25
  sslRating: null,
26
+ isCaaValid: true,
27
+ caaSource: null,
28
+ caaAllowedIssuers: [],
29
+ caaError: null,
25
30
  message: ''
26
31
  };
27
32
 
28
- _private.doChecks = (subdomain, customdomain, subdirectory, prodomain) => {
33
+ _private.doChecks = (subdomain, customdomain, subdirectory, prodomain, caaIssuer) => {
29
34
  const checkResult = {};
30
35
  debug('starting pro');
31
36
  return pro.validate('https', subdomain, customdomain, subdirectory, prodomain)
32
37
  .then((proResult) => {
33
38
  checkResult.pro = proResult;
34
39
  debug('got pro, starting dns');
35
- return dns.validate(subdomain, customdomain, checkResult.pro.isGhostPro, prodomain);
40
+ return Promise.all([
41
+ dns.validate(subdomain, customdomain, checkResult.pro.isGhostPro, prodomain),
42
+ caaIssuer ? caa.validate(customdomain, caaIssuer) : Promise.resolve(null)
43
+ ]);
36
44
  })
37
- .then((dnsResult) => {
45
+ .then(([dnsResult, caaResult]) => {
38
46
  checkResult.dns = dnsResult;
47
+ checkResult.caa = caaResult;
39
48
  debug('got dns, returning');
40
49
  return checkResult;
41
50
  });
@@ -43,16 +52,23 @@ _private.doChecks = (subdomain, customdomain, subdirectory, prodomain) => {
43
52
 
44
53
  /**
45
54
  * check health
46
- * @param {Object} blog
47
55
  *
48
56
  * - returns a validity report
49
57
  * - does NOT error unless something goes wrong
58
+ *
59
+ * @param {string} subdomain
60
+ * @param {string} customdomain
61
+ * @param {string} subdirectory
62
+ * @param {string} [prodomain=ghost.io]
63
+ * @param {string} [caaIssuer] - CA identity to check CAA records against (e.g. 'certainly.com').
64
+ * When omitted, CAA is not checked and isCaaValid defaults to true.
50
65
  */
51
- module.exports.validate = (subdomain, customdomain, subdirectory, prodomain = 'ghost.io') => _private
52
- .doChecks(subdomain, customdomain, subdirectory, prodomain)
66
+ module.exports.validate = (subdomain, customdomain, subdirectory, prodomain = 'ghost.io', caaIssuer) => _private
67
+ .doChecks(subdomain, customdomain, subdirectory, prodomain, caaIssuer)
53
68
  .then(function (result) {
54
- // Make a new validity report
55
- const validityReport = _.clone(validityReportObject);
69
+ // Deep clone so nested mutable fields (input, caaAllowedIssuers)
70
+ // can't leak across validation calls via the shared template.
71
+ const validityReport = _.cloneDeep(validityReportObject);
56
72
 
57
73
  // Store the inputted details
58
74
  validityReport.input.subdomain = subdomain;
@@ -64,8 +80,25 @@ module.exports.validate = (subdomain, customdomain, subdirectory, prodomain = 'g
64
80
  // Do a few last DNS-related checks, and then output validity values
65
81
  _.merge(validityReport, _.omit(result.dns, 'raw'));
66
82
 
83
+ if (result.caa) {
84
+ _.merge(validityReport, result.caa);
85
+
86
+ // CAA blocking issuance means SSL can't be provisioned going forward,
87
+ // even if DNS currently points at Ghost — treat it like a DNS failure.
88
+ if (!validityReport.isCaaValid) {
89
+ validityReport.isDnsValidForSSL = false;
90
+ validityReport.sslRating = 'D';
91
+ validityReport.message = 'CAA record does not allow ' + caaIssuer
92
+ + ' (allowed: ' + (validityReport.caaAllowedIssuers.join(', ') || 'none')
93
+ + ', source: ' + validityReport.caaSource + ')';
94
+ }
95
+ }
96
+
67
97
  if (validityReport.isProxy) {
68
- validityReport.message = validityReport.isProxyValid ? 'Found a valid proxy' : 'Found invalid proxy config';
98
+ const proxyMessage = validityReport.isProxyValid ? 'Found a valid proxy' : 'Found invalid proxy config';
99
+ validityReport.message = validityReport.message
100
+ ? validityReport.message + '; ' + proxyMessage
101
+ : proxyMessage;
69
102
  }
70
103
 
71
104
  validityReport.raw = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryghost/domain-validator",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "ssh://git@github.com/TryGhost/Pro",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@tryghost/debug": "2.0.3",
34
- "@tryghost/dns": "^0.6.0",
34
+ "@tryghost/dns": "^0.7.0",
35
35
  "@tryghost/errors": "3.0.3",
36
36
  "@tryghost/pretty-cli": "3.0.3",
37
37
  "bluebird": "3.7.2",
38
38
  "got": "11.8.6",
39
39
  "lodash": "4.17.23"
40
40
  },
41
- "gitHead": "ef060c71be33d265e7d97e4579ed83c3e3fdac09"
41
+ "gitHead": "c5d1ebb864969de89c1e8a8dcac3f577bceee1dd"
42
42
  }