v2.7.23: regression bugfixes: node v6 and cloudflare dns-01

This commit is contained in:
AJ ONeal 2019-06-03 09:12:11 +00:00
parent 93b9158b1b
commit 5316af67be
6 changed files with 781 additions and 713 deletions

1310
index.js

File diff suppressed because it is too large Load Diff

21
lib/compat.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
function requireBluebird() {
try {
return require('bluebird');
} catch(e) {
console.error("");
console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support.");
console.error("EASY FIX: `npm install --save bluebird`");
console.error("");
throw e;
}
}
if ('undefined' === typeof Promise) {
global.Promise = requireBluebird();
}
if ('function' !== typeof require('util').promisify) {
require('util').promisify = requireBluebird().promisify;
}

View File

@ -1,11 +1,7 @@
'use strict'; 'use strict';
/*global Promise*/
require('./compat.js');
var PromiseA;
try {
PromiseA = require('bluebird');
} catch(e) {
PromiseA = global.Promise;
}
var util = require('util'); var util = require('util');
function promisifyAll(obj) { function promisifyAll(obj) {
var aobj = {}; var aobj = {};
@ -42,7 +38,7 @@ module.exports.create = function (gl) {
// TODO check response header on request for cache time // TODO check response header on request for cache time
if ((now - gl._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) { if ((now - gl._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
return PromiseA.resolve(gl._ipc.acmeUrls); return Promise.resolve(gl._ipc.acmeUrls);
} }
// TODO acme-v2/nocompat // TODO acme-v2/nocompat
@ -79,7 +75,7 @@ module.exports.create = function (gl) {
+ " and 'rsaKeySize' must be 2048 or greater." + " and 'rsaKeySize' must be 2048 or greater."
); );
err.code = 'E_ARGS'; err.code = 'E_ARGS';
return PromiseA.reject(err); return Promise.reject(err);
} }
return utils.testEmail(args.email).then(function () { return utils.testEmail(args.email).then(function () {
@ -156,9 +152,9 @@ module.exports.create = function (gl) {
if (newAccountKeypair) { if (newAccountKeypair) {
accountKeypairPromise = gl.store.accounts.setKeypairAsync(args, keypair); accountKeypairPromise = gl.store.accounts.setKeypairAsync(args, keypair);
} }
return PromiseA.resolve(accountKeypairPromise).then(function () { return Promise.resolve(accountKeypairPromise).then(function () {
// TODO move templating of arguments to right here? // TODO move templating of arguments to right here?
if (!gl.store.accounts.setAsync) { return PromiseA.resolve({ keypair: keypair }); } if (!gl.store.accounts.setAsync) { return Promise.resolve({ keypair: keypair }); }
return gl.store.accounts.setAsync(args, reg).then(function (account) { return gl.store.accounts.setAsync(args, reg).then(function (account) {
if (account && 'object' !== typeof account) { if (account && 'object' !== typeof account) {
throw new Error("store.accounts.setAsync should either return 'null' or an object with at least a string 'id'"); throw new Error("store.accounts.setAsync should either return 'null' or an object with at least a string 'id'");
@ -181,7 +177,7 @@ module.exports.create = function (gl) {
if (gl.store.accounts.checkAsync) { if (gl.store.accounts.checkAsync) {
accountPromise = core.accounts.checkAsync(args); accountPromise = core.accounts.checkAsync(args);
} }
return PromiseA.resolve(accountPromise).then(function (account) { return Promise.resolve(accountPromise).then(function (account) {
if (!account) { return core.accounts.registerAsync(args); } if (!account) { return core.accounts.registerAsync(args); }
if (account.keypair) { return account; } if (account.keypair) { return account; }
@ -201,7 +197,7 @@ module.exports.create = function (gl) {
var requiredArgs = ['accountId', 'email', 'domains', 'domain']; var requiredArgs = ['accountId', 'email', 'domains', 'domain'];
if (!(args.account && (args.account.id || args.account.kid)) if (!(args.account && (args.account.id || args.account.kid))
&& !requiredArgs.some(function (key) { return -1 !== Object.keys(args).indexOf(key); })) { && !requiredArgs.some(function (key) { return -1 !== Object.keys(args).indexOf(key); })) {
return PromiseA.reject(new Error( return Promise.reject(new Error(
"In order to register or retrieve an account one of '" + requiredArgs.join("', '") + "' must be present" "In order to register or retrieve an account one of '" + requiredArgs.join("', '") + "' must be present"
)); ));
} }
@ -213,7 +209,7 @@ module.exports.create = function (gl) {
// we can re-register the same account until we're blue in the face and it's all the same // we can re-register the same account until we're blue in the face and it's all the same
// of course, we can also skip the lookup if we do store the account, but whatever // of course, we can also skip the lookup if we do store the account, but whatever
if (!gl.store.accounts.checkAsync) { return PromiseA.resolve(null); } if (!gl.store.accounts.checkAsync) { return Promise.resolve(null); }
return gl.store.accounts.checkAsync(args).then(function (account) { return gl.store.accounts.checkAsync(args).then(function (account) {
if (!account) { if (!account) {
@ -240,7 +236,7 @@ module.exports.create = function (gl) {
args = utils.tplCopy(copy); args = utils.tplCopy(copy);
if (!Array.isArray(args.domains)) { if (!Array.isArray(args.domains)) {
return PromiseA.reject(new Error('args.domains should be an array of domains')); return Promise.reject(new Error('args.domains should be an array of domains'));
} }
//if (-1 === args.domains.indexOf(args.subject)) // TODO relax the constraint once acme-v2 handles subject? //if (-1 === args.domains.indexOf(args.subject)) // TODO relax the constraint once acme-v2 handles subject?
if (args.subject !== args.domains[0]) { if (args.subject !== args.domains[0]) {
@ -250,7 +246,7 @@ module.exports.create = function (gl) {
console.warn('\topts.domains: (set by you in approveDomains()) ' + args.domains.join(',')); console.warn('\topts.domains: (set by you in approveDomains()) ' + args.domains.join(','));
console.warn("Updating your code will prevent weird, random, hard-to-repro bugs during renewals"); console.warn("Updating your code will prevent weird, random, hard-to-repro bugs during renewals");
console.warn("(also this will be required in the next major version of greenlock)"); console.warn("(also this will be required in the next major version of greenlock)");
//return PromiseA.reject(new Error('certificate subject (primary domain) must be the first in opts.domains')); //return Promise.reject(new Error('certificate subject (primary domain) must be the first in opts.domains'));
} }
if (!(args.domains.length && args.domains.every(utils.isValidDomain))) { if (!(args.domains.length && args.domains.every(utils.isValidDomain))) {
// NOTE: this library can't assume to handle the http loopback // NOTE: this library can't assume to handle the http loopback
@ -258,7 +254,7 @@ module.exports.create = function (gl) {
// so we do not check dns records or attempt a loopback here // so we do not check dns records or attempt a loopback here
err = new Error("invalid domain name(s): '(" + args.subject + ') ' + args.domains.join(',') + "'"); err = new Error("invalid domain name(s): '(" + args.subject + ') ' + args.domains.join(',') + "'");
err.code = "INVALID_DOMAIN"; err.code = "INVALID_DOMAIN";
return PromiseA.reject(err); return Promise.reject(err);
} }
// If a previous request to (re)register a certificate is already underway we need // If a previous request to (re)register a certificate is already underway we need
@ -384,6 +380,9 @@ module.exports.create = function (gl) {
Object.keys(challenge).forEach(function (key) { Object.keys(challenge).forEach(function (key) {
done[key] = challenge[key]; done[key] = challenge[key];
}); });
// regression bugfix for le-challenge-cloudflare
// (_acme-challege => _greenlock-dryrun-XXXX)
copy.acmePrefix = (challenge.dnsHost||'').replace(/\.*/, '') || copy.acmePrefix;
gl.challenges[challenge.type].set(copy, challenge.altname, challenge.token, challenge.keyAuthorization, done); gl.challenges[challenge.type].set(copy, challenge.altname, challenge.token, challenge.keyAuthorization, done);
} }
}; };
@ -429,7 +428,7 @@ module.exports.create = function (gl) {
args.keypair = domainKeypair; args.keypair = domainKeypair;
promise = gl.store.certificates.setKeypairAsync(args, domainKeypair); promise = gl.store.certificates.setKeypairAsync(args, domainKeypair);
} }
return PromiseA.resolve(promise).then(function () { return Promise.resolve(promise).then(function () {
return gl.store.certificates.setAsync(args).then(function () { return gl.store.certificates.setAsync(args).then(function () {
return results; return results;
}); });
@ -455,7 +454,7 @@ module.exports.create = function (gl) {
+ new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force." + new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force."
); );
err.code = 'E_NOT_RENEWABLE'; err.code = 'E_NOT_RENEWABLE';
return PromiseA.reject(err); return Promise.reject(err);
} }
// Either the cert has entered its renewal period // Either the cert has entered its renewal period

View File

@ -1,12 +1,12 @@
'use strict'; 'use strict';
require('./compat.js');
var path = require('path'); var path = require('path');
var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")"); var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")");
// very basic check. Allows *.example.com. // very basic check. Allows *.example.com.
var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/; var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/;
var punycode = require('punycode'); var punycode = require('punycode');
var promisify = (require('util').promisify || require('bluebird').promisify); var dnsResolveMxAsync = require('util').promisify(require('dns').resolveMx);
var dnsResolveMxAsync = promisify(require('dns').resolveMx);
module.exports.attachCertInfo = function (results) { module.exports.attachCertInfo = function (results) {
var certInfo = require('cert-info').info(results.cert); var certInfo = require('cert-info').info(results.cert);
@ -54,12 +54,24 @@ module.exports.merge = function (/*defaults, args*/) {
allDefaults.forEach(function (defaults) { allDefaults.forEach(function (defaults) {
Object.keys(defaults).forEach(function (key) { Object.keys(defaults).forEach(function (key) {
copy[key] = defaults[key]; if ('challenges' === key && copy[key] && defaults[key]) {
Object.keys(defaults[key]).forEach(function (k) {
copy[key][k] = defaults[key][k];
});
} else {
copy[key] = defaults[key];
}
}); });
}); });
Object.keys(args).forEach(function (key) { Object.keys(args).forEach(function (key) {
copy[key] = args[key]; if ('challenges' === key && copy[key] && args[key]) {
Object.keys(args[key]).forEach(function (k) {
copy[key][k] = args[key][k];
});
} else {
copy[key] = args[key];
}
}); });
return copy; return copy;

View File

@ -1,6 +1,6 @@
{ {
"name": "greenlock", "name": "greenlock",
"version": "2.7.22", "version": "2.7.23",
"description": "Greenlock is Let's Encrypt (ACME) client for node.js", "description": "Greenlock is Let's Encrypt (ACME) client for node.js",
"homepage": "https://greenlock.domains/", "homepage": "https://greenlock.domains/",
"main": "index.js", "main": "index.js",

View File

@ -1,106 +0,0 @@
'use strict';
var PromiseA = require('bluebird');
var path = require('path');
var requestAsync = PromiseA.promisify(require('@coolaj86/request'));
var LE = require('../').LE;
var le = LE.create({
server: 'staging'
, acme: require('le-acme-core').ACME.create()
, store: require('le-store-certbot').create({
configDir: '~/letsencrypt.test/etc'.split('/').join(path.sep)
, webrootPath: '~/letsencrypt.test/var/:hostname'.split('/').join(path.sep)
})
, challenge: require('le-challenge-fs').create({
webrootPath: '~/letsencrypt.test/var/:hostname'.split('/').join(path.sep)
})
, debug: true
});
var utils = require('../lib/utils');
if ('/.well-known/acme-challenge/' !== LE.acmeChallengePrefix) {
throw new Error("Bad constant 'acmeChallengePrefix'");
}
var baseUrl;
// could use localhost as well, but for the sake of an FQDN for testing, we use this
// also, example.com is just a junk domain to make sure that it is ignored
// (even though it should always be an array of only one element in lib/core.js)
var domains = [ 'localhost.daplie.com', 'example.com' ]; // or just localhost
var token = 'token-id';
var secret = 'key-secret';
var tests = [
function () {
console.log('Test Url:', baseUrl + token);
return requestAsync({ url: baseUrl + token }).then(function (req) {
if (404 !== req.statusCode) {
console.log(req.statusCode);
throw new Error("Should be status 404");
}
});
}
, function () {
var copy = utils.merge({ domains: domains }, le);
copy = utils.tplCopy(copy);
return PromiseA.promisify(le.challenge.set)(copy, domains[0], token, secret);
}
, function () {
return requestAsync(baseUrl + token).then(function (req) {
if (200 !== req.statusCode) {
console.log(req.statusCode, req.body);
throw new Error("Should be status 200");
}
if (req.body !== secret) {
console.error(token, secret, req.body);
throw new Error("req.body should be secret");
}
});
}
, function () {
var copy = utils.merge({ domains: domains }, le);
copy = utils.tplCopy(copy);
return PromiseA.promisify(le.challenge.remove)(copy, domains[0], token);
}
, function () {
return requestAsync(baseUrl + token).then(function (req) {
if (404 !== req.statusCode) {
console.log(req.statusCode);
throw new Error("Should be status 404");
}
});
}
];
function run() {
//var express = require(express);
var server = require('http').createServer(le.middleware());
server.listen(0, function () {
console.log('Server running, proceeding to test.');
baseUrl = 'http://' + domains[0] + ':' + server.address().port + LE.acmeChallengePrefix;
function next() {
var test = tests.shift();
if (!test) {
console.info('All tests passed');
server.close();
return;
}
test().then(next, function (err) {
console.error('ERROR');
console.error(err.stack);
server.close();
});
}
next();
});
}
run();