v3.1.0: support zones test, made prettier
				
					
				
			This commit is contained in:
		
							parent
							
								
									32030b9d80
								
							
						
					
					
						commit
						7b079bcf3a
					
				
							
								
								
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "bracketSpacing": true, | ||||
|   "printWidth": 80, | ||||
|   "singleQuote": true, | ||||
|   "tabWidth": 2, | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": false | ||||
| } | ||||
							
								
								
									
										72
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								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" | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										178
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								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(); | ||||
|     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) { | ||||
|         setTimeout(function() { | ||||
|           resolve(result); | ||||
|         }, 1000); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // The return value will checked. It must not be 'undefined'.
 | ||||
|       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 | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -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" | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										20
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								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"); | ||||
| 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); | ||||
| }); | ||||
|   }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user