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