diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..7433ee4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "printWidth": 80, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false +} diff --git a/README.md b/README.md index 34d126f..08c79b7 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ and [ACME.js](https://git.rootprojects.org/root/acme-v2.js). _acme-challenge.example.com TXT xxxxxxxxxxxxxxxx TTL 60 ``` -* Prints the ACME challenge DNS Host and DNS Key Authorization Digest to the terminal - * (waits for you to hit enter before continuing) -* Let's you know when the challenge as succeeded or failed, and is safe to remove. +- Prints the ACME challenge DNS Host and DNS Key Authorization Digest to the terminal + - (waits for you to hit enter before continuing) +- Let's you know when the challenge as succeeded or failed, and is safe to remove. Other ACME Challenge Reference Implementations: -* [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-dns-01-cli**](https://git.rootprojects.org/root/acme-dns-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-dns-01-cli**](https://git.rootprojects.org/root/acme-dns-01-cli.js.git) ## Install @@ -31,16 +31,16 @@ If you have `greenlock@v2.6` or lower, you'll need the old `le-challenge-dns@2.x ## Usage -```bash +```js var Greenlock = require('greenlock'); Greenlock.create({ - ... -, challenges: { 'http-01': require('acme-http-01-fs') - , 'dns-01': require('acme-dns-01-cli').create({ debug: true }) - , 'tls-alpn-01': require('acme-tls-alpn-01-cli') - } - ... + challenges: { + 'http-01': require('acme-http-01-fs'), + 'dns-01': require('acme-dns-01-cli').create({ debug: true }), + '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 function approveDomains(opts) { - ... - + // ... if (!opts.challenges) { opts.challenges = {}; } opts.challenges['dns-01'] = acmeDns01Cli; - opts.challenges['http-01'] = ... return Promise.resolve({ ... }); } @@ -66,36 +64,37 @@ it will require 6 individual challenges. For ACME Challenge: -* `set(opts)` -* `remove(opts)` +- `set(opts)` +- `remove(opts)` The `dns-01` strategy supports wildcards (whereas `http-01` does not). The options object has whatever options were set in `approveDomains()` as well as the `challenge`, which looks like this: -```js -{ challenge: { - identifier: { type: 'dns', value: 'example.com' - , wildcard: true - , altname: '*.example.com' - , type: 'dns-01' - , token: 'xxxxxx' - , keyAuthorization: 'xxxxxx.abc123' - , dnsHost: '_acme-challenge.example.com' - , dnsAuthorization: 'xyz567' - , expires: '1970-01-01T00:00:00Z' +```json +{ + "challenge": { + "identifier": { "type": "dns", "value": "example.com" }, + "wildcard": true, + "altname": "*.example.com", + "type": "dns-01", + "token": "xxxxxx", + "keyAuthorization": "xxxxxx.abc123", + "dnsHost": "_acme-challenge.example.com", + "dnsAuthorization": "xyz567", + "expires": "1970-01-01T00:00:00Z" } } ``` 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: -* `get(limitedOpts)` +- `get(limitedOpts)` 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. @@ -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 a NameServer (which currently there is not), it would probably look like this: -```js -{ challenge: { - type: 'dns-01' - , identifier: { type: 'dns', value: 'example.com' } - , token: 'abc123' - , dnsHost: '_acme-challenge.example.com' +```json +{ + "challenge": { + "type": "dns-01", + "identifier": { "type": "dns", "value": "example.com" }, + "token": "abc123", + "dnsHost": "_acme-challenge.example.com" } } ``` diff --git a/index.js b/index.js index f7f1e14..1e42392 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,9 @@ 'use strict'; /*global Promise*/ - var Challenge = module.exports; // If your implementation needs config options, set them. Otherwise, don't bother (duh). -Challenge.create = function (config) { - +Challenge.create = function(config) { var challenger = {}; // 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 // (the calling functions check function.length and then Promisify accordingly) - // Called when it's tiem to set the challenge - challenger.set = function (opts, cb) { + // Fetches an array of zone name strings + 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); }; // Called when it's time to remove the challenge - challenger.remove = function (opts) { - return Challenge._removeDns(opts); + challenger.remove = function(opts, cb) { + return Challenge._removeDns(opts, cb); }; // Optional (only really useful for http and testing) // Called when the challenge needs to be retrieved - challenger.get = function (opts) { + challenger.get = function(opts) { return Challenge._getDns(opts); }; @@ -38,88 +41,167 @@ Challenge.create = function (config) { 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 -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 (!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(); } 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("You're about to receive the following DNS query:"); - console.info(""); - console.info("\tTXT\t" + ch.dnsHost + "\t" + ch.dnsAuthorization + "\tTTL 60"); - console.info(""); + console.info(''); + console.info( + '\tTXT\t' + ch.dnsHost + '\t' + ch.dnsAuthorization + '\tTTL 60' + ); + console.info(''); if (ch.debug) { - console.info("Debug Info:"); - console.info(""); - console.info(JSON.stringify(dnsChallengeToJson(ch), null, ' ').replace(/^/gm, '\t')); - console.info(""); + console.info('Debug Info:'); + 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("[Press the ANY key to continue...]"); + console.info( + '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.once('data', function () { + process.stdin.once('data', function() { 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 -Challenge._removeDns = function (args) { +Challenge._removeDns = function(args, cb) { 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("Challenge complete. You may now remove the DNS-01 challenge record:"); - console.info(""); - console.info("\tTXT\t" + ch.altname + "\t" + ch.dnsAuthorization); - console.info(""); + console.info( + 'Challenge complete. You may now remove the DNS-01 challenge record:' + ); + 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), // 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; // because the way to mock a DNS challenge is weird - var altname = (ch.altname || ch.dnsHost || ch.identifier.value); - var dnsHost = (ch.dnsHost || ch.identifier.value); + var altname = ch.altname || ch.dnsHost || ch.identifier.value; + var dnsHost = ch.dnsHost || ch.identifier.value; 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; - console.info(""); - console.info("[ACME " + ch.type + " '" + altname + "' REQUEST]: " + ch.status); + console.info(''); + console.info( + '[ACME ' + ch.type + " '" + altname + "' REQUEST]: " + ch.status + ); console.info("The '" + ch.type + "' challenge request has arrived!"); console.info('dig TXT ' + dnsHost); - console.info("(paste in the \"DNS Authorization\" you received a moment ago to respond)"); - process.stdout.write("> "); + console.info( + '(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.once('error', reject); - process.stdin.once('data', function (chunk) { + process.stdin.once('data', function(chunk) { process.stdin.pause(); var result = chunk.toString('utf8').trim(); try { result = JSON.parse(result); - } catch(e) { + } catch (e) { args.challenge.dnsAuthorization = result; result = args.challenge; } if (result.dnsAuthorization) { - resolve(result); + setTimeout(function() { + resolve(result); + }, 1000); return; } // 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) { return { - type: ch.type - , altname: ch.altname - , identifier: ch.identifier - , wildcard: ch.wildcard - , expires: ch.expires - , token: ch.token - , thumbprint: ch.thumbprint - , keyAuthorization: ch.keyAuthorization - , dnsHost: ch.dnsHost - , dnsAuthorization: ch.dnsAuthorization + type: ch.type, + altname: ch.altname, + identifier: ch.identifier, + wildcard: ch.wildcard, + expires: ch.expires, + token: ch.token, + thumbprint: ch.thumbprint, + keyAuthorization: ch.keyAuthorization, + dnsHost: ch.dnsHost, + dnsAuthorization: ch.dnsAuthorization }; } diff --git a/package.json b/package.json index 7695e5c..5133d14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", "homepage": "https://greenlock.domains/", "main": "index.js", @@ -29,5 +29,8 @@ "bugs": { "url": "https://git.rootprojects.org/root/acme-dns-01-cli.js/issues" }, - "dependencies": {} + "dependencies": {}, + "devDependencies": { + "acme-dns-01-test": "^3.1.0" + } } diff --git a/test.js b/test.js index 6bc6e23..cc21c11 100644 --- a/test.js +++ b/test.js @@ -1,18 +1,22 @@ 'use strict'; -var tester = require('greenlock-challenge-test'); +var tester = require('acme-dns-01-test'); 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' // 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 () { - console.info("PASS"); -}).catch(function (err) { - console.error("FAIL"); - console.error(err); - process.exit(20); -}); +tester + // will test example.com, foo.example.com, *.foo.example.com + .testZone(type, zone, challenger) + .then(function() { + console.info('PASS'); + }) + .catch(function(err) { + console.error('FAIL'); + console.error(err); + process.exit(20); + });