one step closer to the edge, but without the break

This commit is contained in:
AJ ONeal 2018-05-24 19:19:50 +00:00
parent d3022c246e
commit 7db2f0f703
4 changed files with 188 additions and 75 deletions

View File

@ -36,7 +36,7 @@ for convenience.
You can customize the installation:
```bash
export NODEJS_VER=v8.11.1
export NODEJS_VER=v8.11.2
export TELEBITD_PATH=/opt/telebitd
curl -fsS https://get.telebit.cloud/ | bash
```

View File

@ -6,7 +6,7 @@ var pkg = require('../package.json');
var argv = process.argv.slice(2);
var telebitd = require('../telebitd.js');
var greenlock = require('greenlock');
var Greenlock = require('greenlock');
var confIndex = argv.indexOf('--config');
var confpath;
@ -55,6 +55,80 @@ function applyConfig(config) {
console.info("");
console.info("");
}
function approveDomains(opts, certs, cb) {
console.log('[debug] approveDomains', opts.domains);
// This is where you check your database and associated
// email addresses with domains and agreements and such
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if (certs) {
opts.domains = certs.altnames;
cb(null, { options: opts, certs: certs });
return;
}
if (state.config.vhost) {
console.log('[sni] vhost checking is turned on');
var vhost = state.config.vhost.replace(/:hostname/, opts.domains[0]);
require('fs').readdir(vhost, function (err, nodes) {
console.log('[sni] checking fs vhost');
if (err) { check(); return; }
if (nodes) { approve(); }
});
return;
}
function approve() {
opts.email = state.config.email;
opts.agreeTos = state.config.agreeTos;
opts.challenges = {
// TODO dns-01
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' })
};
opts.communityMember = state.config.communityMember;
cb(null, { options: opts, certs: certs });
}
function check() {
console.log('[sni] checking servername');
if (-1 !== state.servernames.indexOf(opts.domain) || -1 !== (state._servernames||[]).indexOf(opts.domain)) {
approve();
} else {
cb(new Error("failed the approval chain '" + opts.domains[0] + "'"));
}
console.log('Approve Domains cb');
}
check();
}
/*
if (!config.email || !config.agreeTos) {
console.error("You didn't specify --email <EMAIL> and --agree-tos");
console.error("(required for ACME / Let's Encrypt / Greenlock TLS/SSL certs)");
console.error("");
process.exit(1);
}
*/
state.greenlock = Greenlock.create({
version: 'draft-11'
, server: 'https://acme-v02.api.letsencrypt.org/directory'
//, server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
, store: require('le-store-certbot').create({ debug: true, webrootPath: '/tmp/acme-challenges' })
, approveDomains: approveDomains
, configDir: '/root/acme'
, debug: true
//, approvedDomains: program.servernames
});
require('../handlers').create(state); // adds directly to config for now...
//require('cluster-store').create().then(function (store) {
@ -77,60 +151,6 @@ function applyConfig(config) {
state.tcp[port].on('connection', netConnHandlers.tcp);
});
//});
function approveDomains(opts, certs, cb) {
console.log('Approve Domains', opts.domains);
// This is where you check your database and associated
// email addresses with domains and agreements and such
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if (certs) {
opts.domains = certs.altnames;
} else {
if (-1 !== state.servernames.indexOf(opts.domain) || -1 !== (state._servernames||[]).indexOf(opts.domain)) {
opts.email = state.config.email;
opts.agreeTos = state.config.agreeTos;
opts.challenges = {
// TODO dns-01
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' })
};
opts.communityMember = state.config.communityMember;
}
}
// NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({});
console.log('Approve Domains cb');
cb(null, { options: opts, certs: certs });
}
/*
if (!config.email || !config.agreeTos) {
console.error("You didn't specify --email <EMAIL> and --agree-tos");
console.error("(required for ACME / Let's Encrypt / Greenlock TLS/SSL certs)");
console.error("");
process.exit(1);
}
*/
state.greenlock = greenlock.create({
version: 'draft-11'
, server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
, store: require('le-store-certbot').create({ debug: true, webrootPath: '/tmp/acme-challenges' })
, approveDomains: approveDomains
, configDir: '/root/acme'
, debug: true
//, approvedDomains: program.servernames
});
}
require('fs').readFile(confpath, 'utf8', function (err, text) {

View File

@ -5,6 +5,14 @@ var tls = require('tls');
var wrapSocket = require('tunnel-packer').wrapSocket;
var redirectHttps = require('redirect-https')();
function noSniCallback(tag) {
return function _noSniCallback(servername, cb) {
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
console.error(err.message);
cb(new Error(err));
}
}
module.exports.create = function (state) {
var tunnelAdminTlsOpts = {};
var setupSniCallback;
@ -62,6 +70,9 @@ module.exports.create = function (state) {
// things get a little messed up here
state.httpInvalidSniServer.emit('connection', tlsSocket);
});
state.tlsInvalidSniServer.on('tlsClientError', function () {
console.error('tlsClientError InvalidSniServer');
});
state.httpsInvalid = function (servername, socket) {
// none of these methods work:
// httpsServer.emit('connection', socket); // this didn't work
@ -95,6 +106,9 @@ module.exports.create = function (state) {
});
httpInvalidSniServer.emit('connection', tlsSocket);
});
tlsInvalidSniServer.on('tlsClientError', function () {
console.error('tlsClientError InvalidSniServer httpsInvalid');
});
tlsInvalidSniServer.emit('connection', wrapSocket(socket));
};
@ -110,15 +124,24 @@ module.exports.create = function (state) {
Object.keys(state.tlsOptions).forEach(function (key) {
tunnelAdminTlsOpts[key] = state.tlsOptions[key];
});
tunnelAdminTlsOpts.SNICallback = (state.greenlock && state.greenlock.tlsOptions && function (servername, cb) {
console.log("time to handle '" + servername + "'");
state.greenlock.tlsOptions.SNICallback(servername, cb);
}) || tunnelAdminTlsOpts.SNICallback;
if (state.greenlock && state.greenlock.tlsOptions) {
console.log('greenlock tlsOptions for SNICallback');
tunnelAdminTlsOpts.SNICallback = function (servername, cb) {
console.log("time to handle '" + servername + "'");
state.greenlock.tlsOptions.SNICallback(servername, cb);
};
} else {
console.log('custom or null tlsOptions for SNICallback');
tunnelAdminTlsOpts.SNICallback = tunnelAdminTlsOpts.SNICallback || noSniCallback('admin');
}
state.tlsTunnelServer = tls.createServer(tunnelAdminTlsOpts, function (tlsSocket) {
console.log('tls connection');
console.log('(Admin) tls connection');
// things get a little messed up here
(state.httpTunnelServer || state.httpServer).emit('connection', tlsSocket);
});
state.tlsTunnelServer.on('tlsClientError', function () {
console.error('tlsClientError TunnelServer client error');
});
state.httpsTunnel = function (servername, socket) {
// none of these methods work:
// httpsServer.emit('connection', socket); // this didn't work
@ -152,11 +175,54 @@ module.exports.create = function (state) {
// things get a little messed up here
state.httpSetupServer.emit('connection', tlsSocket);
});
state.tlsSetupServer.on('tlsClientError', function () {
console.error('tlsClientError SetupServer');
});
state.httpsSetupServer = function (servername, socket) {
console.log('httpsTunnel (Admin) servername', servername);
console.log('httpsTunnel (Setup) servername', servername);
state._servernames = [servername];
state.config.agreeTos = true; // TODO: BUG XXX BAD, make user accept
setupSniCallback = state.greenlock.tlsOptions.SNICallback;
setupSniCallback = state.greenlock.tlsOptions.SNICallback || noSniCallback('setup');
state.tlsSetupServer.emit('connection', wrapSocket(socket));
};
//
// vhost
//
state.httpVhost = http.createServer(function (req, res) {
console.log('httpVhost (local)');
console.log('req.socket.encrypted', req.socket.encrypted);
var finalhandler = require('finalhandler');
// TODO compare SNI to hostname?
var host = (req.headers.host||'').toLowerCase().trim();
var serveSetup = require('serve-static')(state.config.vhost.replace(/:hostname/g, host), { redirect: true });
if (req.socket.encrypted) { serveSetup(req, res, finalhandler(req, res)); return; }
console.log('try greenlock middleware for vhost');
(state.greenlock && state.greenlock.middleware(redirectHttpsAndClose)
|| redirectHttpsAndClose)(req, res, function () {
console.log('fallthrough to vhost serving???');
serveSetup(req, res, finalhandler(req, res));
});
});
state.tlsVhost = tls.createServer(
{ SNICallback: function (servername, cb) {
console.log('tlsVhost debug SNICallback', servername);
tunnelAdminTlsOpts.SNICallback(servername, cb);
}
}
, function (tlsSocket) {
console.log('tlsVhost (local)');
state.httpVhost.emit('connection', tlsSocket);
}
);
state.tlsVhost.on('tlsClientError', function () {
console.error('tlsClientError Vhost');
});
state.httpsVhost = function (servername, socket) {
console.log('httpsVhost (local)', servername);
state.tlsVhost.emit('connection', wrapSocket(socket));
};
};

View File

@ -80,6 +80,10 @@ module.exports.createTcpConnectionHandler = function (copts) {
var m;
function tryTls() {
var vhost;
console.log("");
if (!copts.servernames.length) {
console.log("https => admin => setup => (needs bogus tls certs to start?)");
copts.httpsSetupServer(servername, conn);
@ -92,21 +96,44 @@ module.exports.createTcpConnectionHandler = function (copts) {
return;
}
if (!servername) {
console.log("No SNI was given, so there's nothing we can do here");
copts.httpsInvalid(servername, conn);
if (copts.config.nowww && /^www\./i.test(servername)) {
console.log("TODO: use www bare redirect");
}
function run() {
if (!servername) {
console.log("No SNI was given, so there's nothing we can do here");
copts.httpsInvalid(servername, conn);
return;
}
var nextDevice = Devices.next(copts.deviceLists, servername);
if (!nextDevice) {
console.log("No devices match the given servername");
copts.httpsInvalid(servername, conn);
return;
}
console.log("pipeWs(servername, service, socket, deviceLists['" + servername + "'])");
pipeWs(servername, service, conn, nextDevice);
}
if (copts.config.vhost) {
console.log("VHOST path", copts.config.vhost);
vhost = copts.config.vhost.replace(/:hostname/, (servername||''));
console.log("VHOST name", vhost);
conn.pause();
//copts.httpsVhost(servername, conn);
//return;
require('fs').readdir(vhost, function (err, nodes) {
console.log("VHOST error?", err);
if (err) { run(); return; }
if (nodes) { copts.httpsVhost(servername, conn); }
});
return;
}
var nextDevice = Devices.next(copts.deviceLists, servername);
if (!nextDevice) {
console.log("No devices match the given servername");
copts.httpsInvalid(servername, conn);
return;
}
console.log("pipeWs(servername, service, socket, deviceLists['" + servername + "'])");
pipeWs(servername, service, conn, nextDevice);
run();
}
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155