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