Merge branch 'master' into forwarding

This commit is contained in:
AJ ONeal 2017-05-25 15:30:17 -06:00
commit 112034e26c
10 changed files with 418 additions and 292 deletions

View File

@ -69,26 +69,34 @@ function readConfigAndRun(args) {
var recase = require('recase').create({}); var recase = require('recase').create({});
config = recase.camelCopy(config); config = recase.camelCopy(config);
config.debug = config.debug || args.debug;
if (!config.dns) { 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.
var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {});
if (!config.tcp) { if (!config.tcp) {
config.tcp = {}; config.tcp = {};
} }
if (!config.http) { if (!config.http) {
config.http = { modules: { name: 'proxy', port: 3000 } }; config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] };
} }
if (!config.tls) { if (!config.tls) {
console.log("TODO: tls: { modules: { name: 'acme', email: 'foo@bar.com', domains: [ '*' ] } }"); config.tls = {};
config.tls = { }
agreeTos: args.agreeTos || args.agree || args['agree-tos'] if (!config.tls.acme && (args.email || args.agreeTos)) {
, servernames: (args.servernames||'').split(',').filter(Boolean).map(function (str) { return str.toLowerCase(); }) config.tls.acme = {};
}; }
if (typeof args.agreeTos === 'string') {
config.tls.acme.approvedDomains = args.agreeTos.split(',');
} }
if (args.email) { if (args.email) {
config.email = 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? // maybe this should not go in config... but be ephemeral in some way?
@ -190,20 +198,20 @@ function readConfigAndRun(args) {
function readEnv(args) { function readEnv(args) {
// TODO // TODO
try {
if (process.env.GOLDILOCKS_HOME) {
process.chdir(process.env.GOLDILOCKS_HOME);
}
} catch (err) {}
var env = { var env = {
tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
, email: process.env.GOLDILOCKS_EMAIL , email: process.env.GOLDILOCKS_EMAIL
, cwd: process.env.GOLDILOCKS_HOME , cwd: process.env.GOLDILOCKS_HOME || process.cwd()
, debug: process.env.GOLDILOCKS_DEBUG && true , 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'); var program = require('commander');
@ -217,5 +225,4 @@ program
.option('--debug', "Enable debug output") .option('--debug', "Enable debug output")
.parse(process.argv); .parse(process.argv);
program.cwd = process.cwd();
readEnv(program); readEnv(program);

View File

@ -10,17 +10,55 @@ tcp:
address: '127.0.0.1:8022' address: '127.0.0.1:8022'
tls: 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: modules:
- name: proxy - name: proxy
domains: domains:
- localhost.bar.daplie.me - localhost.bar.daplie.me
- localhost.foo.daplie.me - localhost.foo.daplie.me
address: '127.0.0.1:5443' 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: http:
trust_proxy: true trust_proxy: true
allow_insecure: false allow_insecure: false
primary_domain: localhost.foo.daplie.me 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
port: 3001
modules: modules:
- name: redirect - name: redirect
domains: domains:
@ -31,8 +69,15 @@ http:
- name: proxy - name: proxy
domains: domains:
- localhost.daplie.me - localhost.daplie.me
address: '127.0.0.1:4000' host: locahost
port: 4000
- name: static - name: static
domains: domains:
- '*.localhost.daplie.me' - '*.localhost.daplie.me'
root: '/srv/www/:hostname' root: '/srv/www/:hostname'
mdns:
disabled: false
port: 5353
broadcast: '224.0.0.251'
ttl: 300

View File

@ -171,9 +171,6 @@ install_service()
set -e set -e
set -u set -u
# Dependencies
dap_dl_bash "https://git.daplie.com/coolaj86/node-install-script/raw/master/setup-min.sh"
# Install # Install
# TODO install to tmp location, then move to /opt # TODO install to tmp location, then move to /opt
export NODE_PATH=/opt/goldilocks/lib/node_modules 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 /var/www
$sudo_cmd mkdir -p /opt/goldilocks/{lib,bin,etc} $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 # Change to user perms
# OS X or Linux # OS X or Linux

View File

@ -74,22 +74,36 @@ module.exports.create = function (deps, config) {
} }
function dnsListener(msg) { function dnsListener(msg) {
var dgram = require('dgram'); if (!Array.isArray(config.dns.modules)) {
var socket = dgram.createSocket('udp4'); return;
socket.send(msg, config.dns.proxy.port, config.dns.proxy.address || '127.0.0.1'); }
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) { function createTcpForwarder(mod) {
return function (conn) { var dest = require('./domain-utils').separatePort(mod.address || '');
var newConnOpts = 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) { ['remote', 'local'].forEach(function (end) {
['Family', 'Address', 'Port'].forEach(function (name) { ['Family', 'Address', 'Port'].forEach(function (name) {
newConnOpts[end+name] = conn[end+name]; newConnOpts[end+name] = conn[end+name];
}); });
}); });
deps.proxy(conn, newConnOpts); deps.proxy(conn, Object.assign({}, dest, newConnOpts));
}; };
} }
@ -225,5 +239,9 @@ module.exports.create = function (deps, config) {
} }
} }
if (!config.mdns.disabled) {
require('./mdns').start(deps, config);
}
return PromiseA.all(listenPromises); return PromiseA.all(listenPromises);
}; };

144
lib/mdns.js Normal file
View File

@ -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);
});
};

View File

@ -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; var host = separatePort((req.headers || req).host).host;
return mod.domains.some(function (pattern) { return domains.some(function (pattern) {
return domainMatches(pattern, host); 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 // 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 // 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. // 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, // 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. // 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 (ipv4Re.test(host.host) || ipv6Re.test(host.host)) {
if (!conf.http.primaryDomain) { var dest;
(conf.http.modules || []).some(function (mod) {
return mod.domains.some(function (domain) {
if (domain[0] !== '*') {
conf.http.primaryDomain = domain;
return true;
}
});
});
}
if (conf.http.primaryDomain) { 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) { function checkProxy(mod, conn, opts, headers) {
if (!moduleMatchesHost(headers, mod)) {
return false;
}
var index = opts.firstChunk.indexOf('\r\n\r\n'); var index = opts.firstChunk.indexOf('\r\n\r\n');
var body = opts.firstChunk.slice(index); var body = opts.firstChunk.slice(index);
@ -203,7 +224,9 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
opts.firstChunk = Buffer.concat([head, body]); 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.servername = separatePort(headers.host).host;
newConnOpts.data = opts.firstChunk; newConnOpts.data = opts.firstChunk;
@ -216,10 +239,6 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
} }
function checkRedirect(mod, conn, opts, headers) { function checkRedirect(mod, conn, opts, headers) {
if (!moduleMatchesHost(headers, mod)) {
return false;
}
if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) { if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) {
// Escape any characters that (can) have special meaning in regular expression // Escape any characters that (can) have special meaning in regular expression
// but that aren't the special characters we have interest in. // but that aren't the special characters we have interest in.
@ -283,10 +302,6 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
}); });
} }
function checkStatic(mod, conn, opts, headers) { function checkStatic(mod, conn, opts, headers) {
if (!moduleMatchesHost(headers, mod)) {
return false;
}
var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host); var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host);
return statAsync(rootDir) return statAsync(rootDir)
.then(function (stats) { .then(function (stats) {
@ -309,6 +324,12 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
; ;
} }
var moduleChecks = {
proxy: checkProxy
, redirect: checkRedirect
, static: checkStatic
};
function handleConnection(conn) { function handleConnection(conn) {
var opts = conn.__opts; var opts = conn.__opts;
parseHeaders(conn, opts) parseHeaders(conn, opts)
@ -318,19 +339,34 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
if (checkAdmin(conn, opts, headers)) { return; } if (checkAdmin(conn, opts, headers)) { return; }
var prom = PromiseA.resolve(false); 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) { (conf.http.modules || []).forEach(function (mod) {
prom = prom.then(function (handled) { prom = prom.then(function (handled) {
if (handled) { if (handled) {
return handled; return handled;
} }
if (mod.name === 'proxy') { if (!hostMatchesDomains(headers, mod.domains)) {
return checkProxy(mod, conn, opts, headers); return false;
} }
if (mod.name === 'redirect') {
return checkRedirect(mod, conn, opts, headers); if (moduleChecks[mod.name]) {
} return moduleChecks[mod.name](mod, conn, opts, headers);
if (mod.name === 'static') {
return checkStatic(mod, conn, opts, headers);
} }
console.warn('unknown HTTP module found', mod); console.warn('unknown HTTP module found', mod);
}); });

View File

@ -22,6 +22,12 @@ module.exports.create = function (deps, config, netHandler) {
return value || ''; return value || '';
} }
function nameMatchesDomains(name, domains) {
return domains.some(function (pattern) {
return domainMatches(pattern, name);
});
}
var addressNames = [ var addressNames = [
'remoteAddress' 'remoteAddress'
, 'remotePort' , 'remotePort'
@ -67,17 +73,17 @@ module.exports.create = function (deps, config, netHandler) {
} }
var le = greenlock.create({ var le = greenlock.create({
// server: 'staging'
server: 'https://acme-v01.api.letsencrypt.org/directory' server: 'https://acme-v01.api.letsencrypt.org/directory'
, challenges: { , 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 }) , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug })
// TODO dns-01 // 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) { , approveDomains: function (opts, certs, cb) {
// This is where you check your database and associated // This is where you check your database and associated
@ -88,60 +94,87 @@ module.exports.create = function (deps, config, netHandler) {
if (certs) { if (certs) {
// TODO make sure the same options are used for renewal as for registration? // TODO make sure the same options are used for renewal as for registration?
opts.domains = certs.altnames; opts.domains = certs.altnames;
cb(null, { options: opts, certs: certs }); cb(null, { options: opts, certs: certs });
return; return;
} }
function complete(optsOverride) { function complete(optsOverride, domains) {
Object.keys(optsOverride).forEach(function (key) { if (!cb) {
opts[key] = optsOverride[key]; console.warn('tried to complete domain approval multiple times');
}); return;
cb(null, { options: opts, certs: certs });
} }
// 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.
// check config for domain name Object.assign(opts, optsOverride, { domains: domains, agreeTos: true });
if (-1 !== (config.tls.servernames || []).indexOf(opts.domain)) { cb(null, { options: opts, certs: certs });
// TODO how to handle SANs? cb = null;
// TODO fetch domain-specific email }
// TODO fetch domain-specific acmeDirectory
// NOTE: you can also change other options such as `challengeType` and `challenge` var handled = false;
// opts.challengeType = 'http-01'; if (Array.isArray(config.tls.domains)) {
// opts.challenge = require('le-challenge-fs').create({}); // TODO this doesn't actually work yet handled = config.tls.domains.some(function (dom) {
complete({ if (!nameMatchesDomains(opts.domain, dom.names)) {
return false;
}
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 email: config.tls.email
, agreeTos: true
, server: config.tls.acmeDirectoryUrl || le.server , 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; 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')); 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; le.tlsOptions = le.tlsOptions || le.httpsOptions;
@ -186,7 +219,9 @@ module.exports.create = function (deps, config, netHandler) {
}); });
function proxy(socket, opts, mod) { 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.servername = opts.servername;
newConnOpts.data = opts.firstChunk; newConnOpts.data = opts.firstChunk;
@ -204,6 +239,7 @@ module.exports.create = function (deps, config, netHandler) {
return new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts); return new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
} }
}); });
return true;
} }
function terminate(socket, opts) { function terminate(socket, opts) {
@ -244,31 +280,40 @@ module.exports.create = function (deps, config, netHandler) {
return; return;
} }
var handled = (config.tls.modules || []).some(function (mod) { function checkModule(mod) {
var relevant = mod.domains.some(function (pattern) {
return domainMatches(pattern, opts.servername);
});
if (!relevant) {
return false;
}
if (mod.name === 'proxy') { if (mod.name === 'proxy') {
proxy(socket, opts, mod); return proxy(socket, opts, mod);
} }
else { if (mod.name !== 'acme') {
console.error('saw unknown TLS module', mod); console.error('saw unknown TLS module', mod);
}
}
var handled = (config.tls.domains || []).some(function (dom) {
if (!nameMatchesDomains(opts.servername, dom.names)) {
return false; return false;
} }
return true; return dom.modules.some(checkModule);
}); });
if (handled) {
return;
}
handled = (config.tls.modules || []).some(function (mod) {
if (!nameMatchesDomains(opts.servername, mod.domains)) {
return false;
}
return checkModule(mod);
});
if (handled) {
return;
}
// TODO: figure out all of the domains that the other modules intend to handle, and only // TODO: figure out all of the domains that the other modules intend to handle, and only
// terminate those ones, closing connections for all others. // terminate those ones, closing connections for all others.
if (!handled) {
terminate(socket, opts); terminate(socket, opts);
} }
}
return { return {
emit: function (type, socket) { emit: function (type, socket) {

View File

@ -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;
}
}
//*/
});
});
};

View File

@ -41,29 +41,27 @@
"bluebird": "^3.4.6", "bluebird": "^3.4.6",
"body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1", "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
"commander": "^2.9.0", "commander": "^2.9.0",
"daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master", "dns-suite": "git+https://git@git.daplie.com:Daplie/dns-suite#v1",
"ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master",
"express": "git+https://github.com/expressjs/express.git#4.x", "express": "git+https://github.com/expressjs/express.git#4.x",
"finalhandler": "^0.4.0", "finalhandler": "^0.4.0",
"greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", "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", "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
"ipify": "^1.1.0", "ipify": "^1.1.0",
"js-yaml": "^3.8.3", "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-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-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
"le-challenge-sni": "^2.0.1", "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", "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", "recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
"redirect-https": "^1.1.0", "redirect-https": "^1.1.0",
"request": "^2.81.0",
"scmp": "git+https://github.com/freewil/scmp.git#1.x", "scmp": "git+https://github.com/freewil/scmp.git#1.x",
"serve-index": "^1.7.0", "serve-index": "^1.7.0",
"serve-static": "^1.10.0", "serve-static": "^1.10.0",
"server-destroy": "^1.0.1", "server-destroy": "^1.0.1",
"sni": "^1.0.0",
"socket-pair": "^1.0.0", "socket-pair": "^1.0.0",
"stream-pair": "^1.0.3", "stream-pair": "^1.0.3",
"stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1" "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1"

View File

@ -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));
});