| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | # Let's Encrypt + DNS = [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js.git)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | | Built by [Root](https://rootprojects.org) for [Hub](https://rootprojects.org/hub/) | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | An ACME dns-01 test harness for Let's Encrypt integrations. | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | | [ACME HTTP-01](https://git.rootprojects.org/root/acme-http-01-test.js) | 
					
						
							|  |  |  | | [ACME DNS-01](https://git.rootprojects.org/root/acme-dns-01-test.js) | 
					
						
							|  |  |  | | [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) | 
					
						
							|  |  |  | | [Greenlock.js](https://git.rootprojects.org/root/greenlock.js) | 
					
						
							|  |  |  | | [ACME.js](https://git.rootprojects.org/root/acme.js) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This was specificially designed for [ACME.js](https://git.coolaj86.com/coolaj86/acme-v2.js) | 
					
						
							|  |  |  | and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js), | 
					
						
							|  |  |  | but will be generically useful to any JavaScript DNS plugin for Let's Encrypt. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm install --save-dev acme-dns-01-test@3.x | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!--
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npx acme-dns-01-test --module /path/to/module.js --foo-user --bar--token | 
					
						
							|  |  |  | ``` | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | --> | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | # How Let's Encrypt works with DNS
 | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | In order to validate **wildcard**, **localhost**, and **private domains** through Let's Encrypt, | 
					
						
							|  |  |  | you must use set some special TXT records in your domain's DNS. | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | This is called the **ACME DNS-01 Challenge** | 
					
						
							| 
									
										
										
										
											2019-06-06 05:41:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | For example: | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | ```txt | 
					
						
							|  |  |  | dig TXT example.com | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ;; QUESTION SECTION: | 
					
						
							|  |  |  | ;_acme-challenge.example.com.		IN	TXT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ;; ANSWER SECTION: | 
					
						
							|  |  |  | _acme-challenge.example.com.	300	IN	TXT	"xxxxxxx" | 
					
						
							|  |  |  | _acme-challenge.example.com.	300	IN	TXT	"xxxxxxx" | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | ## ACME DNS-01 Challenge Process
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The ACME DNS-01 Challenge process works like this: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1. The ACME client order's an SSL Certificate from Let's Encrypt | 
					
						
							|  |  |  | 2. Let's Encrypt asks for validation of the domains on the certificate | 
					
						
							|  |  |  | 3. The ACME client asks to use DNS record verification | 
					
						
							|  |  |  | 4. Let's Encrypt gives a DNS authorization token | 
					
						
							|  |  |  | 5. The ACME client manipulates the token and sets TXT record with the result | 
					
						
							|  |  |  | 6. Let's Encrypt checks the TXT record from DNS clients in diverse locations | 
					
						
							|  |  |  | 7. The ACME client gets a certificate if the validate passes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Using a Let's Encrypt DNS plugin
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Each plugin will define some options, such as an api key, or username and password | 
					
						
							|  |  |  | that are specific to that plugin. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Other than that, they're all used the same. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## ACME.js + Let's Encrypt DNS-01
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This is how an ACME challenge module is with ACME.js: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | acme.certificates.create({ | 
					
						
							|  |  |  | 	accountKey, | 
					
						
							|  |  |  | 	csr, | 
					
						
							|  |  |  | 	domains, | 
					
						
							|  |  |  | 	challenges: { | 
					
						
							|  |  |  | 		'dns-01': require('acme-dns-01-MODULE_NAME').create({ | 
					
						
							|  |  |  | 			fooUser: 'A_PLUGIN_SPECIFIC_OPTION', | 
					
						
							|  |  |  | 			barToken: 'A_PLUGIN_SPECIFIC_OPTION' | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Greenlock + Let's Encrypt DNS-01
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This is how modules are used with Greenlock / Greenlock Express | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Global** default: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | greenlock.manager.defaults({ | 
					
						
							|  |  |  | 	challenges: { | 
					
						
							|  |  |  | 		'dns-01': { | 
					
						
							|  |  |  | 			module: 'acme-dns-01-_MODULE_NAME', | 
					
						
							|  |  |  | 			fooUser: 'A_PLUGIN_SPECIFIC_OPTION', | 
					
						
							|  |  |  | 			barToken: 'A_PLUGIN_SPECIFIC_OPTION' | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Per-Site** config: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | greenlock.add({ | 
					
						
							|  |  |  | 	subject: 'example.com', | 
					
						
							|  |  |  | 	altnames: ['example.com', '*.example.com', 'foo.bar.example.com'], | 
					
						
							|  |  |  | 	challenges: { | 
					
						
							|  |  |  | 		'dns-01': { | 
					
						
							|  |  |  | 			module: 'acme-dns-01-YOUR_MODULE_NAME', | 
					
						
							|  |  |  | 			fooUser: 'A_PLUGIN_SPECIFIC_OPTION', | 
					
						
							|  |  |  | 			barToken: 'A_PLUGIN_SPECIFIC_OPTION' | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # The Easy Way to Build a Plugin
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This repo includes **unit test suite** which makes it _very_ easy to create a plugin. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | You can start with a **template file** that will fail all of the tests, and just | 
					
						
							|  |  |  | build until you pass all of the tests. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | After that, you can **test the Greenlock CLI** to see if | 
					
						
							|  |  |  | you actually get a valid SSL certificate. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Overview
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | There are only a few methods to implement - just basic CRUD operations. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For most serivices these are very simple to implement | 
					
						
							|  |  |  | (see the **reference implementations** down below). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Some enterprise-y services are more difficult as they may have special | 
					
						
							|  |  |  | rules about zones (Google Cloud) or intricate authentication schemes (AWS). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | init({ request }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | zones({ dnsHosts }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | set({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | get({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | remove({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Plugin Outline
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This is an even better starter template below, | 
					
						
							|  |  |  | but this outline shows the bare bones of a plugin. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var MyModule = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MyModule.create = function (options) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var m = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m.init = async function ({ request }) { | 
					
						
							|  |  |  |         // (optional) initialize your module | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m.zones = async function ({ dnsHosts }) { | 
					
						
							|  |  |  |         // return a list of "Zones" or "Apex Domains" (i.e. example.com, NOT foo.example.com) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m.set = async function ({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) { | 
					
						
							|  |  |  |         // set a TXT record for dnsHost with keyAuthorizationDigest as the value | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m.get = async function ({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) { | 
					
						
							|  |  |  |         // check that the EXACT a TXT record that was set, exists, and return it | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m.remove = async function ({ challenge: { dnsZone, dnsPrefix, dnsHost, keyAuthorizationDigest } }) { | 
					
						
							|  |  |  |         // remove the exact TXT record that was set | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return m; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Using the Test Suite
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Test setup: | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | var tester = require('acme-dns-01-test'); | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | var YOUR_PLUGIN = require('./YOUR-CHALLENGE-STRATEGY'); | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | var challenger = YOUR_PLUGIN.create({ | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 	YOUR_TOKEN_OPTION: 'SOME_API_KEY' | 
					
						
							| 
									
										
										
										
											2019-06-06 06:20:19 +00:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | ``` | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | Run the tests: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ``` | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | var zone = 'example.com'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | tester.testZone('dns-01', zone, challenger).then(function() { | 
					
						
							|  |  |  | 	console.info('PASS'); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | **Note**: Special DNS services, like **DuckDNS**, only give you a **single sub-domain**, | 
					
						
							|  |  |  | not a full "zone". You can test them too: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Some DNS services, such as **DuckDNS**, only give you a **single sub-domain**, | 
					
						
							|  |  |  | not not _multiple_ records in a zone. Testing them is slightly different: | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | var record = 'foo.example.com'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | tester.testRecord('dns-01', record, challenger).then(function() { | 
					
						
							|  |  |  | 	console.info('PASS'); | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-15 22:50:02 -06:00
										 |  |  | ## Reference Implementations
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | - Compatibility | 
					
						
							|  |  |  |   - [x] Let's Encrypt v2.1 / ACME draft 18 | 
					
						
							|  |  |  |   - [x] Node v6+ | 
					
						
							|  |  |  |   - [x] Chrome, Firefox, Safari, Edge, etc | 
					
						
							|  |  |  | - Quality | 
					
						
							|  |  |  |   - [x] Written in VanillaJS | 
					
						
							|  |  |  |   - [x] No compliers or build scripts | 
					
						
							|  |  |  |   - [x] Simple, minimal code, in a single file | 
					
						
							|  |  |  |   - [x] **Zero dependencies** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | These libraries are useful as a model for any plugins that you create. | 
					
						
							| 
									
										
										
										
											2019-04-15 22:50:02 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | - dns-01 | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  |   - [`cli`](https://git.rootprojects.org/root/acme-dns-01-cli.js) | 
					
						
							|  |  |  |   - [`digitalocean`](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js) | 
					
						
							|  |  |  |   - [`vultr`](https://git.rootprojects.org/root/acme-dns-01-vultr.js) | 
					
						
							| 
									
										
										
										
											2019-07-11 23:03:06 -06:00
										 |  |  |   - [`gandi`](https://git.rootprojects.org/root/acme-dns-01-gandi.js) | 
					
						
							|  |  |  |   - [`duckdns`](https://git.rootprojects.org/root/acme-dns-01-duckdns.js) | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | - http-01 | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  |   - [`cli`](https://git.rootprojects.org/root/acme-http-01-cli.js) | 
					
						
							|  |  |  |   - [`fs`](https://git.rootprojects.org/root/acme-http-01-fs.js) | 
					
						
							| 
									
										
										
										
											2019-04-15 22:50:02 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | You can find other implementations by searching npm for [acme-http-01-](https://www.npmjs.com/search?q=acme-http-01-) | 
					
						
							|  |  |  | and [acme-dns-01-](https://www.npmjs.com/search?q=acme-dns-01-). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | If you are building a plugin, please let us know. | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | We may like to co-author and help maintain and promote your module. | 
					
						
							| 
									
										
										
										
											2019-06-06 06:15:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | <small>Note: In some cases (such as non-HTTP, or very complex APIs) you will not be able to maintain | 
					
						
							|  |  |  | browser compatibility. Other than than, if you keep your code simple, it will also work in browser | 
					
						
							|  |  |  | implementations of ACME.js.</small> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | # Example
 | 
					
						
							| 
									
										
										
										
											2019-04-15 22:50:02 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | See `example.js` (it works). | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 05:41:27 +00:00
										 |  |  | ## Starter Template
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Here's what you could start with. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | var tester = require('acme-dns-01-test'); | 
					
						
							| 
									
										
										
										
											2019-06-06 05:41:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // The dry-run tests can pass on, literally, 'example.com' | 
					
						
							|  |  |  | // but the integration tests require that you have control over the domain | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | var zone = 'example.com'; | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | var deps = {}; | 
					
						
							| 
									
										
										
										
											2019-06-06 06:49:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | tester | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 	.testZone('dns-01', zone, { | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | 		// Gives you the promisified `request` object for HTTP APIs | 
					
						
							|  |  |  | 		init: function(deps) { | 
					
						
							|  |  |  | 			request = deps.request; | 
					
						
							|  |  |  | 			return null; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 		// Should return an array of zone domain name strings | 
					
						
							|  |  |  | 		// (APIs that don't implement zones, such as DuckDNS, should return an empty array) | 
					
						
							|  |  |  | 		zones: function(opts) { | 
					
						
							|  |  |  | 			console.log('dnsHosts:', opts.dnsHosts); | 
					
						
							|  |  |  | 			throw new Error('_zone not implemented'); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 		// Should set a TXT record for dnsHost with dnsAuthorization and ttl || 300 | 
					
						
							|  |  |  | 		set: function(opts) { | 
					
						
							|  |  |  | 			console.log('set opts:', opts); | 
					
						
							|  |  |  | 			throw new Error('set not implemented'); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Should remove the *one* TXT record for dnsHost with dnsAuthorization | 
					
						
							|  |  |  | 		// Should NOT remove otherrecords for dnsHost (wildcard shares dnsHost with | 
					
						
							|  |  |  | 		// non-wildcard) | 
					
						
							|  |  |  | 		remove: function(opts) { | 
					
						
							|  |  |  | 			console.log('remove opts:', opts); | 
					
						
							|  |  |  | 			throw new Error('remove not implemented'); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Should get the record via the DNS server's API | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 		// (Note: gets different options than set or remove) | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 		get: function(opts) { | 
					
						
							|  |  |  | 			console.log('get opts:', opts); | 
					
						
							|  |  |  | 			throw new Error('get not implemented'); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	.then(function() { | 
					
						
							|  |  |  | 		console.info('PASS'); | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2019-06-06 05:41:27 +00:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | ## Full Detailed Example
 | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-15 22:50:02 -06:00
										 |  |  | Here's a quick pseudo stub-out of what a test-passing plugin object might look like: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | ```js | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | var deps = {}; | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 06:49:46 +00:00
										 |  |  | tester | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 	.testZone('dns-01', 'example.com', { | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | 		init: function({ request }) { | 
					
						
							|  |  |  | 			// { request: { get, post, put, delete } } | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | 			deps.request = request; | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | 			return null; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | 		zones: function({ dnsHosts }) { | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			// { dnsHosts: [ | 
					
						
							|  |  |  | 			//     '_acme-challenge.foo.example.com', | 
					
						
							|  |  |  | 			//     '_acme-challenge.bar.example.com' | 
					
						
							|  |  |  | 			//  ] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return YourApi( | 
					
						
							|  |  |  | 				'GET', | 
					
						
							|  |  |  | 				// Most Domain Zone apis don't have a search or filter option, | 
					
						
							|  |  |  | 				// but `opts` includes list of dnsHosts is provided just in case. | 
					
						
							|  |  |  | 				'https://exampledns.com/api/dns/zones?search=' + opts.dnsHosts.join(',') | 
					
						
							|  |  |  | 			).then(function(result) { | 
					
						
							|  |  |  | 				return result.zones.map(function(zone) { | 
					
						
							|  |  |  | 					return zone.name; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 		set: function(opts) { | 
					
						
							|  |  |  | 			var ch = opts.challenge; | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			// { type: 'dns-01' | 
					
						
							|  |  |  | 			// , identifier: { type: 'dns', value: 'foo.example.com' } | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 			// , wildcard: false | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			// , dnsHost: '_acme-challenge.foo.example.com' | 
					
						
							|  |  |  | 			// , dnsPrefix: '_acme-challenge.foo' | 
					
						
							|  |  |  | 			// , dnsZone: 'example.com' | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 			// , dnsAuthorization: 'zzzz' } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			return YourApi( | 
					
						
							|  |  |  | 				'POST', | 
					
						
							|  |  |  | 				'https://exampledns.com/api/dns/txt/' + ch.dnsZone + '/' + ch.dnsPrefix, | 
					
						
							|  |  |  | 				{ value: ch.dnsAuthorization } | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		get: function(query) { | 
					
						
							|  |  |  | 			var ch = query.challenge; | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			// { type: 'dns-01' | 
					
						
							|  |  |  | 			// , identifier: { type: 'dns', value: 'foo.example.com' } | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 			// , altname: '...' | 
					
						
							|  |  |  | 			// , dnsHost: '...' | 
					
						
							|  |  |  | 			// , wildcard: false } | 
					
						
							|  |  |  | 			// Note: query.identifier.value is different for http-01 than for dns-01 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			//       because of how a DNS query is different from an HTTP request | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			return YourApi( | 
					
						
							|  |  |  | 				'GET', | 
					
						
							|  |  |  | 				'https://exampledns.com/api/dns/txt/' + ch.dnsZone + '/' + ch.dnsPrefix | 
					
						
							|  |  |  | 			).then(function(secret) { | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 				return { dnsAuthorization: secret }; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		remove: function(opts) { | 
					
						
							|  |  |  | 			var ch = opts.challenge; | 
					
						
							|  |  |  | 			// same options as in `set()` (which are not the same as `get()` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 00:36:53 -06:00
										 |  |  | 			return YourApi( | 
					
						
							|  |  |  | 				'DELETE', | 
					
						
							|  |  |  | 				'https://exampledns.com/api/dns/txt/' + ch.dnsZone + '/' + ch.dnsPrefix | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	.then(function() { | 
					
						
							|  |  |  | 		console.info('PASS'); | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2019-06-06 06:49:46 +00:00
										 |  |  | ``` | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 06:49:46 +00:00
										 |  |  | Where `YourApi` might look something like this: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | var YourApi = function createApi(config) { | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | 	return function(method, url, body) { | 
					
						
							|  |  |  | 		return request({ | 
					
						
							|  |  |  | 			method: method, | 
					
						
							|  |  |  | 			url: url, | 
					
						
							|  |  |  | 			json: body || true, | 
					
						
							|  |  |  | 			headers: { | 
					
						
							|  |  |  | 				Authorization: 'Bearer ' + config.apiToken | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}).then(function(resp) { | 
					
						
							|  |  |  | 			return resp.body; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-15 13:25:11 -06:00
										 |  |  | Note: `request` is actually `@root/request`, but the API is the same as the standard `request`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Avoid using 3rd party API libraries where you can - they tend to bloat your dependencies and | 
					
						
							|  |  |  | add security risk. Instead, just use the API documentation and cURL examples. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 06:07:08 +00:00
										 |  |  | ### Two notes:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Note 1: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The `API.get()`, `API.set()`, and `API.remove()` is where you do your magic up to upload a file to the correct | 
					
						
							| 
									
										
										
										
											2019-04-07 15:55:48 -06:00
										 |  |  | location on an http serever, set DNS records, or add the appropriate data to the database that handles such things. | 
					
						
							| 
									
										
										
										
											2019-06-06 06:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Note 2: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 23:04:51 -06:00
										 |  |  | - When `altname` is `foo.example.com` the `dnsHost` will be `_acme-challenge.foo.example.com` | 
					
						
							|  |  |  | - When `altname` is `*.foo.example.com` the `dnsHost` will _still_ be `_acme-challenge.foo.example.com`!! | 
					
						
							|  |  |  | - When `altname` is `bar.foo.example.com` the `dnsHost` will be `_acme-challenge.bar.foo.example.com` | 
					
						
							| 
									
										
										
										
											2019-11-02 11:52:54 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | # We Build Let's Encrypt Plugins for You
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Want to get the experts involved? [Contact Root](acme-plugins@therootcompany.com) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | We can take it on ourselves, work within your team, or guide an outsourced team. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Turaround is typically a few days for simple modules with publicly available APIs. |