From 73d339660940335083d4cdb457e1a878fe1b4fd6 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Tue, 23 May 2017 12:21:24 -0600 Subject: [PATCH 1/8] removed some unused file and cleaned package.json I used git grep to find all require statements coupled with sed, sort and uniq to create a list of all node modules actually required in our code, then went through package.json to make the list match our dependencies. --- lib/tunnel.js | 144 --------------------------------------------- package.json | 11 ++-- stages/01-serve.js | 23 -------- 3 files changed, 4 insertions(+), 174 deletions(-) delete mode 100644 lib/tunnel.js delete mode 100644 stages/01-serve.js diff --git a/lib/tunnel.js b/lib/tunnel.js deleted file mode 100644 index a4ea58c..0000000 --- a/lib/tunnel.js +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; - -module.exports.create = function (opts, servers) { - // servers = { plainserver, server } - var Oauth3 = require('oauth3-cli'); - var Tunnel = require('daplie-tunnel').create({ - Oauth3: Oauth3 - , PromiseA: opts.PromiseA - , CLI: { - init: function (rs, ws/*, state, options*/) { - // noop - return ws; - } - } - }).Tunnel; - var stunnel = require('stunnel'); - var killcount = 0; - - /* - var Dup = { - write: function (chunk, encoding, cb) { - this.__my_socket.push(chunk, encoding); - cb(); - } - , read: function (size) { - var x = this.__my_socket.read(size); - if (x) { this.push(x); } - } - , setTimeout: function () { - console.log('TODO implement setTimeout on Duplex'); - } - }; - - var httpServer = require('http').createServer(function (req, res) { - console.log('req.socket.encrypted', req.socket.encrypted); - res.end('Hello, tunneled World!'); - }); - - var tlsServer = require('tls').createServer(opts.httpsOptions, function (tlsSocket) { - console.log('tls connection'); - // things get a little messed up here - httpServer.emit('connection', tlsSocket); - - // try again - //servers.server.emit('connection', tlsSocket); - }); - */ - - process.on('SIGINT', function () { - killcount += 1; - console.log('[quit] closing http and https servers'); - if (killcount >= 3) { - process.exit(1); - } - if (servers.server) { - servers.server.close(); - } - if (servers.insecureServer) { - servers.insecureServer.close(); - } - }); - - return Tunnel.token({ - refreshToken: opts.refreshToken - , email: opts.email - , domains: opts.sites.map(function (site) { - return site.name; - }) - , device: { hostname: opts.devicename || opts.device } - }).then(function (result) { - // { jwt, tunnelUrl } - var locals = []; - opts.sites.map(function (site) { - locals.push({ - protocol: 'https' - , hostname: site.name - , port: opts.port - }); - locals.push({ - protocol: 'http' - , hostname: site.name - , port: opts.insecurePort || opts.port - }); - }); - return stunnel.connect({ - token: result.jwt - , stunneld: result.tunnelUrl - // XXX TODO BUG // this is just for testing - , insecure: /*opts.insecure*/ true - , locals: locals - // a simple passthru is proving to not be so simple - , net: require('net') /* - { - createConnection: function (info, cb) { - // data is the hello packet / first chunk - // info = { data, servername, port, host, remoteAddress: { family, address, port } } - - var myDuplex = new (require('stream').Duplex)(); - var myDuplex2 = new (require('stream').Duplex)(); - // duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] }; - - myDuplex2.__my_socket = myDuplex; - myDuplex.__my_socket = myDuplex2; - - myDuplex2._write = Dup.write; - myDuplex2._read = Dup.read; - - myDuplex._write = Dup.write; - myDuplex._read = Dup.read; - - myDuplex.remoteFamily = info.remoteFamily; - myDuplex.remoteAddress = info.remoteAddress; - myDuplex.remotePort = info.remotePort; - - // socket.local{Family,Address,Port} - myDuplex.localFamily = 'IPv4'; - myDuplex.localAddress = '127.0.01'; - myDuplex.localPort = info.port; - - myDuplex.setTimeout = Dup.setTimeout; - - // this doesn't seem to work so well - //servers.server.emit('connection', myDuplex); - - // try a little more manual wrapping / unwrapping - var firstByte = info.data[0]; - if (firstByte < 32 || firstByte >= 127) { - tlsServer.emit('connection', myDuplex); - } - else { - httpServer.emit('connection', myDuplex); - } - - if (cb) { - process.nextTick(cb); - } - - return myDuplex2; - } - } - //*/ - }); - }); -}; diff --git a/package.json b/package.json index cb2446c..67be583 100644 --- a/package.json +++ b/package.json @@ -41,29 +41,26 @@ "bluebird": "^3.4.6", "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1", "commander": "^2.9.0", - "daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master", - "ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master", "express": "git+https://github.com/expressjs/express.git#4.x", "finalhandler": "^0.4.0", "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", - "greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master", - "httpolyglot": "^0.1.1", "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0", "ipify": "^1.1.0", "js-yaml": "^3.8.3", + "jsonwebtoken": "^7.4.0", "le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master", "le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master", "le-challenge-sni": "^2.0.1", - "livereload": "^0.6.0", + "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master", "localhost.daplie.me-certificates": "^1.3.0", - "minimist": "^1.1.1", - "oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master", "recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4", "redirect-https": "^1.1.0", + "request": "^2.81.0", "scmp": "git+https://github.com/freewil/scmp.git#1.x", "serve-index": "^1.7.0", "serve-static": "^1.10.0", "server-destroy": "^1.0.1", + "sni": "^1.0.0", "socket-pair": "^1.0.0", "stream-pair": "^1.0.3", "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1" diff --git a/stages/01-serve.js b/stages/01-serve.js deleted file mode 100644 index 8f92791..0000000 --- a/stages/01-serve.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -var https = require('httpolyglot'); -var httpsOptions = require('localhost.daplie.me-certificates').merge({}); -var httpsPort = 8443; -var redirectApp = require('redirect-https')({ - port: httpsPort -}); - -var server = https.createServer(httpsOptions); - -server.on('request', function (req, res) { - if (!req.socket.encrypted) { - redirectApp(req, res); - return; - } - - res.end("Hello, Encrypted World!"); -}); - -server.listen(httpsPort, function () { - console.log('https://' + 'localhost.daplie.me' + (443 === httpsPort ? ':' : ':' + httpsPort)); -}); From 1f8e44947fd2a9048f0108dde63a27588c1a9b34 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Tue, 23 May 2017 16:23:43 -0600 Subject: [PATCH 2/8] added simple mDNS responder --- bin/goldilocks.js | 3 + lib/goldilocks.js | 4 ++ lib/mdns.js | 144 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 152 insertions(+) create mode 100644 lib/mdns.js diff --git a/bin/goldilocks.js b/bin/goldilocks.js index b19d5e0..205074c 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -73,6 +73,9 @@ function readConfigAndRun(args) { if (!config.dns) { config.dns = { modules: { name: 'proxy', port: 3053 } }; } + if (!config.mdns) { + config.mdns = { port: 5353, broadcast: '224.0.0.251', ttl: 300 }; + } if (!config.tcp) { config.tcp = {}; } diff --git a/lib/goldilocks.js b/lib/goldilocks.js index e938bfd..be18c92 100644 --- a/lib/goldilocks.js +++ b/lib/goldilocks.js @@ -225,5 +225,9 @@ module.exports.create = function (deps, config) { } } + if (!config.mdns.disabled) { + require('./mdns').start(deps, config); + } + return PromiseA.all(listenPromises); }; diff --git a/lib/mdns.js b/lib/mdns.js new file mode 100644 index 0000000..cc08ccd --- /dev/null +++ b/lib/mdns.js @@ -0,0 +1,144 @@ +'use strict'; + +var PromiseA = require('bluebird'); +var fs = PromiseA.promisifyAll(require('fs')); +var idFilename = require('path').join(__dirname, '..', 'var', 'mdns-id'); +var queryName = '_cloud._tcp.local'; + +var randomId = { + get: function () { + return fs.readFileAsync(idFilename) + .catch(function (err) { + if (err.code !== 'ENOENT') { + return PromiseA.reject(err); + } + var id = require('crypto').randomBytes(5).toString('hex'); + return randomId.set(id); + }); + } + +, set: function (value) { + return fs.writeFileAsync(idFilename, value) + .then(function () { + return value; + }); + } +}; + +function createResponse(name, packet, ttl) { + var rpacket = { + header: { + id: packet.header.id + , qr: 1 + , opcode: 0 + , aa: 1 + , tc: 0 + , rd: 0 + , ra: 0 + , res1: 0 + , res2: 0 + , res3: 0 + , rcode: 0 + , } + , question: packet.question + , answer: [] + , authority: [] + , additional: [] + , edns_options: [] + }; + + rpacket.answer.push({ + name: queryName + , typeName: 'PTR' + , ttl: ttl + , className: 'IN' + , data: name + '.' + queryName + }); + + var ifaces = require('./local-ip').find(); + Object.keys(ifaces).forEach(function (iname) { + var iface = ifaces[iname]; + + iface.ipv4.forEach(function (addr) { + rpacket.additional.push({ + name: name + '.local' + , typeName: 'A' + , ttl: ttl + , className: 'IN' + , address: addr.address + }); + }); + + iface.ipv6.forEach(function (addr) { + rpacket.additional.push({ + name: name + '.local' + , typeName: 'AAAA' + , ttl: ttl + , className: 'IN' + , address: addr.address + }); + }); + }); + + rpacket.additional.push({ + name: name + '.' + queryName + , typeName: 'SRV' + , ttl: ttl + , className: 'IN' + , priority: 1 + , weight: 0 + , port: 443 + , target: name + ".local" + }); + rpacket.additional.push({ + name: name + '._device-info._tcp.local' + , typeName: 'TXT' + , ttl: ttl + , className: 'IN' + , data: ["model=CloudHome1,1", "dappsvers=1"] + }); + + return require('dns-suite').DNSPacket.write(rpacket); +} + +module.exports.start = function (deps, config) { + var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); + var dns = require('dns-suite'); + + socket.on('message', function (message, rinfo) { + // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port); + + var packet; + try { + packet = dns.DNSPacket.parse(message); + } + catch (er) { + // `dns-suite` actually errors on a lot of the packets floating around in our network, + // so don't bother logging any errors. (We still use `dns-suite` because unlike `dns-js` + // it can successfully craft the one packet we want to send.) + return; + } + + // Only respond to queries. + if (packet.header.qr !== 0) { + return; + } + // Only respond if they were asking for cloud devices. + if (packet.question.length !== 1 || packet.question[0].name !== queryName) { + return; + } + + randomId.get().then(function (name) { + var resp = createResponse(name, packet, config.mdns.ttl); + socket.send(resp, config.mdns.port, config.mdns.broadcast); + }); + }); + + socket.bind(config.mdns.port, function () { + var addr = this.address(); + console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port); + + socket.setBroadcast(true); + socket.addMembership(config.mdns.broadcast); + }); +}; diff --git a/package.json b/package.json index 67be583..0f2629a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "bluebird": "^3.4.6", "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1", "commander": "^2.9.0", + "dns-suite": "git+https://git@git.daplie.com:Daplie/dns-suite#v1", "express": "git+https://github.com/expressjs/express.git#4.x", "finalhandler": "^0.4.0", "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", From 1e3021c66928a5c9f72c42ce36c237b919976060 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Tue, 23 May 2017 18:26:03 -0600 Subject: [PATCH 3/8] added ability to scope config by domain (issue #25) --- goldilocks.example.yml | 16 +++++++ lib/modules/http.js | 98 ++++++++++++++++++++++++++++-------------- lib/modules/tls.js | 40 +++++++++++------ 3 files changed, 110 insertions(+), 44 deletions(-) diff --git a/goldilocks.example.yml b/goldilocks.example.yml index 80f1283..54e128b 100644 --- a/goldilocks.example.yml +++ b/goldilocks.example.yml @@ -10,6 +10,12 @@ tcp: address: '127.0.0.1:8022' tls: + domains: + - names: + - localhost.gamma.daplie.me + modules: + - name: proxy + address: '127.0.0.1:6443' modules: - name: proxy domains: @@ -21,6 +27,16 @@ http: trust_proxy: true allow_insecure: false primary_domain: localhost.foo.daplie.me + domains: + - names: + - localhost.baz.daplie.me + modules: + - name: redirect + from: /nowhere/in/particular + to: /just/an/example + - name: proxy + address: '127.0.0.1:3001' + modules: - name: redirect domains: diff --git a/lib/modules/http.js b/lib/modules/http.js index d5fa572..c6f2b1b 100644 --- a/lib/modules/http.js +++ b/lib/modules/http.js @@ -70,14 +70,43 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { }); } - function moduleMatchesHost(req, mod) { + function hostMatchesDomains(req, domains) { var host = separatePort((req.headers || req).host).host; - return mod.domains.some(function (pattern) { + return domains.some(function (pattern) { return domainMatches(pattern, host); }); } + function determinePrimaryHost() { + var result; + if (Array.isArray(conf.http.domains)) { + conf.http.domains.some(function (dom) { + return dom.names.some(function (domain) { + if (domain[0] !== '*') { + result = domain; + return true; + } + }); + }); + } + if (result) { + return result; + } + + if (Array.isArray(conf.http.modules)) { + conf.http.modules.some(function (mod) { + return mod.domains.some(function (domain) { + if (domain[0] !== '*') { + result = domain; + return true; + } + }); + }); + } + return result; + } + // We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect // any unencrypted requests to the same port they came from unless it came in on // the default HTTP port, in which case there wont be a port specified in the host. @@ -100,18 +129,14 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { // Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses, // but since those still won't be valid domains that won't really be a problem. if (ipv4Re.test(host.host) || ipv6Re.test(host.host)) { - if (!conf.http.primaryDomain) { - (conf.http.modules || []).some(function (mod) { - return mod.domains.some(function (domain) { - if (domain[0] !== '*') { - conf.http.primaryDomain = domain; - return true; - } - }); - }); - } + var dest; if (conf.http.primaryDomain) { - req.headers.host = conf.http.primaryDomain + (host.port ? ':'+host.port : ''); + dest = conf.http.primaryDomain; + } else { + dest = determinePrimaryHost(); + } + if (dest) { + req.headers.host = dest + (host.port ? ':'+host.port : ''); } } @@ -175,10 +200,6 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { } function checkProxy(mod, conn, opts, headers) { - if (!moduleMatchesHost(headers, mod)) { - return false; - } - var index = opts.firstChunk.indexOf('\r\n\r\n'); var body = opts.firstChunk.slice(index); @@ -216,10 +237,6 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { } function checkRedirect(mod, conn, opts, headers) { - if (!moduleMatchesHost(headers, mod)) { - return false; - } - if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) { // Escape any characters that (can) have special meaning in regular expression // but that aren't the special characters we have interest in. @@ -283,10 +300,6 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { }); } function checkStatic(mod, conn, opts, headers) { - if (!moduleMatchesHost(headers, mod)) { - return false; - } - var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host); return statAsync(rootDir) .then(function (stats) { @@ -309,6 +322,12 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { ; } + var moduleChecks = { + proxy: checkProxy + , redirect: checkRedirect + , static: checkStatic + }; + function handleConnection(conn) { var opts = conn.__opts; parseHeaders(conn, opts) @@ -318,19 +337,34 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { if (checkAdmin(conn, opts, headers)) { return; } var prom = PromiseA.resolve(false); + (conf.http.domains || []).forEach(function (dom) { + prom = prom.then(function (handled) { + if (handled) { + return handled; + } + if (!hostMatchesDomains(headers, dom.names)) { + return false; + } + + return dom.modules.some(function (mod) { + if (moduleChecks[mod.name]) { + return moduleChecks[mod.name](mod, conn, opts, headers); + } + console.warn('unknown HTTP module under domains', dom.names.join(','), mod); + }); + }); + }); (conf.http.modules || []).forEach(function (mod) { prom = prom.then(function (handled) { if (handled) { return handled; } - if (mod.name === 'proxy') { - return checkProxy(mod, conn, opts, headers); + if (!hostMatchesDomains(headers, mod.domains)) { + return false; } - if (mod.name === 'redirect') { - return checkRedirect(mod, conn, opts, headers); - } - if (mod.name === 'static') { - return checkStatic(mod, conn, opts, headers); + + if (moduleChecks[mod.name]) { + return moduleChecks[mod.name](mod, conn, opts, headers); } console.warn('unknown HTTP module found', mod); }); diff --git a/lib/modules/tls.js b/lib/modules/tls.js index 9c00ae1..a93c1da 100644 --- a/lib/modules/tls.js +++ b/lib/modules/tls.js @@ -204,6 +204,7 @@ module.exports.create = function (deps, config, netHandler) { return new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts); } }); + return true; } function terminate(socket, opts) { @@ -244,30 +245,45 @@ module.exports.create = function (deps, config, netHandler) { return; } - var handled = (config.tls.modules || []).some(function (mod) { - var relevant = mod.domains.some(function (pattern) { + function checkModule(mod) { + if (mod.name === 'proxy') { + return proxy(socket, opts, mod); + } + if (mod.name !== 'acme') { + console.error('saw unknown TLS module', mod); + } + } + + var handled = (config.tls.domains || []).some(function (dom) { + var relevant = dom.names.some(function (pattern) { return domainMatches(pattern, opts.servername); }); if (!relevant) { return false; } - if (mod.name === 'proxy') { - proxy(socket, opts, mod); - } - else { - console.error('saw unknown TLS module', mod); + return dom.modules.some(checkModule); + }); + if (handled) { + return; + } + + handled = (config.tls.modules || []).some(function (mod) { + var relevant = mod.domains.some(function (pattern) { + return domainMatches(pattern, opts.servername); + }); + if (!relevant) { return false; } - - return true; + return checkModule(mod); }); + if (handled) { + return; + } // TODO: figure out all of the domains that the other modules intend to handle, and only // terminate those ones, closing connections for all others. - if (!handled) { - terminate(socket, opts); - } + terminate(socket, opts); } return { From be67f04afa008f44a644a0e5e266be054fe897f1 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 24 May 2017 11:42:17 -0600 Subject: [PATCH 4/8] added the mDNS options to the example config --- bin/goldilocks.js | 8 +++++--- goldilocks.example.yml | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bin/goldilocks.js b/bin/goldilocks.js index 205074c..e853336 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -73,9 +73,11 @@ function readConfigAndRun(args) { if (!config.dns) { config.dns = { modules: { name: 'proxy', port: 3053 } }; } - if (!config.mdns) { - config.mdns = { port: 5353, broadcast: '224.0.0.251', ttl: 300 }; - } + // Use Object.assign to add any properties needed but not defined in the mdns config. + // It will first copy the defaults into an empty object, then copy any real config over that. + var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 }; + config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {}); + if (!config.tcp) { config.tcp = {}; } diff --git a/goldilocks.example.yml b/goldilocks.example.yml index 54e128b..7836148 100644 --- a/goldilocks.example.yml +++ b/goldilocks.example.yml @@ -52,3 +52,9 @@ http: domains: - '*.localhost.daplie.me' root: '/srv/www/:hostname' + +mdns: + disabled: false + port: 5353 + broadcast: '224.0.0.251' + ttl: 300 From 21a77ad10a52b81bdf1637d21274f7bc11de66f0 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 24 May 2017 13:05:37 -0600 Subject: [PATCH 5/8] added way to specify proxy destination --- bin/goldilocks.js | 4 ++-- goldilocks.example.yml | 5 +++-- lib/goldilocks.js | 26 ++++++++++++++++++++------ lib/modules/http.js | 4 +++- lib/modules/tls.js | 4 +++- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/bin/goldilocks.js b/bin/goldilocks.js index e853336..28d33c0 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -71,7 +71,7 @@ function readConfigAndRun(args) { config = recase.camelCopy(config); if (!config.dns) { - config.dns = { modules: { name: 'proxy', port: 3053 } }; + config.dns = { modules: [{ name: 'proxy', port: 3053 }] }; } // Use Object.assign to add any properties needed but not defined in the mdns config. // It will first copy the defaults into an empty object, then copy any real config over that. @@ -82,7 +82,7 @@ function readConfigAndRun(args) { config.tcp = {}; } if (!config.http) { - config.http = { modules: { name: 'proxy', port: 3000 } }; + config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] }; } if (!config.tls) { console.log("TODO: tls: { modules: { name: 'acme', email: 'foo@bar.com', domains: [ '*' ] } }"); diff --git a/goldilocks.example.yml b/goldilocks.example.yml index 7836148..31d4f72 100644 --- a/goldilocks.example.yml +++ b/goldilocks.example.yml @@ -35,7 +35,7 @@ http: from: /nowhere/in/particular to: /just/an/example - name: proxy - address: '127.0.0.1:3001' + port: 3001 modules: - name: redirect @@ -47,7 +47,8 @@ http: - name: proxy domains: - localhost.daplie.me - address: '127.0.0.1:4000' + host: locahost + port: 4000 - name: static domains: - '*.localhost.daplie.me' diff --git a/lib/goldilocks.js b/lib/goldilocks.js index be18c92..ae6c552 100644 --- a/lib/goldilocks.js +++ b/lib/goldilocks.js @@ -74,22 +74,36 @@ module.exports.create = function (deps, config) { } function dnsListener(msg) { - var dgram = require('dgram'); - var socket = dgram.createSocket('udp4'); - socket.send(msg, config.dns.proxy.port, config.dns.proxy.address || '127.0.0.1'); + if (!Array.isArray(config.dns.modules)) { + return; + } + var socket = require('dgram').createSocket('udp4'); + config.dns.modules.forEach(function (mod) { + if (mod.name !== 'proxy') { + console.warn('found bad DNS module', mod); + return; + } + var dest = require('./domain-utils').separatePort(mod.address || ''); + dest.port = dest.port || mod.port; + dest.host = dest.host || mod.host || 'localhost'; + socket.send(msg, dest.port, dest.host); + }); } function createTcpForwarder(mod) { - return function (conn) { - var newConnOpts = require('./domain-utils').separatePort(mod.address); + var dest = require('./domain-utils').separatePort(mod.address || ''); + dest.port = dest.port || mod.port; + dest.host = dest.host || mod.host || 'localhost'; + return function (conn) { + var newConnOpts = {}; ['remote', 'local'].forEach(function (end) { ['Family', 'Address', 'Port'].forEach(function (name) { newConnOpts[end+name] = conn[end+name]; }); }); - deps.proxy(conn, newConnOpts); + deps.proxy(conn, Object.assign({}, dest, newConnOpts)); }; } diff --git a/lib/modules/http.js b/lib/modules/http.js index c6f2b1b..6d78a58 100644 --- a/lib/modules/http.js +++ b/lib/modules/http.js @@ -224,7 +224,9 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { opts.firstChunk = Buffer.concat([head, body]); - var newConnOpts = separatePort(mod.address); + var newConnOpts = separatePort(mod.address || ''); + newConnOpts.port = newConnOpts.port || mod.port; + newConnOpts.host = newConnOpts.host || mod.host || 'localhost'; newConnOpts.servername = separatePort(headers.host).host; newConnOpts.data = opts.firstChunk; diff --git a/lib/modules/tls.js b/lib/modules/tls.js index a93c1da..34ad6fe 100644 --- a/lib/modules/tls.js +++ b/lib/modules/tls.js @@ -186,7 +186,9 @@ module.exports.create = function (deps, config, netHandler) { }); function proxy(socket, opts, mod) { - var newConnOpts = require('../domain-utils').separatePort(mod.address); + var newConnOpts = require('../domain-utils').separatePort(mod.address || ''); + newConnOpts.port = newConnOpts.port || mod.port; + newConnOpts.host = newConnOpts.host || mod.host || 'localhost'; newConnOpts.servername = opts.servername; newConnOpts.data = opts.firstChunk; From 3633c7570bbd9bb6b1b26d4e635765ef05648d1d Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 24 May 2017 18:16:01 -0600 Subject: [PATCH 6/8] added support for different ACME config for different domains --- goldilocks.example.yml | 22 +++++++ lib/modules/tls.js | 129 +++++++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 51 deletions(-) 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); From 2eb6d1bc951f54e3b08be5290a801ad1bd3cc3e6 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 24 May 2017 18:20:02 -0600 Subject: [PATCH 7/8] made more command line flags do things --- bin/goldilocks.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/bin/goldilocks.js b/bin/goldilocks.js index 28d33c0..70526cd 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -69,6 +69,7 @@ function readConfigAndRun(args) { var recase = require('recase').create({}); config = recase.camelCopy(config); + config.debug = config.debug || args.debug; if (!config.dns) { config.dns = { modules: [{ name: 'proxy', port: 3053 }] }; @@ -85,15 +86,17 @@ function readConfigAndRun(args) { config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] }; } if (!config.tls) { - console.log("TODO: tls: { modules: { name: 'acme', email: 'foo@bar.com', domains: [ '*' ] } }"); - config.tls = { - agreeTos: args.agreeTos || args.agree || args['agree-tos'] - , servernames: (args.servernames||'').split(',').filter(Boolean).map(function (str) { return str.toLowerCase(); }) - }; + config.tls = {}; + } + if (!config.tls.acme && (args.email || args.agreeTos)) { + config.tls.acme = {}; + } + if (typeof args.agreeTos === 'string') { + config.tls.acme.approvedDomains = args.agreeTos.split(','); } if (args.email) { config.email = args.email; - config.tls.email = args.email; + config.tls.acme.email = args.email; } // maybe this should not go in config... but be ephemeral in some way? @@ -195,20 +198,20 @@ function readConfigAndRun(args) { function readEnv(args) { // TODO + try { + if (process.env.GOLDILOCKS_HOME) { + process.chdir(process.env.GOLDILOCKS_HOME); + } + } catch (err) {} + var env = { tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true , email: process.env.GOLDILOCKS_EMAIL - , cwd: process.env.GOLDILOCKS_HOME + , cwd: process.env.GOLDILOCKS_HOME || process.cwd() , debug: process.env.GOLDILOCKS_DEBUG && true }; - args.cwd = args.cwd || env.cwd; - Object.keys(env).forEach(function (key) { - if ('undefined' === typeof args[key]) { - args[key] = env[key]; - } - }); - readConfigAndRun(args); + readConfigAndRun(Object.assign({}, env, args)); } var program = require('commander'); @@ -222,5 +225,4 @@ program .option('--debug', "Enable debug output") .parse(process.argv); -program.cwd = process.cwd(); readEnv(program); From 002c0059eb88487667b83a112d96d329f27072c6 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 25 May 2017 15:29:17 -0600 Subject: [PATCH 8/8] update node install path --- install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 9568d44..444bed6 100644 --- a/install.sh +++ b/install.sh @@ -171,9 +171,6 @@ install_service() set -e set -u -# Dependencies -dap_dl_bash "https://git.daplie.com/coolaj86/node-install-script/raw/master/setup-min.sh" - # Install # TODO install to tmp location, then move to /opt export NODE_PATH=/opt/goldilocks/lib/node_modules @@ -184,6 +181,9 @@ $sudo_cmd mkdir -p /srv/www $sudo_cmd mkdir -p /var/www $sudo_cmd mkdir -p /opt/goldilocks/{lib,bin,etc} +# Dependencies +dap_dl_bash "https://git.daplie.com/coolaj86/node-install-script/raw/master/setup-min.sh" + # Change to user perms # OS X or Linux