v3.1.0: support `zones` test, made prettier

This commit is contained in:
AJ ONeal 2019-06-12 23:53:43 -06:00
parent 32030b9d80
commit 7b079bcf3a
5 changed files with 197 additions and 100 deletions

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"printWidth": 80,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
}

View File

@ -11,15 +11,15 @@ and [ACME.js](https://git.rootprojects.org/root/acme-v2.js).
_acme-challenge.example.com TXT xxxxxxxxxxxxxxxx TTL 60 _acme-challenge.example.com TXT xxxxxxxxxxxxxxxx TTL 60
``` ```
* Prints the ACME challenge DNS Host and DNS Key Authorization Digest to the terminal - Prints the ACME challenge DNS Host and DNS Key Authorization Digest to the terminal
* (waits for you to hit enter before continuing) - (waits for you to hit enter before continuing)
* Let's you know when the challenge as succeeded or failed, and is safe to remove. - Let's you know when the challenge as succeeded or failed, and is safe to remove.
Other ACME Challenge Reference Implementations: Other ACME Challenge Reference Implementations:
* [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js.git) - [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js.git)
* [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js.git) - [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js.git)
* [**acme-dns-01-cli**](https://git.rootprojects.org/root/acme-dns-01-cli.js.git) - [**acme-dns-01-cli**](https://git.rootprojects.org/root/acme-dns-01-cli.js.git)
## Install ## Install
@ -31,16 +31,16 @@ If you have `greenlock@v2.6` or lower, you'll need the old `le-challenge-dns@2.x
## Usage ## Usage
```bash ```js
var Greenlock = require('greenlock'); var Greenlock = require('greenlock');
Greenlock.create({ Greenlock.create({
... challenges: {
, challenges: { 'http-01': require('acme-http-01-fs') 'http-01': require('acme-http-01-fs'),
, 'dns-01': require('acme-dns-01-cli').create({ debug: true }) 'dns-01': require('acme-dns-01-cli').create({ debug: true }),
, 'tls-alpn-01': require('acme-tls-alpn-01-cli') 'tls-alpn-01': require('acme-tls-alpn-01-cli')
} }
... // ...
}); });
``` ```
@ -49,11 +49,9 @@ overwriting the default with the one that you want in `approveDomains()`:
```js ```js
function approveDomains(opts) { function approveDomains(opts) {
... // ...
if (!opts.challenges) { opts.challenges = {}; } if (!opts.challenges) { opts.challenges = {}; }
opts.challenges['dns-01'] = acmeDns01Cli; opts.challenges['dns-01'] = acmeDns01Cli;
opts.challenges['http-01'] = ...
return Promise.resolve({ ... }); return Promise.resolve({ ... });
} }
@ -66,36 +64,37 @@ it will require 6 individual challenges.
For ACME Challenge: For ACME Challenge:
* `set(opts)` - `set(opts)`
* `remove(opts)` - `remove(opts)`
The `dns-01` strategy supports wildcards (whereas `http-01` does not). The `dns-01` strategy supports wildcards (whereas `http-01` does not).
The options object has whatever options were set in `approveDomains()` The options object has whatever options were set in `approveDomains()`
as well as the `challenge`, which looks like this: as well as the `challenge`, which looks like this:
```js ```json
{ challenge: { {
identifier: { type: 'dns', value: 'example.com' "challenge": {
, wildcard: true "identifier": { "type": "dns", "value": "example.com" },
, altname: '*.example.com' "wildcard": true,
, type: 'dns-01' "altname": "*.example.com",
, token: 'xxxxxx' "type": "dns-01",
, keyAuthorization: 'xxxxxx.abc123' "token": "xxxxxx",
, dnsHost: '_acme-challenge.example.com' "keyAuthorization": "xxxxxx.abc123",
, dnsAuthorization: 'xyz567' "dnsHost": "_acme-challenge.example.com",
, expires: '1970-01-01T00:00:00Z' "dnsAuthorization": "xyz567",
"expires": "1970-01-01T00:00:00Z"
} }
} }
``` ```
For greenlock.js internals: For greenlock.js internals:
* `options` stores the internal defaults merged with the user-supplied options - `options` stores the internal defaults merged with the user-supplied options
Optional: Optional:
* `get(limitedOpts)` - `get(limitedOpts)`
Note: Typically there wouldn't be a `get()` for DNS because the NameServer (not Greenlock) answers the requests. Note: Typically there wouldn't be a `get()` for DNS because the NameServer (not Greenlock) answers the requests.
It could be used for testing implementations, but that's about it. It could be used for testing implementations, but that's about it.
@ -104,12 +103,13 @@ It could be used for testing implementations, but that's about it.
If there were an implementation of Greenlock integrated directly into If there were an implementation of Greenlock integrated directly into
a NameServer (which currently there is not), it would probably look like this: a NameServer (which currently there is not), it would probably look like this:
```js ```json
{ challenge: { {
type: 'dns-01' "challenge": {
, identifier: { type: 'dns', value: 'example.com' } "type": "dns-01",
, token: 'abc123' "identifier": { "type": "dns", "value": "example.com" },
, dnsHost: '_acme-challenge.example.com' "token": "abc123",
"dnsHost": "_acme-challenge.example.com"
} }
} }
``` ```

184
index.js
View File

@ -1,11 +1,9 @@
'use strict'; 'use strict';
/*global Promise*/ /*global Promise*/
var Challenge = module.exports; var Challenge = module.exports;
// If your implementation needs config options, set them. Otherwise, don't bother (duh). // If your implementation needs config options, set them. Otherwise, don't bother (duh).
Challenge.create = function (config) { Challenge.create = function(config) {
var challenger = {}; var challenger = {};
// Note: normally you'd these right in the method body, but for the sake of // Note: normally you'd these right in the method body, but for the sake of
@ -14,19 +12,24 @@ Challenge.create = function (config) {
// Note: All of these methods can be synchronous, async, Promise, and callback-style // Note: All of these methods can be synchronous, async, Promise, and callback-style
// (the calling functions check function.length and then Promisify accordingly) // (the calling functions check function.length and then Promisify accordingly)
// Called when it's tiem to set the challenge // Fetches an array of zone name strings
challenger.set = function (opts, cb) { challenger.zones = function(opts, cb) {
return Challenge._getZones(opts, cb);
};
// Called when it's time to set the challenge
challenger.set = function(opts, cb) {
return Challenge._setDns(opts, cb); return Challenge._setDns(opts, cb);
}; };
// Called when it's time to remove the challenge // Called when it's time to remove the challenge
challenger.remove = function (opts) { challenger.remove = function(opts, cb) {
return Challenge._removeDns(opts); return Challenge._removeDns(opts, cb);
}; };
// Optional (only really useful for http and testing) // Optional (only really useful for http and testing)
// Called when the challenge needs to be retrieved // Called when the challenge needs to be retrieved
challenger.get = function (opts) { challenger.get = function(opts) {
return Challenge._getDns(opts); return Challenge._getDns(opts);
}; };
@ -38,88 +41,167 @@ Challenge.create = function (config) {
return challenger; return challenger;
}; };
// Show the user the token and key and wait for them to be ready to continue
Challenge._getZones = function(args, cb) {
// if you need per-run / per-domain options set them in approveDomains() and they'll be on 'args' here.
if (!Array.isArray(args.dnsHosts)) {
console.error(
'You must be using Greenlock v2.7+ to use acme-dns-01-cli v3+'
);
process.exit();
}
console.info();
console.info('############################');
console.info('# Step 1: Get Domain Zones #');
console.info('############################');
console.info();
console.info(
'Enter a comma or space delimited list of domain zones to which the following domain records belong.'
);
console.info();
console.info(
'Example:' +
'\n\tDOMAIN RECORD\t=>\tDOMAIN ZONE' +
'\n\texample.com\t=>\texample.com' +
'\n\tfoo.example.com\t=>\texample.com' +
'\n\tbar.example.com\t=>\texample.com' +
'\n\texample.co.uk\t=>\texample.co.uk' +
'\n\nYou would enter: example.com, example.co.uk'
);
console.info();
console.info('Domain RECORDS: ', args.dnsHosts.join(', '));
process.stdout.write('Domain ZONE list: ');
process.stdin.resume();
process.stdin.once('data', function(chunk) {
process.stdin.pause();
var zones = chunk
.toString('utf8')
.trim()
.split(/[,\s]+/);
console.info('Got Domain Zones:', zones);
setTimeout(function() {
cb(null, zones);
}, 1000);
});
};
// Show the user the token and key and wait for them to be ready to continue // Show the user the token and key and wait for them to be ready to continue
Challenge._setDns = function (args, cb) { Challenge._setDns = function(args, cb) {
// if you need per-run / per-domain options set them in approveDomains() and they'll be on 'args' here. // if you need per-run / per-domain options set them in approveDomains() and they'll be on 'args' here.
if (!args.challenge) { if (!args.challenge) {
console.error("You must be using Greenlock v2.7+ to use greenlock-challenge-dns v3+"); console.error(
'You must be using Greenlock v2.7+ to use acme-dns-01-cli v3+'
);
process.exit(); process.exit();
} }
var ch = args.challenge; var ch = args.challenge;
console.info(""); console.info('\n\n\n\n\n');
console.info('#################################');
console.info('# Step 2: Set Domain TXT Record #');
console.info('#################################');
console.info('');
console.info("[ACME dns-01 '" + ch.altname + "' CHALLENGE]"); console.info("[ACME dns-01 '" + ch.altname + "' CHALLENGE]");
console.info("You're about to receive the following DNS query:"); console.info("You're about to receive the following DNS query:");
console.info(""); console.info('');
console.info("\tTXT\t" + ch.dnsHost + "\t" + ch.dnsAuthorization + "\tTTL 60"); console.info(
console.info(""); '\tTXT\t' + ch.dnsHost + '\t' + ch.dnsAuthorization + '\tTTL 60'
);
console.info('');
if (ch.debug) { if (ch.debug) {
console.info("Debug Info:"); console.info('Debug Info:');
console.info(""); console.info('');
console.info(JSON.stringify(dnsChallengeToJson(ch), null, ' ').replace(/^/gm, '\t')); console.info(
console.info(""); JSON.stringify(dnsChallengeToJson(ch), null, ' ').replace(/^/gm, '\t')
);
console.info('');
} }
console.info("Go set that DNS record, wait a few seconds for it to propagate, and then continue when ready"); console.info(
console.info("[Press the ANY key to continue...]"); 'Go set that DNS record, wait a few seconds for it to propagate, and then continue when ready'
);
console.info('[Press the ANY key to continue...]');
process.stdin.resume(); process.stdin.resume();
process.stdin.once('data', function () { process.stdin.once('data', function() {
process.stdin.pause(); process.stdin.pause();
cb(null, null); setTimeout(function() {
cb(null, null);
}, 1000);
}); });
}; };
// might as well tell the user that whatever they were setting up has been checked // might as well tell the user that whatever they were setting up has been checked
Challenge._removeDns = function (args) { Challenge._removeDns = function(args, cb) {
var ch = args.challenge; var ch = args.challenge;
console.info(""); console.info('\n\n\n\n\n');
console.info('####################################');
console.info('# Step 4: Remove Domain TXT Record #');
console.info('####################################');
console.info('');
console.info("[ACME dns-01 '" + ch.altname + "' COMPLETE]: " + ch.status); console.info("[ACME dns-01 '" + ch.altname + "' COMPLETE]: " + ch.status);
console.info("Challenge complete. You may now remove the DNS-01 challenge record:"); console.info(
console.info(""); 'Challenge complete. You may now remove the DNS-01 challenge record:'
console.info("\tTXT\t" + ch.altname + "\t" + ch.dnsAuthorization); );
console.info(""); console.info('');
console.info('\tTXT\t' + ch.altname + '\t' + ch.dnsAuthorization);
console.info('');
console.info('NOTE: the next get should be EMPTY');
console.info('');
return null; setTimeout(function() {
cb(null, null);
}, 1000);
}; };
// This is implemented here for completeness (and perhaps some possible use in testing), // This is implemented here for completeness (and perhaps some possible use in testing),
// but it's not something you would implement because the Greenlock server isn't the NameServer. // but it's not something you would implement because the Greenlock server isn't the NameServer.
Challenge._getDns = function (args) { Challenge._getDns = function(args) {
var ch = args.challenge; var ch = args.challenge;
// because the way to mock a DNS challenge is weird // because the way to mock a DNS challenge is weird
var altname = (ch.altname || ch.dnsHost || ch.identifier.value); var altname = ch.altname || ch.dnsHost || ch.identifier.value;
var dnsHost = (ch.dnsHost || ch.identifier.value); var dnsHost = ch.dnsHost || ch.identifier.value;
if (ch._test || !Challenge._getCache[ch.token]) { if (ch._test || !Challenge._getCache[ch.token]) {
console.info('\n\n\n\n\n');
console.info('#################################');
console.info('# Step 3: Get Domain TXT Record #');
console.info('#################################');
Challenge._getCache[ch.token] = true; Challenge._getCache[ch.token] = true;
console.info(""); console.info('');
console.info("[ACME " + ch.type + " '" + altname + "' REQUEST]: " + ch.status); console.info(
'[ACME ' + ch.type + " '" + altname + "' REQUEST]: " + ch.status
);
console.info("The '" + ch.type + "' challenge request has arrived!"); console.info("The '" + ch.type + "' challenge request has arrived!");
console.info('dig TXT ' + dnsHost); console.info('dig TXT ' + dnsHost);
console.info("(paste in the \"DNS Authorization\" you received a moment ago to respond)"); console.info(
process.stdout.write("> "); '(paste in the "DNS Authorization" you received a moment ago to respond)'
);
process.stdout.write('> ');
} }
return new Promise(function (resolve, reject) { return new Promise(function(resolve, reject) {
process.stdin.resume(); process.stdin.resume();
process.stdin.once('error', reject); process.stdin.once('error', reject);
process.stdin.once('data', function (chunk) { process.stdin.once('data', function(chunk) {
process.stdin.pause(); process.stdin.pause();
var result = chunk.toString('utf8').trim(); var result = chunk.toString('utf8').trim();
try { try {
result = JSON.parse(result); result = JSON.parse(result);
} catch(e) { } catch (e) {
args.challenge.dnsAuthorization = result; args.challenge.dnsAuthorization = result;
result = args.challenge; result = args.challenge;
} }
if (result.dnsAuthorization) { if (result.dnsAuthorization) {
resolve(result); setTimeout(function() {
resolve(result);
}, 1000);
return; return;
} }
// The return value will checked. It must not be 'undefined'. // The return value will checked. It must not be 'undefined'.
resolve(null); setTimeout(function() {
resolve(null);
}, 1000);
}); });
}); });
}; };
@ -127,15 +209,15 @@ Challenge._getCache = {};
function dnsChallengeToJson(ch) { function dnsChallengeToJson(ch) {
return { return {
type: ch.type type: ch.type,
, altname: ch.altname altname: ch.altname,
, identifier: ch.identifier identifier: ch.identifier,
, wildcard: ch.wildcard wildcard: ch.wildcard,
, expires: ch.expires expires: ch.expires,
, token: ch.token token: ch.token,
, thumbprint: ch.thumbprint thumbprint: ch.thumbprint,
, keyAuthorization: ch.keyAuthorization keyAuthorization: ch.keyAuthorization,
, dnsHost: ch.dnsHost dnsHost: ch.dnsHost,
, dnsAuthorization: ch.dnsAuthorization dnsAuthorization: ch.dnsAuthorization
}; };
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "acme-dns-01-cli", "name": "acme-dns-01-cli",
"version": "3.0.7", "version": "3.1.0",
"description": "A manual (interactive CLI) dns-based strategy for Greenlock / Let's Encrypt / ACME DNS-01 challenges", "description": "A manual (interactive CLI) dns-based strategy for Greenlock / Let's Encrypt / ACME DNS-01 challenges",
"homepage": "https://greenlock.domains/", "homepage": "https://greenlock.domains/",
"main": "index.js", "main": "index.js",
@ -29,5 +29,8 @@
"bugs": { "bugs": {
"url": "https://git.rootprojects.org/root/acme-dns-01-cli.js/issues" "url": "https://git.rootprojects.org/root/acme-dns-01-cli.js/issues"
}, },
"dependencies": {} "dependencies": {},
"devDependencies": {
"acme-dns-01-test": "^3.1.0"
}
} }

24
test.js
View File

@ -1,18 +1,22 @@
'use strict'; 'use strict';
var tester = require('greenlock-challenge-test'); var tester = require('acme-dns-01-test');
var type = 'dns-01'; var type = 'dns-01';
var challenger = require('greenlock-challenge-dns').create({}); var challenger = require('./index.js').create({});
// The dry-run tests can pass on, literally, 'example.com' // The dry-run tests can pass on, literally, 'example.com'
// but the integration tests require that you have control over the domain // but the integration tests require that you have control over the domain
var domain = '*.example.com'; var zone = 'example.com';
tester.test(type, domain, challenger).then(function () { tester
console.info("PASS"); // will test example.com, foo.example.com, *.foo.example.com
}).catch(function (err) { .testZone(type, zone, challenger)
console.error("FAIL"); .then(function() {
console.error(err); console.info('PASS');
process.exit(20); })
}); .catch(function(err) {
console.error('FAIL');
console.error(err);
process.exit(20);
});