From 8fc805024b2553ea819f81297e116130220d62b6 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 1 Nov 2019 13:43:56 -0600 Subject: [PATCH] export challenge underlay, and a few bugfixes in it --- challenges-underlay.js | 96 ++++++++++++++++++++ greenlock.js | 194 ++++++++++++++++++++++------------------- 2 files changed, 202 insertions(+), 88 deletions(-) create mode 100644 challenges-underlay.js diff --git a/challenges-underlay.js b/challenges-underlay.js new file mode 100644 index 0000000..8d4d7f9 --- /dev/null +++ b/challenges-underlay.js @@ -0,0 +1,96 @@ +'use strict'; + +var Greenlock = require('./'); + +module.exports.wrap = function(greenlock) { + greenlock.challenges.get = function(chall) { + // TODO pick one and warn on the others + // (just here due to some backwards compat issues with early v3 plugins) + var servername = + chall.servername || + chall.altname || + (chall.identifier && chall.identifier.value); + + // TODO some sort of caching to prevent database hits? + return greenlock + ._config({ servername: servername }) + .then(function(site) { + if (!site) { + return null; + } + + // Hmm... this _should_ be impossible + if (!site.challenges || !site.challenges['http-01']) { + var copy = JSON.parse(JSON.stringify(site)); + sanitizeCopiedConf(copy); + sanitizeCopiedConf(copy.store); + if (site.challenges) { + sanitizeCopiedConf(copy.challenges['http-01']); + sanitizeCopiedConf(copy.challenges['dns-01']); + sanitizeCopiedConf(copy.challenges['tls-alpn-01']); + } + console.warn('[Bug] Please report this error:'); + console.warn( + '\terror: http-01 challenge requested, but not even a default http-01 config exists' + ); + console.warn('\tservername:', JSON.stringify(servername)); + console.warn('\tsite:', JSON.stringify(copy)); + return null; + } + + return Greenlock._loadChallenge(site.challenges, 'http-01'); + }) + .then(function(plugin) { + if (!plugin) { + return null; + } + return plugin + .get({ + challenge: { + type: chall.type, + //hostname: chall.servername, + altname: chall.servername, + identifier: { value: chall.servername }, + token: chall.token + } + }) + .then(function(result) { + var keyAuth; + var keyAuthDigest; + if (result) { + // backwards compat that shouldn't be dropped + // because new v3 modules had to do this to be + // backwards compatible with Greenlock v2.7 at + // the time. + if (result.challenge) { + result = result.challenge; + } + keyAuth = result.keyAuthorization; + keyAuthDigest = result.keyAuthorizationDigest; + } + if (/dns/.test(chall.type)) { + return { + keyAuthorizationDigest: keyAuthDigest + }; + } + return { + keyAuthorization: keyAuth + }; + }); + }); + }; +}; + +function sanitizeCopiedConf(copy) { + if (!copy) { + return; + } + + Object.keys(copy).forEach(function(k) { + if (/(api|key|token)/i.test(k) && 'string' === typeof copy[k]) { + copy[k] = '**redacted**'; + } + }); + + return copy; +} diff --git a/greenlock.js b/greenlock.js index df055bf..5aa199a 100644 --- a/greenlock.js +++ b/greenlock.js @@ -23,45 +23,114 @@ G.create = function(gconf) { if (!gconf) { gconf = {}; } + var manager; - if (!gconf.maintainerEmail) { - throw E.NO_MAINTAINER('create'); - } - - // TODO send welcome message with benefit info - U._validMx(gconf.maintainerEmail).catch(function() { - console.error( - 'invalid maintainer contact info:', - gconf.maintainerEmail - ); - - // maybe move this to init and don't exit the process, just in case - process.exit(1); - }); - - if ('function' === typeof gconf.notify) { - gdefaults.notify = gconf.notify; - } else { - gdefaults.notify = _notify; - } - - if (gconf.directoryUrl) { - gdefaults = gconf.directoryUrl; - if (gconf.staging) { - throw new Error('supply `directoryUrl` or `staging`, but not both'); + greenlock._create = function() { + if (!gconf.maintainerEmail) { + throw E.NO_MAINTAINER('create'); } - } else if (gconf.staging) { - gdefaults.directoryUrl = - 'https://acme-staging-v02.api.letsencrypt.org/directory'; - } else { - gdefaults.directoryUrl = - 'https://acme-v02.api.letsencrypt.org/directory'; - } - console.info('ACME Directory URL:', gdefaults.directoryUrl); - var manager = normalizeManager(gconf); - require('./manager-underlay.js').wrap(greenlock, manager, gconf); - //console.log('debug greenlock.manager', Object.keys(greenlock.manager)); + // TODO send welcome message with benefit info + U._validMx(gconf.maintainerEmail).catch(function() { + console.error( + 'invalid maintainer contact info:', + gconf.maintainerEmail + ); + + // maybe move this to init and don't exit the process, just in case + process.exit(1); + }); + + if ('function' === typeof gconf.notify) { + gdefaults.notify = gconf.notify; + } else { + gdefaults.notify = _notify; + } + + if (gconf.directoryUrl) { + gdefaults = gconf.directoryUrl; + if (gconf.staging) { + throw new Error( + 'supply `directoryUrl` or `staging`, but not both' + ); + } + } else if (gconf.staging) { + gdefaults.directoryUrl = + 'https://acme-staging-v02.api.letsencrypt.org/directory'; + } else { + gdefaults.directoryUrl = + 'https://acme-v02.api.letsencrypt.org/directory'; + } + console.info('ACME Directory URL:', gdefaults.directoryUrl); + + manager = normalizeManager(gconf); + + // Wraps each of the following with appropriate error checking + // greenlock.manager.defaults + // greenlock.manager.add + // greenlock.manager.update + // greenlock.manager.remove + // greenlock.manager.find + require('./manager-underlay.js').wrap(greenlock, manager, gconf); + + // Exports challenges.get for Greenlock Express HTTP-01, + // and whatever odd use case pops up, I suppose + // greenlock.challenges.get + require('./challenges-underlay.js').wrap(greenlock, manager, gconf); + + greenlock._defaults = gdefaults; + greenlock._defaults.debug = gconf.debug; + + // renew every 90-ish minutes (random for staggering) + // the weak setTimeout (unref) means that when run as a CLI process this + // will still finish as expected, and not wait on the timeout + (function renew() { + setTimeout(function() { + greenlock.renew({}); + renew(); + }, Math.PI * 30 * 60 * 1000).unref(); + })(); + }; + + // The purpose of init is to make MCONF the source of truth + greenlock._init = function() { + var p; + greenlock._init = function() { + return p; + }; + + if (manager.init) { + // TODO punycode? + p = manager.init({ + request: request + //punycode: require('punycode') + }); + } else { + p = Promise.resolve(); + } + p = p + .then(function() { + return manager.defaults().then(function(MCONF) { + mergeDefaults(MCONF, gconf); + if (true === MCONF.agreeToTerms) { + gdefaults.agreeToTerms = function(tos) { + return Promise.resolve(tos); + }; + } + return manager.defaults(MCONF); + }); + }) + .catch(function(err) { + console.error('Fatal error during greenlock init:'); + console.error(err); + process.exit(1); + }); + return p; + }; + + // The goal here is to reduce boilerplate, such as error checking + // and duration parsing, that a manager must implement + greenlock.sites.add = greenlock.add = greenlock.manager.add; greenlock.notify = greenlock._notify = function(ev, params) { var mng = greenlock.manager; @@ -121,46 +190,6 @@ G.create = function(gconf) { } }; - // The purpose of init is to make MCONF the source of truth - greenlock._init = function() { - var p; - greenlock._init = function() { - return p; - }; - - if (manager.init) { - // TODO punycode? - p = manager.init({ - request: request - //punycode: require('punycode') - }); - } else { - p = Promise.resolve(); - } - p = p - .then(function() { - return manager.defaults().then(function(MCONF) { - mergeDefaults(MCONF, gconf); - if (true === MCONF.agreeToTerms) { - gdefaults.agreeToTerms = function(tos) { - return Promise.resolve(tos); - }; - } - return manager.defaults(MCONF); - }); - }) - .catch(function(err) { - console.error('Fatal error during greenlock init:'); - console.error(err); - process.exit(1); - }); - return p; - }; - - // The goal here is to reduce boilerplate, such as error checking - // and duration parsing, that a manager must implement - greenlock.sites.add = greenlock.add = greenlock.manager.add; - // certs.get greenlock.get = function(args) { return greenlock @@ -412,18 +441,7 @@ G.create = function(gconf) { }); }; - greenlock._defaults = gdefaults; - greenlock._defaults.debug = gconf.debug; - - // renew every 90-ish minutes (random for staggering) - // the weak setTimeout (unref) means that when run as a CLI process this - // will still finish as expected, and not wait on the timeout - (function renew() { - setTimeout(function() { - greenlock.renew({}); - renew(); - }, Math.PI * 30 * 60 * 1000).unref(); - })(); + greenlock._create(); return greenlock; };