getting there...
This commit is contained in:
		
							parent
							
								
									11212d3a56
								
							
						
					
					
						commit
						b41f09df11
					
				
							
								
								
									
										55
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								README.md
									
									
									
									
									
								
							| @ -1,19 +1,44 @@ | |||||||
|  | [](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||||||
|  | 
 | ||||||
|  | | [letsencrypt](https://github.com/Daplie/node-letsencrypt) (library) | ||||||
|  | | [letsencrypt-cli](https://github.com/Daplie/letsencrypt-cli) | ||||||
|  | | [letsencrypt-express](https://github.com/Daplie/letsencrypt-express) | ||||||
|  | | [letsencrypt-koa](https://github.com/Daplie/letsencrypt-koa) | ||||||
|  | | [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi) | ||||||
|  | | | ||||||
|  | 
 | ||||||
| le-challenge-dns | le-challenge-dns | ||||||
| ================ | ================ | ||||||
| 
 | 
 | ||||||
| A dns-based strategy for node-letsencrypt for setting, and clearing ACME DNS-01 challenges issued by the ACME server. | A dns-based strategy for node-letsencrypt for setting, retrieving, | ||||||
|  | and clearing ACME DNS-01 challenges issued by the ACME server | ||||||
| 
 | 
 | ||||||
| DRAFT | It creates a subdomain record for `_acme-challenge` wich `challenge` | ||||||
| ----- | to be tested by the ACME server. | ||||||
| 
 | 
 | ||||||
| This details how any dns-based challenge will work with node-letsencrypt, but is not yet implemented specifically (though it is in the pipeline at present, obviously). | ``` | ||||||
|  | _acme-challenge.example.com   TXT   xxxxxxxxxxxxxxxx    TTL 60 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | * Safe to use with node cluster | ||||||
|  | * Safe to use with ephemeral services (Heroku, Joyent, etc) | ||||||
|  | 
 | ||||||
|  | Install | ||||||
|  | ------- | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | npm install --save le-challenge-dns@2.x | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| Usage | Usage | ||||||
| ----- | ----- | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| var leChallenge = require('le-challenge-dns').create({ | var leChallengeDns = require('le-challenge-dns').create({ | ||||||
|   ttl: 600 |   email: 'john.doe@example.com' | ||||||
|  | , refreshToken: '...' | ||||||
|  | , ttl: 60 | ||||||
|  | 
 | ||||||
| , debug: false | , debug: false | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -21,7 +46,11 @@ var LE = require('letsencrypt'); | |||||||
| 
 | 
 | ||||||
| LE.create({ | LE.create({ | ||||||
|   server: LE.stagingServerUrl                               // Change to LE.productionServerUrl in production |   server: LE.stagingServerUrl                               // Change to LE.productionServerUrl in production | ||||||
| , challenge: leChallenge | , challengeType: 'dns-01' | ||||||
|  | , challenges: { | ||||||
|  |     'dns-01': leChallengeDns | ||||||
|  |   } | ||||||
|  | , approvedDomains: [ 'example.com' ] | ||||||
| }); | }); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -33,11 +62,15 @@ Exposed Methods | |||||||
| 
 | 
 | ||||||
| For ACME Challenge: | For ACME Challenge: | ||||||
| 
 | 
 | ||||||
| * `set(opts, domain, key, val, done)` | * `set(opts, domain, challange, keyAuthorization, done)` | ||||||
| * `get(defaults, domain, key, done)` | * `get(defaults, domain, challenge, done)` | ||||||
| * `remove(defaults, domain, key, done)` | * `remove(defaults, domain, challenge, done)` | ||||||
|  | 
 | ||||||
|  | Note: `get()` is a no-op for `dns-01` and although `dns-01` does not use `keyAuthorization`, | ||||||
|  | it must be passed in as `null` to keep the correct method signature. | ||||||
| 
 | 
 | ||||||
| For node-letsencrypt internals: | For node-letsencrypt internals: | ||||||
| 
 | 
 | ||||||
| * `getOptions()` returns the internal defaults merged with the user-supplied options | * `getOptions()` returns the internal defaults merged with the user-supplied options | ||||||
| * `loopback(defaults, domain, key, value, done)` should test, by external means, that the ACME server's challenge server will succeed | * `loopback(defaults, domain, challange, keyAuthorization, done)` should test, by external means, that the ACME server's challenge server will succeed | ||||||
|  | * `test(opts, domain, challange, keyAuthorization, done)` runs set, loopback, remove, loopback | ||||||
|  | |||||||
							
								
								
									
										159
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | // See https://gitlab.com/pushrocks/cert/blob/master/ts/cert.hook.ts
 | ||||||
|  | 
 | ||||||
|  | var PromiseA = require('bluebird'); | ||||||
|  | var dns = PromiseA.promisifyAll(require('dns')); | ||||||
|  | var DDNS = require('ddns-cli'); | ||||||
|  | var fs = require('fs'); | ||||||
|  | var path = require('path'); | ||||||
|  | 
 | ||||||
|  | var cluster = require('cluster'); | ||||||
|  | var numCores = require('os').cpus().length; | ||||||
|  | var defaults = { | ||||||
|  |   oauth3: 'oauth3.org' | ||||||
|  | , debug: false | ||||||
|  | , acmeChallengeDns: '_acme-challenge.' // _acme-challenge.example.com TXT xxxxxxxxxxxxxxxx
 | ||||||
|  | , memstoreConfig: { | ||||||
|  |     sock: '/tmp/memstore.sock' | ||||||
|  | 
 | ||||||
|  |     // If left 'null' or 'undefined' this defaults to a similar memstore
 | ||||||
|  |     // with no special logic for 'cookie' or 'expires'
 | ||||||
|  |   , store: null | ||||||
|  | 
 | ||||||
|  |     // a good default to use for instances where you might want
 | ||||||
|  |     // to cluster or to run standalone, but with the same API
 | ||||||
|  |   , serve: cluster.isMaster | ||||||
|  |   , connect: cluster.isWorker | ||||||
|  |   , standalone: (1 === numCores) // overrides serve and connect
 | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var Challenge = module.exports; | ||||||
|  | 
 | ||||||
|  | Challenge.create = function (options) { | ||||||
|  |   var store = require('memstore-cluster'); | ||||||
|  |   var results = {}; | ||||||
|  | 
 | ||||||
|  |   Object.keys(Challenge).forEach(function (key) { | ||||||
|  |     results[key] = Challenge[key]; | ||||||
|  |   }); | ||||||
|  |   results.create = undefined; | ||||||
|  | 
 | ||||||
|  |   Object.keys(defaults).forEach(function (key) { | ||||||
|  |     if (!(key in options)) { | ||||||
|  |       options[key] = defaults[key]; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   results._options = options; | ||||||
|  | 
 | ||||||
|  |   results.getOptions = function () { | ||||||
|  |     return results._options; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   // TODO fix race condition at startup
 | ||||||
|  |   results._memstore = options.memstore; | ||||||
|  | 
 | ||||||
|  |   if (!results._memstore) { | ||||||
|  |     store.create(options.memstoreConfig).then(function (store) { | ||||||
|  |       // same api as new sqlite3.Database(options.filename)
 | ||||||
|  | 
 | ||||||
|  |       results._memstore = store; | ||||||
|  | 
 | ||||||
|  |       // app.use(expressSession({ secret: 'keyboard cat', store: store }));
 | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return results; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // NOTE: the "args" here in `set()` are NOT accessible to `get()` and `remove()`
 | ||||||
|  | // They are provided so that you can store them in an implementation-specific way
 | ||||||
|  | // if you need access to them.
 | ||||||
|  | //
 | ||||||
|  | Challenge.set = function (args, domain, challenge, keyAuthorization, done) { | ||||||
|  |   // Note: keyAuthorization is not used for dns-01
 | ||||||
|  | 
 | ||||||
|  |   this._memstore.set(domain, { | ||||||
|  |     email: args.email | ||||||
|  |   , refreshToken: args.refreshToken | ||||||
|  |   }, function () { | ||||||
|  | 
 | ||||||
|  |     return DDNS.run({ | ||||||
|  |       email: args.email | ||||||
|  |     , refreshToken: args.refreshToken | ||||||
|  | 
 | ||||||
|  |     , name: args.test + args.acmeChallengeDns + '.' + domain | ||||||
|  |     , type: "TXT" | ||||||
|  |     , value: challenge | ||||||
|  |     , ttl: 60 | ||||||
|  |     }).then(function () { done(null); }, done); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // NOTE: the "defaults" here are still merged and templated, just like "args" would be,
 | ||||||
|  | // but if you specifically need "args" you must retrieve them from some storage mechanism
 | ||||||
|  | // based on domain and key
 | ||||||
|  | //
 | ||||||
|  | Challenge.get = function (defaults, domain, challenge, done) { | ||||||
|  |   throw new Error("Challenge.get() does not need an implementation for dns-01. (did you mean Challenge.loopback?)"); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Challenge.remove = function (defaults, domain, challenge, done) { | ||||||
|  |   this._memstore.get(domain, function (data) { | ||||||
|  |     return DDNS.run({ | ||||||
|  |       email: data.email | ||||||
|  |     , refreshToken: data.refreshToken | ||||||
|  | 
 | ||||||
|  |     , name: defaults.test + defaults.acmeChallengeDns + '.' + domain | ||||||
|  |     , type: "TXT" | ||||||
|  |     , value: challenge | ||||||
|  |     , ttl: 60 | ||||||
|  | 
 | ||||||
|  |     , remove: true | ||||||
|  |     }).then(function () { | ||||||
|  | 
 | ||||||
|  |       done(null); | ||||||
|  |     }, done).then(function () { | ||||||
|  |       this._memstore.remove(domain); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // same as get, but external
 | ||||||
|  | Challenge.loopback = function (defaults, domain, challenge, done) { | ||||||
|  |   var subdomain = defaults.test + defaults.acmeChallengeDns + '.' + domain; | ||||||
|  |   dns.resolveAsync(subdomain).then(function () { done(null); }, done); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Challenge.test = function (args, domain, challenge, keyAuthorization, done) { | ||||||
|  |   // Note: keyAuthorization is not used for dns-01
 | ||||||
|  | 
 | ||||||
|  |   args.test = '_test.'; | ||||||
|  | 
 | ||||||
|  |   Challenge.set(args, domain, challenge, keyAuthorization, function (err) { | ||||||
|  |     if (err) { done(err); return; } | ||||||
|  | 
 | ||||||
|  |     Challenge.loopback(defaults, domain, challenge, function (err) { | ||||||
|  |       if (err) { done(err); return; } | ||||||
|  | 
 | ||||||
|  |       Challenge.remove(defaults, domain, challenge, function (err) { | ||||||
|  |         if (err) { done(err); return; } | ||||||
|  | 
 | ||||||
|  |         // TODO needs to use native-dns so that specific nameservers can be used
 | ||||||
|  |         // (otherwise the cache will still have the old answer)
 | ||||||
|  |         done(); | ||||||
|  |         /* | ||||||
|  |         Challenge.loopback(defaults, domain, challenge, function (err) { | ||||||
|  |           if (err) { done(err); return; } | ||||||
|  | 
 | ||||||
|  |           done(); | ||||||
|  |         }); | ||||||
|  |         */ | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										36
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | { | ||||||
|  |   "name": "le-challenge-dns", | ||||||
|  |   "version": "2.0.0", | ||||||
|  |   "description": "A dns-based strategy for node-letsencrypt for setting, retrieving, and clearing ACME DNS-01 challenges issued by the ACME server", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "test": "node test.js" | ||||||
|  |   }, | ||||||
|  |   "repository": { | ||||||
|  |     "type": "git", | ||||||
|  |     "url": "git+https://github.com/Daplie/le-challenge-dns.git" | ||||||
|  |   }, | ||||||
|  |   "keywords": [ | ||||||
|  |     "le", | ||||||
|  |     "letsencrypt", | ||||||
|  |     "le-challenge", | ||||||
|  |     "le-challenge-", | ||||||
|  |     "le-challenge-dns", | ||||||
|  |     "acme", | ||||||
|  |     "challenge", | ||||||
|  |     "dns", | ||||||
|  |     "cluster", | ||||||
|  |     "ephemeral" | ||||||
|  |   ], | ||||||
|  |   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||||
|  |   "license": "(MIT OR Apache-2.0)", | ||||||
|  |   "bugs": { | ||||||
|  |     "url": "https://github.com/Daplie/le-challenge-dns/issues" | ||||||
|  |   }, | ||||||
|  |   "homepage": "https://github.com/Daplie/le-challenge-dns#readme", | ||||||
|  |   "dependencies": { | ||||||
|  |     "daplie-dns": "git+https://github.com/Daplie/daplie-cli-dns.git#master", | ||||||
|  |     "daplie-domains": "git+https://github.com/Daplie/daplie-cli-domains.git#master", | ||||||
|  |     "oauth3-cli": "git+https://github.com/OAuth3/oauth3-cli.git#master" | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user