diff --git a/goldilocks.example.yml b/goldilocks.example.yml index 31d4f72..c593118 100644 --- a/goldilocks.example.yml +++ b/goldilocks.example.yml @@ -10,18 +10,40 @@ tcp: address: '127.0.0.1:8022' tls: + acme: + email: 'joe.shmoe@example.com' + server: 'https://acme-staging.api.letsencrypt.org/directory' + challenge_type: 'http-01' + approved_domains: + - localhost.baz.daplie.me + - localhost.beta.daplie.me domains: - names: - localhost.gamma.daplie.me modules: - name: proxy address: '127.0.0.1:6443' + - names: + - beta.localhost.daplie.me + - baz.localhost.daplie.me + modules: + - name: acme + email: 'owner@example.com' + challenge_type: 'tls-sni-01' + # default server is 'https://acme-v01.api.letsencrypt.org/directory' modules: - name: proxy domains: - localhost.bar.daplie.me - localhost.foo.daplie.me address: '127.0.0.1:5443' + - name: acme + email: 'guest@example.com' + challenge_type: 'http-01' + domains: + - foo.localhost.daplie.me + - gamma.localhost.daplie.me + http: trust_proxy: true diff --git a/lib/modules/tls.js b/lib/modules/tls.js index 34ad6fe..a18ab2d 100644 --- a/lib/modules/tls.js +++ b/lib/modules/tls.js @@ -22,6 +22,12 @@ module.exports.create = function (deps, config, netHandler) { return value || ''; } + function nameMatchesDomains(name, domains) { + return domains.some(function (pattern) { + return domainMatches(pattern, name); + }); + } + var addressNames = [ 'remoteAddress' , 'remotePort' @@ -67,17 +73,17 @@ module.exports.create = function (deps, config, netHandler) { } var le = greenlock.create({ - // server: 'staging' server: 'https://acme-v01.api.letsencrypt.org/directory' , challenges: { - 'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges', debug: config.debug }) + 'http-01': require('le-challenge-fs').create({ debug: config.debug }) , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug }) // TODO dns-01 - //, 'dns-01': require('le-challenge-ddns').create() + //, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug }) } + , challengeType: 'http-01' - , store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' }) + , store: require('le-store-certbot').create({ debug: config.debug }) , approveDomains: function (opts, certs, cb) { // This is where you check your database and associated @@ -88,60 +94,87 @@ module.exports.create = function (deps, config, netHandler) { if (certs) { // TODO make sure the same options are used for renewal as for registration? opts.domains = certs.altnames; - cb(null, { options: opts, certs: certs }); return; } - function complete(optsOverride) { - Object.keys(optsOverride).forEach(function (key) { - opts[key] = optsOverride[key]; - }); + function complete(optsOverride, domains) { + if (!cb) { + console.warn('tried to complete domain approval multiple times'); + return; + } + // We can't request certificates for wildcard domains, so filter any of those + // out of this list and put the domain that triggered this in the list if needed. + domains = (domains || []).filter(function (dom) { return dom[0] !== '*'; }); + if (domains.indexOf(opts.domain) < 0) { + domains.push(opts.domain); + } + // TODO: allow user to specify options for challenges or storage. + + Object.assign(opts, optsOverride, { domains: domains, agreeTos: true }); cb(null, { options: opts, certs: certs }); + cb = null; } + var handled = false; + if (Array.isArray(config.tls.domains)) { + handled = config.tls.domains.some(function (dom) { + if (!nameMatchesDomains(opts.domain, dom.names)) { + return false; + } - // check config for domain name - if (-1 !== (config.tls.servernames || []).indexOf(opts.domain)) { - // TODO how to handle SANs? - // TODO fetch domain-specific email - // TODO fetch domain-specific acmeDirectory - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); // TODO this doesn't actually work yet - complete({ + return dom.modules.some(function (mod) { + if (mod.name !== 'acme') { + return false; + } + complete(mod, dom.names); + return true; + }); + }); + } + if (handled) { + return; + } + + if (Array.isArray(config.tls.modules)) { + handled = config.tls.modules.some(function (mod) { + if (mod.name !== 'acme') { + return false; + } + if (!nameMatchesDomains(opts.domain, mod.domains)) { + return false; + } + + complete(mod, mod.domains); + return true; + }); + } + if (handled) { + return; + } + + var defAcmeConf; + if (config.tls.acme) { + defAcmeConf = config.tls.acme; + } else { + defAcmeConf = { email: config.tls.email - , agreeTos: true , server: config.tls.acmeDirectoryUrl || le.server - , challengeType: config.tls.challengeType || 'http-01' - }); + , challengeType: config.tls.challengeType || le.challengeType + , approvedDomains: config.tls.servernames + }; + } + + // Check config for domain name + // TODO: if `approvedDomains` isn't defined check all other modules to see if they can + // handle this domain (and what other domains it's grouped with). + if (-1 !== (defAcmeConf.approvedDomains || []).indexOf(opts.domain)) { + complete(defAcmeConf, defAcmeConf.approvedDomains); return; } - // TODO ask http module (and potentially all other modules) about what domains it can - // handle. We can allow any domains that other modules will handle after we terminate TLS. cb(new Error('domain is not allowed')); - // if (!modules.http) { - // modules.http = require('./modules/http.js').create(deps, config); - // } - // modules.http.checkServername(opts.domain).then(function (stuff) { - // if (!stuff || !stuff.domains) { - // // TODO once precheck is implemented we can just let it pass if it passes, yknow? - // cb(new Error('domain is not allowed')); - // return; - // } - - // complete({ - // domain: stuff.domain || stuff.domains[0] - // , domains: stuff.domains - // , email: stuff.email || program.email - // , server: stuff.acmeDirectoryUrl || program.acmeDirectoryUrl - // , challengeType: stuff.challengeType || program.challengeType - // , challenge: stuff.challenge - // }); - // return; - // }, cb); } }); le.tlsOptions = le.tlsOptions || le.httpsOptions; @@ -257,10 +290,7 @@ module.exports.create = function (deps, config, netHandler) { } var handled = (config.tls.domains || []).some(function (dom) { - var relevant = dom.names.some(function (pattern) { - return domainMatches(pattern, opts.servername); - }); - if (!relevant) { + if (!nameMatchesDomains(opts.servername, dom.names)) { return false; } @@ -271,10 +301,7 @@ module.exports.create = function (deps, config, netHandler) { } handled = (config.tls.modules || []).some(function (mod) { - var relevant = mod.domains.some(function (pattern) { - return domainMatches(pattern, opts.servername); - }); - if (!relevant) { + if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; } return checkModule(mod);