made workaround for the TLS issue that I should have ignored...

This commit is contained in:
AJ ONeal 2017-04-28 13:07:05 -06:00
parent f2b05ee7af
commit eacf2e0dbf
4 changed files with 200 additions and 140 deletions

View File

@ -114,7 +114,7 @@ function readConfigAndRun(args) {
// TODO maybe move to config.state.addresses (?) // TODO maybe move to config.state.addresses (?)
config.addresses = addresses; config.addresses = addresses;
config.device = { hostname: 'TODO: fetch hostname from device and from ip and try to make a match' }; config.device = { hostname: 'daplien-pod' };
if (config.tcp.ports) { if (config.tcp.ports) {
run(config); run(config);

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
module.exports = function (deps, conf, overrideHttp) { module.exports = function (myDeps, conf, overrideHttp) {
var express = require('express'); var express = require('express');
//var finalhandler = require('finalhandler'); //var finalhandler = require('finalhandler');
var serveStatic = require('serve-static'); var serveStatic = require('serve-static');
@ -100,118 +100,132 @@ module.exports = function (deps, conf, overrideHttp) {
} }
}; };
return require('../packages/apis/com.daplie.goldilocks').create({ myDeps.PromiseA = PromiseA;
PromiseA: PromiseA myDeps.OAUTH3 = OAUTH3;
, OAUTH3: OAUTH3 myDeps.storage = { owners: owners };
, storage: { myDeps.recase = require('recase').create({});
owners: owners myDeps.request = request;
myDeps.api = {
// TODO move loopback to oauth3.api('tunnel:loopback')
loopback: function (deps, session, opts2) {
var crypto = require('crypto');
var token = crypto.randomBytes(16).toString('hex');
var keyAuthorization = crypto.randomBytes(16).toString('hex');
var nonce = crypto.randomBytes(16).toString('hex');
// TODO set token and keyAuthorization to /.well-known/cloud-challenge/:token
return request({
method: 'POST'
, url: 'https://oauth3.org/api/org.oauth3.tunnel/loopback'
, json: {
address: opts2.address
, port: opts2.port
, token: token
, keyAuthorization: keyAuthorization
, servername: opts2.servername
, nonce: nonce
, scheme: 'https'
, iat: Date.now()
}
}).then(function (result) {
// TODO this will always fail at the moment
console.log('loopback result:');
return result;
});
} }
, recase: require('recase').create({}) , tunnel: function (deps, session) {
, request: request // TODO save session to config and turn tunnel on
, options: conf var OAUTH3 = deps.OAUTH3;
, api: { var url = require('url');
// TODO move loopback to oauth3.api('tunnel:loopback') var providerUri = session.token.aud;
loopback: function (deps, session, opts2) { var urlObj = url.parse(OAUTH3.url.normalize(session.token.azp));
var crypto = require('crypto'); var oauth3 = OAUTH3.create(urlObj, {
var token = crypto.randomBytes(16).toString('hex'); providerUri: providerUri
var keyAuthorization = crypto.randomBytes(16).toString('hex'); , session: session
var nonce = crypto.randomBytes(16).toString('hex'); });
//var crypto = require('crypto');
// TODO set token and keyAuthorization to /.well-known/cloud-challenge/:token //var id = crypto.createHash('sha256').update(session.token.sub).digest('hex');
return request({ return oauth3.setProvider(providerUri).then(function () {
method: 'POST' /*
, url: 'https://oauth3.org/api/org.oauth3.tunnel/loopback' return oauth3.api('domains.list').then(function (domains) {
, json: { var domainsMap = {};
address: opts2.address domains.forEach(function (d) {
, port: opts2.port if (!d.device) {
, token: token return;
, keyAuthorization: keyAuthorization }
, servername: opts2.servername if (d.device !== conf.device.hostname) {
, nonce: nonce return;
, scheme: 'https' }
, iat: Date.now() domainsMap[d.name] = true;
}
}).then(function (result) {
// TODO this will always fail at the moment
console.log('loopback result:');
return result;
});
}
, tunnel: function (deps, session) {
// TODO save session to config and turn tunnel on
var OAUTH3 = deps.OAUTH3;
var url = require('url');
var providerUri = session.token.aud;
var urlObj = url.parse(OAUTH3.url.normalize(session.token.azp));
var oauth3 = OAUTH3.create(urlObj, {
providerUri: providerUri
, session: session
});
//var crypto = require('crypto');
//var id = crypto.createHash('sha256').update(session.token.sub).digest('hex');
return oauth3.setProvider(providerUri).then(function () {
/*
return oauth3.api('domains.list').then(function (domains) {
var domainsMap = {};
domains.forEach(function (d) {
if (!d.device) {
return;
}
if (d.device !== deps.options.device.hostname) {
return;
}
domainsMap[d.name] = true;
});
*/
//console.log('domains matching hostname', Object.keys(domainsMap));
//console.log('device', deps.options.device);
return oauth3.api('tunnel.token', {
data: {
// filter to all domains that are on this device
//domains: Object.keys(domainsMap)
device: {
hostname: deps.options.device.hostname
, id: deps.options.device.uid || deps.options.device.id
}
}
}).then(function (result) {
console.log('got a token from the tunnel server?');
console.log(result);
if (!result.tunnelUrl) {
result.tunnelUrl = ('wss://' + (new Buffer(result.jwt.split('.')[1], 'base64').toString('ascii')).aud + '/');
}
var opts3 = {
token: result.jwt
, stunneld: result.tunnelUrl
// we'll provide faux networking and pipe as we please
, services: { https: { '*': 443 }, http: { '*': 80 }, smtp: { '*': 25}, smtps: { '*': 587 /*also 465/starttls*/ } /*, ssh: { '*': 22 }*/ }
, net: deps.tunnel.net || deps.net
};
if (tun) {
if (tun.append) {
tun.append(result.jwt);
}
else if (tun.end) {
tun.end();
tun = null;
}
}
if (!tun) {
tun = stunnel.connect(opts3);
conf.tun = true;
}
});
/*
}); });
*/ */
//console.log('domains matching hostname', Object.keys(domainsMap));
//console.log('device', conf.device);
return oauth3.api('tunnel.token', {
data: {
// filter to all domains that are on this device
//domains: Object.keys(domainsMap)
device: {
hostname: conf.device.hostname
, id: conf.device.uid || conf.device.id
}
}
}).then(function (result) {
console.log('got a token from the tunnel server?');
console.log(result);
if (!result.tunnelUrl) {
result.tunnelUrl = ('wss://' + (new Buffer(result.jwt.split('.')[1], 'base64').toString('ascii')).aud + '/');
}
var services = { https: { '*': 443 }, http: { '*': 80 }, smtp: { '*': 25}, smtps: { '*': 587 /*also 465/starttls*/ } /*, ssh: { '*': 22 }*/ };
/*
console.log('blah');
console.log(result.jwt);
console.log(result.tunnelUrl);
console.log(services);
console.log('deps.tunnel');
console.log(deps.tunnel);
console.log('deps.tunnel.net');
console.log(deps.tunnel.net.toString());
console.log('deps.net');
console.log(deps.net);
*/
var opts3 = {
token: result.jwt
, stunneld: result.tunnelUrl
// we'll provide faux networking and pipe as we please
, services: services
, net: myDeps.tunnel.net
};
console.log('blah 2');
if (tun) {
console.log('balh 3');
if (tun.append) {
tun.append(result.jwt);
}
else if (tun.end) {
tun.end();
tun = null;
}
}
console.log('might have tunnel?');
if (!tun) {
console.log('connecting to the tunnel');
tun = stunnel.connect(opts3);
conf.tun = true;
}
});
/*
}); });
//, { token: token, refresh: refresh }); */
} });
//, { token: token, refresh: refresh });
} }
}, conf); };
return require('../packages/apis/com.daplie.goldilocks').create(myDeps, conf);
} }
app = express(); app = express();

View File

@ -11,8 +11,10 @@ module.exports.create = function (deps, config) {
var modules = { }; var modules = { };
var program = { var program = {
tlsOptions: require('localhost.daplie.me-certificates').merge({}) tlsOptions: require('localhost.daplie.me-certificates').merge({})
, acmeDirectoryUrl: 'https://acme-v01.api.letsencrypt.org/directory' // , acmeDirectoryUrl: 'https://acme-v01.api.letsencrypt.org/directory'
, challengeType: 'tls-sni-01' , acmeDirectoryUrl: 'https://acme-staging.api.letsencrypt.org/directory'
// , challengeType: 'tls-sni-01' // won't work with a tunnel
, challengeType: 'http-01'
}; };
var secureContexts = {}; var secureContexts = {};
var tunnelAdminTlsOpts = {}; var tunnelAdminTlsOpts = {};
@ -191,8 +193,8 @@ module.exports.create = function (deps, config) {
function peek(conn, firstChunk, opts) { function peek(conn, firstChunk, opts) {
// TODO port/service-based routing can do here // TODO port/service-based routing can do here
// TLS // TLS byte 1 is handshake and byte 6 is client hello
if (22 === firstChunk[0]) { if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) {
console.log('tryTls'); console.log('tryTls');
opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid'; opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid';
tlsRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts); tlsRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts);
@ -205,6 +207,7 @@ module.exports.create = function (deps, config) {
// even though we've already peeked, this logic is just as well to let be // even though we've already peeked, this logic is just as well to let be
// since it works properly either way, unlike the tls socket // since it works properly either way, unlike the tls socket
conn.once('data', function (chunk) { conn.once('data', function (chunk) {
console.log('hyperPeek re-peek data', chunk.toString('utf8'));
tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, chunk, opts); tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, chunk, opts);
}); });
return; return;
@ -275,7 +278,7 @@ module.exports.create = function (deps, config) {
modules.http = require('./modules/http.js').create(deps, config); modules.http = require('./modules/http.js').create(deps, config);
} }
modules.http.checkServername(opts.domain).then(function (stuff) { modules.http.checkServername(opts.domain).then(function (stuff) {
if (!stuff.domains) { if (!stuff || !stuff.domains) {
// TODO once precheck is implemented we can just let it pass if it passes, yknow? // TODO once precheck is implemented we can just let it pass if it passes, yknow?
cb(new Error('domain is not allowed')); cb(new Error('domain is not allowed'));
return; return;
@ -284,9 +287,10 @@ module.exports.create = function (deps, config) {
complete(null, { complete(null, {
domain: stuff.domain || stuff.domains[0] domain: stuff.domain || stuff.domains[0]
, domains: stuff.domains , domains: stuff.domains
, email: program.email , email: stuff.email || program.email
, server: program.acmeDirectoryUrl , server: stuff.acmeDirectoryUrl || program.acmeDirectoryUrl
, challengeType: program.challengeType , challengeType: stuff.challengeType || program.challengeType
, challenge: stuff.challenge
}); });
return; return;
}, cb); }, cb);
@ -321,35 +325,76 @@ module.exports.create = function (deps, config) {
deps.tunnel = deps.tunnel || {}; deps.tunnel = deps.tunnel || {};
deps.tunnel.net = { deps.tunnel.net = {
createConnection: function (opts, cb) { createConnection: function (opts, cb) {
var Duplex = require('stream').Duplex; console.log('[gl.tunnel] creating connection');
var myDuplex = new Duplex();
// here "reader" means the socket that looks like the connection being accepted
// here "writer" means the remote-looking part of the socket that driving the connection
var writer;
var wrapOpts = {}; var wrapOpts = {};
var rawTls = opts.tls || (0x16 === opts.data[0]) && (0x01 === opts.data[5]);
// this has the normal net/tcp stuff plus our custom stuff function usePair(err, reader) {
// opts = { address, port, if (err) {
// hostname, servername, encrypted, data, localAddress, localPort, remoteAddress, remotePort, remoteFamily } process.nextTick(function () {
Object.keys(opts).forEach(function (key) { writer.emit('error', err);
wrapOpts[key] = opts[key]; });
myDuplex[key] = opts[key]; return;
}); }
// this has the normal net/tcp stuff plus our custom stuff
// opts = { address, port,
// hostname, servername, tls, encrypted, data, localAddress, localPort, remoteAddress, remotePort, remoteFamily }
Object.keys(opts).forEach(function (key) {
wrapOpts[key] = opts[key];
try {
reader[key] = opts[key];
} catch(e) {
// can't set real socket getters, like remoteAddr
}
});
// A few more extra specialty options
wrapOpts.localAddress = wrapOpts.localAddress || '127.0.0.2'; // TODO use the tunnel's external address
wrapOpts.localPort = wrapOpts.localPort || 'tunnel-0';
try {
reader._remoteAddress = wrapOpts.remoteAddress;
reader._remotePort = wrapOpts.remotePort;
reader._localAddress = wrapOpts.localAddress;
reader._localPort = wrapOpts.localPort;
} catch(e) {
}
netHandler(reader, wrapOpts);
process.nextTick(function () {
//opts.data = wrapOpts.data;
// this cb will cause the stream to emit its (actually) first data event
// (even though it already gave a peek into that first data chunk)
console.log('[tunnel] callback, data should begin to flow');
cb();
});
}
// A few more extra specialty options
wrapOpts.firstChunk = opts.data; wrapOpts.firstChunk = opts.data;
wrapOpts.hyperPeek = !!opts.data; wrapOpts.hyperPeek = !!opts.data;
wrapOpts.localAddress = wrapOpts.localAddress || '127.0.0.2'; // TODO use the tunnel's external address // encrypted meaning is *terminated* TLS
wrapOpts.localPort = wrapOpts.localPort || 'tunnel-0'; // tls meaning is *raw* TLS
if (rawTls) {
// TLS sockets must actually use a socket with a file descriptor
// https://nodejs.org/api/net.html#net_class_net_socket
netHandler(myDuplex, wrapOpts); writer = require('socket-pair').create(function (err, other) {
usePair(err, other);
});
}
else {
// stream-pair can only be used by TCP sockets, not tls
writer = require('stream-pair').create();
usePair(null, writer.other);
}
process.nextTick(function () { return writer;
//opts.data = wrapOpts.data;
// this cb will cause the stream to emit its (actually) first data event
// (even though it already gave a peek into that first data chunk)
cb();
});
return myDuplex;
} }
}; };

View File

@ -64,6 +64,7 @@
"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",
"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"
} }
} }