Compare commits

..

No commits in common. "master" and "installer-v2" have entirely different histories.

27 changed files with 2895 additions and 926 deletions

View File

@ -1,10 +1,3 @@
v1.1.5 - Implemented dns-01 ACME challenges
v1.1.4 - Improved responsiveness to config updates
* changed which TCP/UDP ports are bound to on config update
* update tunnel server settings on config update
* update socks5 setting on config update
v1.1.3 - Better late than never... here's some stuff we've got v1.1.3 - Better late than never... here's some stuff we've got
* fixed (probably) network settings not being readable * fixed (probably) network settings not being readable
* supports timeouts in loopback check * supports timeouts in loopback check

View File

@ -23,13 +23,13 @@ Install Standalone
### curl | bash ### curl | bash
```bash ```bash
curl -fsSL https://git.coolaj86.com/coolaj86/goldilocks.js/raw/v1.1/installer/get.sh | bash curl -fsSL https://git.daplie.com/Daplie/goldilocks.js/raw/v1.1/installer/get.sh | bash
``` ```
### git ### git
```bash ```bash
git clone https://git.coolaj86.com/coolaj86/goldilocks.js git clone https://git.daplie.com/Daplie/goldilocks.js
pushd goldilocks.js pushd goldilocks.js
git checkout v1.1 git checkout v1.1
bash installer/install.sh bash installer/install.sh
@ -39,10 +39,10 @@ bash installer/install.sh
```bash ```bash
# v1 in git (unauthenticated) # v1 in git (unauthenticated)
npm install -g git+https://git@git.coolaj86.com:coolaj86/goldilocks.js#v1 npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js#v1
# v1 in git (via ssh) # v1 in git (via ssh)
npm install -g git+ssh://git@git.coolaj86.com:coolaj86/goldilocks.js#v1 npm install -g git+ssh://git@git.daplie.com:Daplie/goldilocks.js#v1
# v1 in npm # v1 in npm
npm install -g goldilocks@v1 npm install -g goldilocks@v1
@ -50,16 +50,8 @@ npm install -g goldilocks@v1
### Uninstall ### Uninstall
Remove goldilocks and services:
``` ```
rm -rf /opt/goldilocks/ /srv/goldilocks/ /var/goldilocks/ /var/log/goldilocks/ /etc/tmpfiles.d/goldilocks.conf /etc/systemd/system/goldilocks.service rm -rf /srv/goldilocks/ /var/goldilocks/ /etc/goldilocks/ /opt/goldilocks/ /var/log/goldilocks/ /etc/tmpfiles.d/goldilocks.conf /etc/systemd/system/goldilocks.service /etc/ssl/goldilocks
```
Remove config as well
```
rm -rf /etc/goldilocks/ /etc/ssl/goldilocks
``` ```
Usage Usage
@ -81,7 +73,7 @@ We have service support for
* launchd (macOS) * launchd (macOS)
```bash ```bash
curl https://git.coolaj86.com/coolaj86/goldilocks.js/raw/master/install.sh | bash curl https://git.daplie.com/Daplie/goldilocks.js/raw/master/install.sh | bash
``` ```
Modules & Configuration Modules & Configuration
@ -305,12 +297,6 @@ tls:
challenge_type: 'http-01' challenge_type: 'http-01'
``` ```
**NOTE:** If you specify `dns-01` as the challenge type there must also be a
[DDNS module](#ddns) defined for all of the relevant domains (though not all
domains handled by a single TLS module need to be handled by the same DDNS
module). The DDNS module provides all of the information needed to actually
set the DNS records needed to verify ownership.
### tcp ### tcp
The tcp system handles both *raw* and *tls-terminated* tcp network traffic The tcp system handles both *raw* and *tls-terminated* tcp network traffic
@ -412,7 +398,7 @@ sni = vpn.example.com
connect = example.com:443 connect = example.com:443
``` ```
3) [Use stunnel.js](https://git.coolaj86.com/coolaj86/tunnel-client.js) as described in the "tunnel_server" section below. 3) [Use stunnel.js](https://git.daplie.com/Daplie/node-tunnel-client) as described in the "tunnel_server" section below.
### tcp.forward ### tcp.forward
@ -616,7 +602,7 @@ mdns:
You can discover goldilocks with `mdig`. You can discover goldilocks with `mdig`.
``` ```
npm install -g git+https://git.coolaj86.com/coolaj86/mdig.js.git npm install -g git+https://git.daplie.com/Daplie/mdig.git
mdig _cloud._tcp.local mdig _cloud._tcp.local
``` ```
@ -645,7 +631,7 @@ TODO
* [ ] http - redirect based on domain name (not just path) * [ ] http - redirect based on domain name (not just path)
* [ ] tcp - bind should be able to specify localhost, uniquelocal, private, or ip * [ ] tcp - bind should be able to specify localhost, uniquelocal, private, or ip
* [ ] tcp - if destination host is omitted default to localhost, if dst port is missing, default to src * [ ] tcp - if destination host is omitted default to localhost, if dst port is missing, default to src
* [ ] sys - `curl https://coolaj86.com/goldilocks | bash -s example.com` * [ ] sys - `curl https://daplie.me/goldilocks | bash -s example.com`
* [ ] oauth3 - `example.com/.well-known/domains@oauth3.org/directives.json` * [ ] oauth3 - `example.com/.well-known/domains@oauth3.org/directives.json`
* [ ] oauth3 - commandline questionnaire * [ ] oauth3 - commandline questionnaire
* [x] modules - use consistent conventions (i.e. address vs host + port) * [x] modules - use consistent conventions (i.e. address vs host + port)

View File

@ -7,7 +7,7 @@ my_ver=master
my_tmp=$(mktemp -d) my_tmp=$(mktemp -d)
mkdir -p $my_tmp/opt/$my_name/lib/node_modules/$my_name mkdir -p $my_tmp/opt/$my_name/lib/node_modules/$my_name
git clone https://git.coolaj86.com/coolaj86/goldilocks.js.git $my_tmp/opt/$my_name/lib/node_modules/$my_name git clone https://git.daplie.com/Daplie/goldilocks.js.git $my_tmp/opt/$my_name/lib/node_modules/$my_name
echo "Installing to $my_tmp (will be moved after install)" echo "Installing to $my_tmp (will be moved after install)"
pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name

View File

@ -5,7 +5,7 @@
# # # #
############################### ###############################
# See https://git.coolaj86.com/coolaj86/snippets/blob/master/bash/http-get.sh # See https://git.daplie.com/Daplie/daplie-snippets/blob/master/bash/http-get.sh
_h_http_get="" _h_http_get=""
_h_http_opts="" _h_http_opts=""

View File

@ -11,13 +11,11 @@ sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_service" > "$my_app_dis
sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_service.2" > "$my_app_dist/$my_app_systemd_service" sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_service.2" > "$my_app_dist/$my_app_systemd_service"
rm "$my_app_dist/$my_app_systemd_service.2" rm "$my_app_dist/$my_app_systemd_service.2"
safe_copy_config "$my_app_dist/$my_app_systemd_service" "$my_root/$my_app_systemd_service" safe_copy_config "$my_app_dist/$my_app_systemd_service" "$my_root/$my_app_systemd_service"
$sudo_cmd chown root:root "$my_root/$my_app_systemd_service"
sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_tmpfiles" > "$my_app_dist/$my_app_systemd_tmpfiles.2" sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_tmpfiles" > "$my_app_dist/$my_app_systemd_tmpfiles.2"
sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_tmpfiles.2" > "$my_app_dist/$my_app_systemd_tmpfiles" sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_tmpfiles.2" > "$my_app_dist/$my_app_systemd_tmpfiles"
rm "$my_app_dist/$my_app_systemd_tmpfiles.2" rm "$my_app_dist/$my_app_systemd_tmpfiles.2"
safe_copy_config "$my_app_dist/$my_app_systemd_tmpfiles" "$my_root/$my_app_systemd_tmpfiles" safe_copy_config "$my_app_dist/$my_app_systemd_tmpfiles" "$my_root/$my_app_systemd_tmpfiles"
$sudo_cmd chown root:root "$my_root/$my_app_systemd_tmpfiles"
$sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null || true $sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null || true
$sudo_cmd systemctl daemon-reload $sudo_cmd systemctl daemon-reload

View File

@ -7,10 +7,10 @@ set -u
### IMPORTANT ### ### IMPORTANT ###
### VERSION ### ### VERSION ###
my_name=goldilocks my_name=goldilocks
my_app_pkg_name=com.coolaj86.goldilocks.web my_app_pkg_name=com.daplie.goldilocks.web
my_app_ver="v1.1" my_app_ver="v1.1"
my_azp_oauth3_ver="v1.2.3" my_azp_oauth3_ver="v1.2"
export NODE_VERSION="v8.9.3" export NODE_VERSION="v8.9.0"
if [ -z "${my_tmp-}" ]; then if [ -z "${my_tmp-}" ]; then
my_tmp="$(mktemp -d)" my_tmp="$(mktemp -d)"
@ -29,7 +29,7 @@ my_npm="$NPM_CONFIG_PREFIX/bin/npm"
my_app_dist=$my_tmp/opt/$my_name/lib/node_modules/$my_name/dist my_app_dist=$my_tmp/opt/$my_name/lib/node_modules/$my_name/dist
installer_base="https://git.coolaj86.com/coolaj86/goldilocks.js/raw/$my_app_ver" installer_base="https://git.daplie.com/Daplie/goldilocks.js/raw/$my_app_ver"
# Backwards compat # Backwards compat
# some scripts still use the old names # some scripts still use the old names
@ -71,13 +71,13 @@ pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name
$my_npm install $my_npm install
popd popd
pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name/packages/assets pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name/packages/assets
OAUTH3_GIT_URL="https://git.oauth3.org/OAuth3/oauth3.js.git" OAUTH3_GIT_URL="https://git.daplie.com/Oauth3/oauth3.js.git"
git clone ${OAUTH3_GIT_URL} oauth3.org || true git clone ${OAUTH3_GIT_URL} oauth3.org || true
ln -s oauth3.org org.oauth3 ln -s oauth3.org org.oauth3
pushd oauth3.org pushd oauth3.org
git remote set-url origin ${OAUTH3_GIT_URL} git remote set-url origin ${OAUTH3_GIT_URL}
git checkout $my_azp_oauth3_ver git checkout $my_azp_oauth3_ver
#git pull git pull
popd popd
mkdir -p jquery.com mkdir -p jquery.com
@ -119,8 +119,6 @@ set -e
source ./installer/my-user-my-group.sh source ./installer/my-user-my-group.sh
echo "User $my_user Group $my_group" echo "User $my_user Group $my_group"
source ./installer/install-system-service.sh
$sudo_cmd chown -R $my_user:$my_group $my_tmp/* $sudo_cmd chown -R $my_user:$my_group $my_tmp/*
$sudo_cmd chown root:root $my_tmp/* $sudo_cmd chown root:root $my_tmp/*
$sudo_cmd chown root:root $my_tmp $sudo_cmd chown root:root $my_tmp
@ -128,6 +126,7 @@ $sudo_cmd chmod 0755 $my_tmp
# don't change permissions on /, /etc, etc # don't change permissions on /, /etc, etc
$sudo_cmd rsync -a --ignore-existing $my_tmp/ $my_root/ $sudo_cmd rsync -a --ignore-existing $my_tmp/ $my_root/
$sudo_cmd rsync -a --ignore-existing $my_app_dist/etc/$my_name/$my_name.yml $my_root/etc/$my_name/$my_name.yml $sudo_cmd rsync -a --ignore-existing $my_app_dist/etc/$my_name/$my_name.yml $my_root/etc/$my_name/$my_name.yml
source ./installer/install-system-service.sh
# Change to admin perms # Change to admin perms
$sudo_cmd chown -R $my_user:$my_group $my_root/opt/$my_name $sudo_cmd chown -R $my_user:$my_group $my_root/opt/$my_name

View File

@ -174,14 +174,6 @@ var mdnsSchema = {
} }
}; };
var tunnelSvrSchema = {
type: 'object'
, properties: {
servernames: { type: 'array', items: { type: 'string' }}
, secret: { type: 'string' }
}
};
var ddnsSchema = { var ddnsSchema = {
type: 'object' type: 'object'
, properties: { , properties: {
@ -231,7 +223,6 @@ var mainSchema = {
, ddns: ddnsSchema , ddns: ddnsSchema
, socks5: socks5Schema , socks5: socks5Schema
, device: deviceSchema , device: deviceSchema
, tunnel_server: tunnelSvrSchema
} }
, additionalProperties: false , additionalProperties: false
}; };

View File

@ -1,122 +0,0 @@
'use strict';
// Much of this file was based on the `le-challenge-ddns` library (which we are not using
// here because it's method of setting records requires things we don't really want).
module.exports.create = function (deps, conf, utils) {
function getReleventSessionId(domain) {
var sessId;
utils.iterateAllModules(function (mod, domainList) {
// We return a truthy value in these cases because of the way the iterate function
// handles modules grouped by domain. By returning true we are saying these domains
// are "handled" and so if there are multiple modules we won't be given the rest.
if (sessId) { return true; }
if (domainList.indexOf(domain) < 0) { return true; }
// But if the domains are relevant but we don't know how to handle the module we
// return false to allow us to look at any other modules that might exist here.
if (mod.type !== 'dns@oauth3.org') { return false; }
sessId = mod.tokenId || mod.token_id;
return true;
});
return sessId;
}
function get(args, domain, challenge, done) {
done(new Error("Challenge.get() does not need an implementation for dns-01. (did you mean Challenge.loopback?)"));
}
// same as get, but external
function loopback(args, domain, challenge, done) {
var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
require('dns').resolveTxt(challengeDomain, done);
}
var activeChallenges = {};
async function removeAsync(args, domain) {
var data = activeChallenges[domain];
if (!data) {
console.warn(new Error('cannot remove DNS challenge for ' + domain + ': already removed'));
return;
}
var session = await utils.getSession(data.sessId);
var directives = await deps.OAUTH3.discover(session.token.aud);
var apiOpts = {
api: 'dns.unset'
, session: session
, type: 'TXT'
, value: data.keyAuthDigest
};
await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, data.splitDomain));
delete activeChallenges[domain];
}
async function setAsync(args, domain, challenge, keyAuth) {
if (activeChallenges[domain]) {
await removeAsync(args, domain, challenge);
}
var sessId = getReleventSessionId(domain);
if (!sessId) {
throw new Error('no DDNS module handles the domain ' + domain);
}
var session = await utils.getSession(sessId);
var directives = await deps.OAUTH3.discover(session.token.aud);
// I'm not sure what role challenge is supposed to play since even in the library
// this code is based on it was never used, but check for it anyway because ...
if (!challenge || keyAuth) {
console.warn(new Error('DDNS challenge missing challenge or keyAuth'));
}
var keyAuthDigest = require('crypto').createHash('sha256').update(keyAuth || '').digest('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
var splitDomain = (await utils.splitDomains(directives.api, [challengeDomain]))[0];
var apiOpts = {
api: 'dns.set'
, session: session
, type: 'TXT'
, value: keyAuthDigest
, ttl: args.ttl || 0
};
await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, splitDomain));
activeChallenges[domain] = {
sessId
, keyAuthDigest
, splitDomain
};
return new Promise(res => setTimeout(res, 1000));
}
// It might be slightly easier to use arguments and apply, but the library that will use
// this function counts the arguments we expect.
function set(a, b, c, d, done) {
setAsync(a, b, c, d).then(result => done(null, result), done);
}
function remove(a, b, c, done) {
removeAsync(a, b, c).then(result => done(null, result), done);
}
function getOptions() {
return {
oauth3: 'oauth3.org'
, debug: conf.debug
, acmeChallengeDns: '_acme-challenge.'
};
}
return {
getOptions
, set
, get
, remove
, loopback
};
};

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
module.exports.create = function (deps, conf, utils) { module.exports.create = function (deps, conf) {
function dnsType(addr) { function dnsType(addr) {
if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) { if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) {
return 'A'; return 'A';
@ -10,6 +10,62 @@ module.exports.create = function (deps, conf, utils) {
} }
} }
var tldCache = {};
async function getTlds(provider) {
async function updateCache() {
var reqObj = {
url: deps.OAUTH3.url.normalize(provider)+'/api/com.daplie.domains/prices'
, method: 'GET'
, json: true
};
var resp = await deps.OAUTH3.request(reqObj);
var tldObj = {};
resp.data.forEach(function (tldInfo) {
if (tldInfo.enabled) {
tldObj[tldInfo.tld] = true;
}
});
tldCache[provider] = {
time: Date.now()
, tlds: tldObj
};
return tldObj;
}
// If we've never cached the results we need to return the promise that will fetch the recult,
// otherwise we can return the cached value. If the cached value has "expired", we can still
// return the cached value we just want to update the cache in parellel (making sure we only
// update once).
if (!tldCache[provider]) {
return updateCache();
}
if (!tldCache[provider].updating && Date.now() - tldCache[provider].time > 24*60*60*1000) {
tldCache[provider].updating = true;
updateCache();
}
return tldCache[provider].tlds;
}
async function splitDomains(provider, domains) {
var tlds = await getTlds(provider);
return domains.map(function (domain) {
var split = domain.split('.');
var tldSegCnt = tlds[split.slice(-2).join('.')] ? 2 : 1;
// Currently assuming that the sld can't contain dots, and that the tld can have at
// most one dot. Not 100% sure this is a valid assumption, but exceptions should be
// rare even if the assumption isn't valid.
return {
tld: split.slice(-tldSegCnt).join('.')
, sld: split.slice(-tldSegCnt-1, -tldSegCnt).join('.')
, sub: split.slice(0, -tldSegCnt-1).join('.')
};
});
}
async function setDeviceAddress(session, addr, domains) { async function setDeviceAddress(session, addr, domains) {
var directives = await deps.OAUTH3.discover(session.token.aud); var directives = await deps.OAUTH3.discover(session.token.aud);
@ -55,7 +111,7 @@ module.exports.create = function (deps, conf, utils) {
return goodAddrDomains.indexOf(domain) < 0; return goodAddrDomains.indexOf(domain) < 0;
}); });
var oldDns = await utils.splitDomains(directives.api, badAddrDomains); var oldDns = await splitDomains(directives.api, badAddrDomains);
var common = { var common = {
api: 'devices.detach' api: 'devices.detach'
, session: session , session: session
@ -68,7 +124,7 @@ module.exports.create = function (deps, conf, utils) {
console.log('removed bad DNS records for ' + badAddrDomains.join(', ')); console.log('removed bad DNS records for ' + badAddrDomains.join(', '));
} }
var newDns = await utils.splitDomains(directives.api, requiredUpdates); var newDns = await splitDomains(directives.api, requiredUpdates);
common = { common = {
api: 'devices.attach' api: 'devices.attach'
, session: session , session: session
@ -113,7 +169,7 @@ module.exports.create = function (deps, conf, utils) {
async function removeDomains(session, domains) { async function removeDomains(session, domains) {
var directives = await deps.OAUTH3.discover(session.token.aud); var directives = await deps.OAUTH3.discover(session.token.aud);
var oldDns = await utils.splitDomains(directives.api, domains); var oldDns = await splitDomains(directives.api, domains);
var common = { var common = {
api: 'devices.detach' api: 'devices.detach'
, session: session , session: session

View File

@ -3,21 +3,48 @@
module.exports.create = function (deps, conf) { module.exports.create = function (deps, conf) {
var dns = deps.PromiseA.promisifyAll(require('dns')); var dns = deps.PromiseA.promisifyAll(require('dns'));
var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network'))); var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network')));
var loopback = require('./loopback').create(deps, conf);
var dnsCtrl = require('./dns-ctrl').create(deps, conf);
var equal = require('deep-equal'); var equal = require('deep-equal');
var utils = require('./utils').create(deps, conf);
var loopback = require('./loopback').create(deps, conf, utils);
var dnsCtrl = require('./dns-ctrl').create(deps, conf, utils);
var challenge = require('./challenge-responder').create(deps, conf, utils);
var tunnelClients = require('./tunnel-client-manager').create(deps, conf, utils);
var loopbackDomain; var loopbackDomain;
function iterateAllModules(action, curConf) {
curConf = curConf || conf;
var promises = curConf.ddns.modules.map(function (mod) {
return action(mod, mod.domains);
});
curConf.domains.forEach(function (dom) {
if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) {
return null;
}
// For the time being all of our things should only be tried once (regardless if it succeeded)
// TODO: revisit this behavior when we support multiple ways of setting records, and/or
// if we want to allow later modules to run if early modules fail.
promises.push(dom.modules.ddns.reduce(function (prom, mod) {
if (prom) { return prom; }
return action(mod, dom.names);
}, null));
});
return deps.PromiseA.all(promises.filter(Boolean));
}
async function getSession(id) {
var session = await deps.storage.tokens.get(id);
if (!session) {
throw new Error('no user token with ID "'+id+'"');
}
return session;
}
var tunnelActive = false; var tunnelActive = false;
async function startTunnel(tunnelSession, mod, domainList) { async function startTunnel(tunnelSession, mod, domainList) {
try { try {
var dnsSession = await utils.getSession(mod.tokenId); var dnsSession = await getSession(mod.tokenId);
var tunnelDomain = await tunnelClients.start(tunnelSession || dnsSession, domainList); var tunnelDomain = await deps.tunnelClients.start(tunnelSession || dnsSession, domainList);
var addrList; var addrList;
try { try {
@ -32,9 +59,7 @@ module.exports.create = function (deps, conf) {
throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"'); throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"');
} }
if (!mod.disabled) { await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList);
await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList);
}
} catch (err) { } catch (err) {
console.log('error starting tunnel for', domainList.join(', ')); console.log('error starting tunnel for', domainList.join(', '));
console.log(err); console.log(err);
@ -48,7 +73,7 @@ module.exports.create = function (deps, conf) {
tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId);
} }
await utils.iterateAllModules(function (mod, domainList) { await iterateAllModules(function (mod, domainList) {
if (mod.type !== 'dns@oauth3.org') { return null; } if (mod.type !== 'dns@oauth3.org') { return null; }
return startTunnel(tunnelSession, mod, domainList); return startTunnel(tunnelSession, mod, domainList);
@ -57,14 +82,14 @@ module.exports.create = function (deps, conf) {
tunnelActive = true; tunnelActive = true;
} }
async function disconnectTunnels() { async function disconnectTunnels() {
tunnelClients.disconnect(); deps.tunnelClients.disconnect();
tunnelActive = false; tunnelActive = false;
await Promise.resolve(); await Promise.resolve();
} }
async function checkTunnelTokens() { async function checkTunnelTokens() {
var oldTokens = tunnelClients.current(); var oldTokens = deps.tunnelClients.current();
var newTokens = await utils.iterateAllModules(function checkTokens(mod, domainList) { var newTokens = await iterateAllModules(function checkTokens(mod, domainList) {
if (mod.type !== 'dns@oauth3.org') { return null; } if (mod.type !== 'dns@oauth3.org') { return null; }
var domainStr = domainList.slice().sort().join(','); var domainStr = domainList.slice().sort().join(',');
@ -78,7 +103,7 @@ module.exports.create = function (deps, conf) {
} }
}); });
await Promise.all(Object.values(oldTokens).map(tunnelClients.remove)); await Promise.all(Object.values(oldTokens).map(deps.tunnelClients.remove));
if (!newTokens.length) { return; } if (!newTokens.length) { return; }
@ -162,10 +187,10 @@ module.exports.create = function (deps, conf) {
} }
publicAddress = addr; publicAddress = addr;
await utils.iterateAllModules(function setModuleDNS(mod, domainList) { await iterateAllModules(function setModuleDNS(mod, domainList) {
if (mod.type !== 'dns@oauth3.org' || mod.disabled) { return null; } if (mod.type !== 'dns@oauth3.org' || mod.disabled) { return null; }
return utils.getSession(mod.tokenId).then(function (session) { return getSession(mod.tokenId).then(function (session) {
return dnsCtrl.setDeviceAddress(session, addr, domainList); return dnsCtrl.setDeviceAddress(session, addr, domainList);
}).catch(function (err) { }).catch(function (err) {
console.log('error setting DNS records for', domainList.join(', ')); console.log('error setting DNS records for', domainList.join(', '));
@ -180,13 +205,13 @@ module.exports.create = function (deps, conf) {
// this returns a Promise, but since the functions we use are synchronous // this returns a Promise, but since the functions we use are synchronous
// and change our enclosed variables we don't need to wait for the return. // and change our enclosed variables we don't need to wait for the return.
utils.iterateAllModules(function (mod, domainList) { iterateAllModules(function (mod, domainList) {
if (mod.type !== 'dns@oauth3.org') { return; } if (mod.type !== 'dns@oauth3.org') { return; }
prevMods[mod.id] = { mod, domainList }; prevMods[mod.id] = { mod, domainList };
return true; return true;
}, prevConf); }, prevConf);
utils.iterateAllModules(function (mod, domainList) { iterateAllModules(function (mod, domainList) {
if (mod.type !== 'dns@oauth3.org') { return; } if (mod.type !== 'dns@oauth3.org') { return; }
curMods[mod.id] = { mod, domainList }; curMods[mod.id] = { mod, domainList };
@ -209,11 +234,8 @@ module.exports.create = function (deps, conf) {
// Then remove DNS records for the domains that we are no longer responsible for. // Then remove DNS records for the domains that we are no longer responsible for.
await Promise.all(Object.values(prevMods).map(function ({mod, domainList}) { await Promise.all(Object.values(prevMods).map(function ({mod, domainList}) {
// If the module was disabled before there should be any records that we need to clean up
if (mod.disabled) { return; }
var oldDomains; var oldDomains;
if (!curMods[mod.id] || curMods[mod.id].disabled || mod.tokenId !== curMods[mod.id].mod.tokenId) { if (!curMods[mod.id] || mod.tokenId !== curMods[mod.id].mod.tokenId) {
oldDomains = domainList.slice(); oldDomains = domainList.slice();
} else { } else {
oldDomains = domainList.filter(function (domain) { oldDomains = domainList.filter(function (domain) {
@ -227,7 +249,7 @@ module.exports.create = function (deps, conf) {
return; return;
} }
return utils.getSession(mod.tokenId).then(function (session) { return getSession(mod.tokenId).then(function (session) {
return dnsCtrl.removeDomains(session, oldDomains); return dnsCtrl.removeDomains(session, oldDomains);
}); });
}).filter(Boolean)); }).filter(Boolean));
@ -237,9 +259,6 @@ module.exports.create = function (deps, conf) {
// And add DNS records for any newly added domains. // And add DNS records for any newly added domains.
await Promise.all(Object.values(curMods).map(function ({mod, domainList}) { await Promise.all(Object.values(curMods).map(function ({mod, domainList}) {
// Don't set any new records if the module has been disabled.
if (mod.disabled) { return; }
var newDomains; var newDomains;
if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) { if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) {
newDomains = domainList.slice(); newDomains = domainList.slice();
@ -255,7 +274,7 @@ module.exports.create = function (deps, conf) {
return; return;
} }
return utils.getSession(mod.tokenId).then(function (session) { return getSession(mod.tokenId).then(function (session) {
return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains); return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains);
}); });
}).filter(Boolean)); }).filter(Boolean));
@ -321,6 +340,5 @@ module.exports.create = function (deps, conf) {
, getDeviceAddresses: dnsCtrl.getDeviceAddresses , getDeviceAddresses: dnsCtrl.getDeviceAddresses
, recheckPubAddr: recheckPubAddr , recheckPubAddr: recheckPubAddr
, updateConf: updateConf , updateConf: updateConf
, challenge
}; };
}; };

View File

@ -1,102 +0,0 @@
'use strict';
module.exports.create = function (deps, conf) {
async function getSession(id) {
var session = await deps.storage.tokens.get(id);
if (!session) {
throw new Error('no user token with ID "' + id + '"');
}
return session;
}
function iterateAllModules(action, curConf) {
curConf = curConf || conf;
var promises = [];
curConf.domains.forEach(function (dom) {
if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) {
return null;
}
// For the time being all of our things should only be tried once (regardless if it succeeded)
// TODO: revisit this behavior when we support multiple ways of setting records, and/or
// if we want to allow later modules to run if early modules fail.
promises.push(dom.modules.ddns.reduce(function (prom, mod) {
if (prom) { return prom; }
return action(mod, dom.names);
}, null));
});
curConf.ddns.modules.forEach(function (mod) {
promises.push(action(mod, mod.domains));
});
return Promise.all(promises.filter(Boolean));
}
var tldCache = {};
async function updateTldCache(provider) {
var reqObj = {
url: deps.OAUTH3.url.normalize(provider) + '/api/com.daplie.domains/prices'
, method: 'GET'
, json: true
};
var resp = await deps.OAUTH3.request(reqObj);
var tldObj = {};
resp.data.forEach(function (tldInfo) {
if (tldInfo.enabled) {
tldObj[tldInfo.tld] = true;
}
});
tldCache[provider] = {
time: Date.now()
, tlds: tldObj
};
return tldObj;
}
async function getTlds(provider) {
// If we've never cached the results we need to return the promise that will fetch the result,
// otherwise we can return the cached value. If the cached value has "expired", we can still
// return the cached value we just want to update the cache in parellel (making sure we only
// update once).
if (!tldCache[provider]) {
tldCache[provider] = {
updating: true
, tlds: updateTldCache(provider)
};
}
if (!tldCache[provider].updating && Date.now() - tldCache[provider].time > 24 * 60 * 60 * 1000) {
tldCache[provider].updating = true;
updateTldCache(provider);
}
return tldCache[provider].tlds;
}
async function splitDomains(provider, domains) {
var tlds = await getTlds(provider);
return domains.map(function (domain) {
var split = domain.split('.');
var tldSegCnt = tlds[split.slice(-2).join('.')] ? 2 : 1;
// Currently assuming that the sld can't contain dots, and that the tld can have at
// most one dot. Not 100% sure this is a valid assumption, but exceptions should be
// rare even if the assumption isn't valid.
return {
tld: split.slice(-tldSegCnt).join('.')
, sld: split.slice(-tldSegCnt - 1, -tldSegCnt).join('.')
, sub: split.slice(0, -tldSegCnt - 1).join('.')
};
});
}
return {
getSession
, iterateAllModules
, getTlds
, splitDomains
};
};

303
lib/goldilocks.js Normal file
View File

@ -0,0 +1,303 @@
'use strict';
module.exports.create = function (deps, config) {
console.log('config', config);
//var PromiseA = global.Promise;
var PromiseA = require('bluebird');
var listeners = require('./servers').listeners;
var domainUtils = require('./domain-utils');
var modules;
var addrProperties = [
'remoteAddress'
, 'remotePort'
, 'remoteFamily'
, 'localAddress'
, 'localPort'
, 'localFamily'
];
function nameMatchesDomains(name, domainList) {
return domainList.some(function (pattern) {
return domainUtils.match(pattern, name);
});
}
function loadModules() {
modules = {};
modules.tls = require('./modules/tls').create(deps, config, tcpHandler);
modules.http = require('./modules/http').create(deps, config, modules.tls.middleware);
}
function checkTcpProxy(conn, opts) {
var proxied = false;
// TCP Proxying (ie forwarding based on domain name not incoming port) only works for
// TLS wrapped connections, so if the opts don't give us a servername or don't tell us
// this is the decrypted side of a TLS connection we can't handle it here.
if (!opts.servername || !opts.encrypted) { return proxied; }
function proxy(mod) {
// First thing we need to add to the connection options is where to proxy the connection to
var newConnOpts = domainUtils.separatePort(mod.address || '');
newConnOpts.port = newConnOpts.port || mod.port;
newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
// Then we add all of the connection address information. We need to prefix all of the
// properties with '_' so we can provide the information to any connection `createConnection`
// implementation but not have the default implementation try to bind the same local port.
addrProperties.forEach(function (name) {
newConnOpts['_' + name] = opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
});
deps.proxy(conn, newConnOpts);
return true;
}
proxied = config.domains.some(function (dom) {
if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; }
if (!nameMatchesDomains(opts.servername, dom.names)) { return false; }
return dom.modules.tcp.some(function (mod) {
if (mod.type !== 'proxy') { return false; }
return proxy(mod);
});
});
proxied = proxied || config.tcp.modules.some(function (mod) {
if (mod.type !== 'proxy') { return false; }
if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; }
return proxy(mod);
});
return proxied;
}
// opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
function peek(conn, firstChunk, opts) {
if (!modules) {
loadModules();
}
opts.firstChunk = firstChunk;
conn.__opts = opts;
// TODO port/service-based routing can do here
// TLS byte 1 is handshake and byte 6 is client hello
if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) {
modules.tls.emit('connection', conn);
return;
}
// This doesn't work with TLS, but now that we know this isn't a TLS connection we can
// unshift the first chunk back onto the connection for future use. The unshift should
// happen after any listeners are attached to it but before any new data comes in.
if (!opts.hyperPeek) {
process.nextTick(function () {
conn.unshift(firstChunk);
});
}
// Connection is not TLS, check for HTTP next.
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
var firstStr = firstChunk.toString();
if (/HTTP\//i.test(firstStr)) {
modules.http.emit('connection', conn);
return;
}
}
console.warn('failed to identify protocol from first chunk', firstChunk);
conn.destroy();
}
function tcpHandler(conn, opts) {
function getProp(name) {
return opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
}
opts = opts || {};
var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' +
getProp('localAddress') + ':' + getProp('localPort');
console.log('[tcpHandler]', logName, 'connection started - encrypted: ' + (opts.encrypted || false));
var start = Date.now();
conn.on('timeout', function () {
console.log('[tcpHandler]', logName, 'connection timed out', (Date.now()-start)/1000);
});
conn.on('end', function () {
console.log('[tcpHandler]', logName, 'connection ended', (Date.now()-start)/1000);
});
conn.on('close', function () {
console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000);
});
if (checkTcpProxy(conn, opts)) { return; }
// XXX PEEK COMMENT XXX
// TODO we can have our cake and eat it too
// we can skip the need to wrap the TLS connection twice
// because we've already peeked at the data,
// but this needs to be handled better before we enable that
// (because it creates new edge cases)
if (opts.hyperPeek) {
console.log('hyperpeek');
peek(conn, opts.firstChunk, opts);
return;
}
function onError(err) {
console.error('[error] socket errored peeking -', err);
conn.destroy();
}
conn.once('error', onError);
conn.once('data', function (chunk) {
conn.removeListener('error', onError);
peek(conn, chunk, opts);
});
}
function udpHandler(port, msg) {
if (!Array.isArray(config.udp.modules)) {
return;
}
var socket = require('dgram').createSocket('udp4');
config.udp.modules.forEach(function (mod) {
if (mod.type !== 'forward') {
console.warn('found bad DNS module', mod);
return;
}
if (mod.ports.indexOf(port) < 0) {
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) {
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 = {};
addrProperties.forEach(function (name) {
newConnOpts['_'+name] = conn[name];
});
deps.proxy(conn, Object.assign(newConnOpts, dest));
};
}
deps.tunnel = deps.tunnel || {};
deps.tunnel.net = {
createConnection: function (opts, cb) {
console.log('[gl.tunnel] creating connection');
// 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 = {};
function usePair(err, reader) {
if (err) {
process.nextTick(function () {
writer.emit('error', err);
});
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._remoteFamily = wrapOpts.remoteFamily;
reader._localAddress = wrapOpts.localAddress;
reader._localPort = wrapOpts.localPort;
reader._localFamily = wrapOpts.localFamily;
} catch(e) {
}
tcpHandler(reader, wrapOpts);
process.nextTick(function () {
// 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();
});
}
wrapOpts.firstChunk = opts.data;
wrapOpts.hyperPeek = !!opts.data;
// We used to use `stream-pair` for non-tls connections, but there are places
// that require properties/functions to be present on the socket that aren't
// present on a JSStream so it caused problems.
writer = require('socket-pair').create(usePair);
return writer;
}
};
deps.tunnelClients = require('./tunnel-client-manager').create(deps, config);
deps.tunnelServer = require('./tunnel-server-manager').create(deps, config);
var listenPromises = [];
var tcpPortMap = {};
config.tcp.bind.filter(Number).forEach(function (port) {
tcpPortMap[port] = true;
});
(config.tcp.modules || []).forEach(function (mod) {
if (mod.type === 'forward') {
var forwarder = createTcpForwarder(mod);
mod.ports.forEach(function (port) {
if (!tcpPortMap[port]) {
console.log("forwarding port", port, "that wasn't specified in bind");
} else {
delete tcpPortMap[port];
}
listenPromises.push(listeners.tcp.add(port, forwarder));
});
}
else if (mod.type !== 'proxy') {
console.warn('unknown TCP module specified', mod);
}
});
var portList = Object.keys(tcpPortMap).map(Number).sort();
portList.forEach(function (port) {
listenPromises.push(listeners.tcp.add(port, tcpHandler));
});
if (config.udp.bind) {
config.udp.bind.forEach(function (port) {
listenPromises.push(listeners.udp.add(port, udpHandler.bind(port)));
});
}
if (!config.mdns.disabled) {
require('./mdns').start(deps, config, portList[0]);
}
return PromiseA.all(listenPromises);
};

View File

@ -2,7 +2,6 @@
var PromiseA = require('bluebird'); var PromiseA = require('bluebird');
var queryName = '_cloud._tcp.local'; var queryName = '_cloud._tcp.local';
var dnsSuite = require('dns-suite');
function createResponse(name, ownerIds, packet, ttl, mainPort) { function createResponse(name, ownerIds, packet, ttl, mainPort) {
var rpacket = { var rpacket = {
@ -86,19 +85,20 @@ function createResponse(name, ownerIds, packet, ttl, mainPort) {
}); });
}); });
return dnsSuite.DNSPacket.write(rpacket); return require('dns-suite').DNSPacket.write(rpacket);
} }
module.exports.create = function (deps, config) { module.exports.start = function (deps, config, mainPort) {
var socket; var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true });
var dns = require('dns-suite');
var nextBroadcast = -1; var nextBroadcast = -1;
function handlePacket(message, rinfo) { socket.on('message', function (message, rinfo) {
// console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port); // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
var packet; var packet;
try { try {
packet = dnsSuite.DNSPacket.parse(message); packet = dns.DNSPacket.parse(message);
} }
catch (er) { catch (er) {
// `dns-suite` actually errors on a lot of the packets floating around in our network, // `dns-suite` actually errors on a lot of the packets floating around in our network,
@ -108,12 +108,16 @@ module.exports.create = function (deps, config) {
} }
// Only respond to queries. // Only respond to queries.
if (packet.header.qr !== 0) { return; } if (packet.header.qr !== 0) {
return;
}
// Only respond if they were asking for cloud devices. // Only respond if they were asking for cloud devices.
if (packet.question.length !== 1) { return; } if (packet.question.length !== 1 || packet.question[0].name !== queryName) {
if (packet.question[0].name !== queryName) { return; } return;
if (packet.question[0].typeName !== 'PTR') { return; } }
if (packet.question[0].className !== 'IN' ) { return; } if (packet.question[0].typeName !== 'PTR' || packet.question[0].className !== 'IN' ) {
return;
}
var proms = [ var proms = [
deps.storage.mdnsId.get() deps.storage.mdnsId.get()
@ -127,7 +131,7 @@ module.exports.create = function (deps, config) {
]; ];
PromiseA.all(proms).then(function (results) { PromiseA.all(proms).then(function (results) {
var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, deps.tcp.mainPort); var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, mainPort);
var now = Date.now(); var now = Date.now();
if (now > nextBroadcast) { if (now > nextBroadcast) {
socket.send(resp, config.mdns.port, config.mdns.broadcast); socket.send(resp, config.mdns.port, config.mdns.broadcast);
@ -136,68 +140,18 @@ module.exports.create = function (deps, config) {
socket.send(resp, rinfo.port, rinfo.address); socket.send(resp, rinfo.port, rinfo.address);
} }
}); });
} });
function start() { socket.bind(config.mdns.port, function () {
socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); var addr = this.address();
socket.on('message', handlePacket); console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port);
return new Promise(function (resolve, reject) { socket.setBroadcast(true);
socket.once('error', reject); socket.addMembership(config.mdns.broadcast);
// This is supposed to be a local device discovery mechanism, so we shouldn't
socket.bind(config.mdns.port, function () { // need to hop through any gateways. This helps with security by making it
var addr = this.address(); // much more difficult for someone to use us as part of a DDoS attack by
console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port); // spoofing the UDP address a request came from.
socket.setTTL(1);
socket.setBroadcast(true); });
socket.addMembership(config.mdns.broadcast);
// This is supposed to be a local device discovery mechanism, so we shouldn't
// need to hop through any gateways. This helps with security by making it
// much more difficult for someone to use us as part of a DDoS attack by
// spoofing the UDP address a request came from.
socket.setTTL(1);
socket.removeListener('error', reject);
resolve();
});
});
}
function stop() {
return new Promise(function (resolve, reject) {
socket.once('error', reject);
socket.close(function () {
socket.removeListener('error', reject);
socket = null;
resolve();
});
});
}
function updateConf() {
var promise;
if (config.mdns.disabled) {
if (socket) {
promise = stop();
}
} else {
if (!socket) {
promise = start();
} else if (socket.address().port !== config.mdns.port) {
promise = stop().then(start);
} else {
// Can't check membership, so just add the current broadcast address to make sure
// it's set. If it's already set it will throw an exception (at least on linux).
try {
socket.addMembership(config.mdns.broadcast);
} catch (e) {}
promise = Promise.resolve();
}
}
}
updateConf();
return {
updateConf
};
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
module.exports.create = function (deps, conf, tcpMods) { module.exports.create = function (deps, conf, greenlockMiddleware) {
var PromiseA = require('bluebird'); var PromiseA = require('bluebird');
var statAsync = PromiseA.promisify(require('fs').stat); var statAsync = PromiseA.promisify(require('fs').stat);
var domainMatches = require('../domain-utils').match; var domainMatches = require('../domain-utils').match;
@ -162,8 +162,8 @@ module.exports.create = function (deps, conf, tcpMods) {
return false; return false;
} }
if (deps.stunneld.isClientDomain(separatePort(headers.host).host)) { if (deps.tunnelServer.isClientDomain(separatePort(headers.host).host)) {
deps.stunneld.handleClientConn(conn); deps.tunnelServer.handleClientConn(conn);
process.nextTick(function () { process.nextTick(function () {
conn.unshift(opts.firstChunk); conn.unshift(opts.firstChunk);
conn.resume(); conn.resume();
@ -172,7 +172,7 @@ module.exports.create = function (deps, conf, tcpMods) {
} }
if (!acmeServer) { if (!acmeServer) {
acmeServer = require('http').createServer(tcpMods.tls.middleware); acmeServer = require('http').createServer(greenlockMiddleware);
} }
return emitConnection(acmeServer, conn, opts); return emitConnection(acmeServer, conn, opts);
} }
@ -214,8 +214,8 @@ module.exports.create = function (deps, conf, tcpMods) {
return emitConnection(adminServer, conn, opts); return emitConnection(adminServer, conn, opts);
} }
if (deps.stunneld.isAdminDomain(host)) { if (deps.tunnelServer.isAdminDomain(host)) {
deps.stunneld.handleAdminConn(conn); deps.tunnelServer.handleAdminConn(conn);
process.nextTick(function () { process.nextTick(function () {
conn.unshift(opts.firstChunk); conn.unshift(opts.firstChunk);
conn.resume(); conn.resume();
@ -241,7 +241,7 @@ module.exports.create = function (deps, conf, tcpMods) {
res.statusCode = 502; res.statusCode = 502;
res.setHeader('Connection', 'close'); res.setHeader('Connection', 'close');
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.end(tcpMods.proxy.getRespBody(err, conf.debug)); res.end(require('../proxy-conn').getRespBody(err, conf.debug));
}); });
proxyServer = http.createServer(function (req, res) { proxyServer = http.createServer(function (req, res) {
@ -292,7 +292,7 @@ module.exports.create = function (deps, conf, tcpMods) {
newConnOpts.remoteAddress = opts.address || conn.remoteAddress; newConnOpts.remoteAddress = opts.address || conn.remoteAddress;
newConnOpts.remotePort = opts.port || conn.remotePort; newConnOpts.remotePort = opts.port || conn.remotePort;
tcpMods.proxy(conn, newConnOpts, opts.firstChunk); deps.proxy(conn, newConnOpts, opts.firstChunk);
} }
function checkProxy(mod, conn, opts, headers) { function checkProxy(mod, conn, opts, headers) {

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
module.exports.create = function (deps, config, tcpMods) { module.exports.create = function (deps, config, netHandler) {
var path = require('path'); var path = require('path');
var tls = require('tls'); var tls = require('tls');
var parseSni = require('sni'); var parseSni = require('sni');
@ -86,7 +86,8 @@ module.exports.create = function (deps, config, tcpMods) {
, challenges: { , challenges: {
'http-01': require('le-challenge-fs').create({ 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 })
, 'dns-01': deps.ddns.challenge // TODO dns-01
//, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug })
} }
, challengeType: 'http-01' , challengeType: 'http-01'
@ -207,7 +208,7 @@ module.exports.create = function (deps, config, tcpMods) {
var terminateServer = tls.createServer(terminatorOpts, function (socket) { var terminateServer = tls.createServer(terminatorOpts, function (socket) {
console.log('(post-terminated) tls connection, addr:', extractSocketProp(socket, 'remoteAddress')); console.log('(post-terminated) tls connection, addr:', extractSocketProp(socket, 'remoteAddress'));
tcpMods.tcpHandler(socket, { netHandler(socket, {
servername: socket.servername servername: socket.servername
, encrypted: true , encrypted: true
// remoteAddress... ugh... https://github.com/nodejs/node/issues/8854 // remoteAddress... ugh... https://github.com/nodejs/node/issues/8854
@ -231,7 +232,7 @@ module.exports.create = function (deps, config, tcpMods) {
newConnOpts.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress'); newConnOpts.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress');
newConnOpts.remotePort = opts.port || extractSocketProp(socket, 'remotePort'); newConnOpts.remotePort = opts.port || extractSocketProp(socket, 'remotePort');
tcpMods.proxy(socket, newConnOpts, opts.firstChunk, function () { deps.proxy(socket, newConnOpts, opts.firstChunk, function () {
// This function is called in the event of a connection error and should decrypt // This function is called in the event of a connection error and should decrypt
// the socket so the proxy module can send a 502 HTTP response. // the socket so the proxy module can send a 502 HTTP response.
var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true}); var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
@ -290,8 +291,8 @@ module.exports.create = function (deps, config, tcpMods) {
return; return;
} }
if (deps.stunneld.isClientDomain(opts.servername)) { if (deps.tunnelServer.isClientDomain(opts.servername)) {
deps.stunneld.handleClientConn(socket); deps.tunnelServer.handleClientConn(socket);
if (!opts.hyperPeek) { if (!opts.hyperPeek) {
process.nextTick(function () { process.nextTick(function () {
socket.unshift(opts.firstChunk); socket.unshift(opts.firstChunk);

View File

@ -32,7 +32,7 @@ module.exports.getRespBody = getRespBody;
module.exports.sendBadGateway = sendBadGateway; module.exports.sendBadGateway = sendBadGateway;
module.exports.create = function (deps, config) { module.exports.create = function (deps, config) {
function proxy(conn, newConnOpts, firstChunk, decrypt) { return function proxy(conn, newConnOpts, firstChunk, decrypt) {
var connected = false; var connected = false;
newConnOpts.allowHalfOpen = true; newConnOpts.allowHalfOpen = true;
var newConn = deps.net.createConnection(newConnOpts, function () { var newConn = deps.net.createConnection(newConnOpts, function () {
@ -73,9 +73,5 @@ module.exports.create = function (deps, config) {
newConn.on('close', function () { newConn.on('close', function () {
conn.destroy(); conn.destroy();
}); });
} };
proxy.getRespBody = getRespBody;
proxy.sendBadGateway = sendBadGateway;
return proxy;
}; };

View File

@ -10,16 +10,20 @@ module.exports.addTcpListener = function (port, handler) {
if (stat) { if (stat) {
if (stat._closing) { if (stat._closing) {
stat.server.destroy(); module.exports.destroyTcpListener(port);
} else { }
// We're already listening on the port, so we only have 2 options. We can either else if (handler !== stat.handler) {
// replace the handler or reject with an error. (Though neither is really needed
// if the handlers are the same). Until there is reason to do otherwise we are // we'll replace the current listener
// opting for the replacement.
stat.handler = handler; stat.handler = handler;
resolve(); resolve();
return; return;
} }
else {
// this exact listener is already open
resolve();
return;
}
} }
var enableDestroy = require('server-destroy'); var enableDestroy = require('server-destroy');
@ -30,7 +34,7 @@ module.exports.addTcpListener = function (port, handler) {
stat = serversMap[port] = { stat = serversMap[port] = {
server: server server: server
, handler: handler , handler: handler
, _closing: false , _closing: null
}; };
// Add .destroy so we can close all open connections. Better if added before listen // Add .destroy so we can close all open connections. Better if added before listen
@ -62,24 +66,14 @@ module.exports.addTcpListener = function (port, handler) {
}); });
}); });
}; };
module.exports.closeTcpListener = function (port, timeout) { module.exports.closeTcpListener = function (port) {
return new PromiseA(function (resolve) { return new PromiseA(function (resolve) {
var stat = serversMap[port]; var stat = serversMap[port];
if (!stat) { if (!stat) {
resolve(); resolve();
return; return;
} }
stat._closing = true; stat.server.once('close', resolve);
var timeoutId;
if (timeout) {
timeoutId = setTimeout(() => stat.server.destroy(), timeout);
}
stat.server.once('close', function () {
clearTimeout(timeoutId);
resolve();
});
stat.server.close(); stat.server.close();
}); });
}; };
@ -90,9 +84,7 @@ module.exports.destroyTcpListener = function (port) {
} }
}; };
module.exports.listTcpListeners = function () { module.exports.listTcpListeners = function () {
return Object.keys(serversMap).map(Number).filter(function (port) { return Object.keys(serversMap).map(Number).filter(Boolean);
return port && !serversMap[port]._closing;
});
}; };

View File

@ -63,29 +63,15 @@ module.exports.create = function (deps, config) {
}); });
} }
var configEnabled = false; if (config.socks5 && config.socks5.enabled) {
function updateConf() { start(config.socks5.port).catch(function (err) {
var wanted = config.socks5 && config.socks5.enabled; console.error('failed to start Socks5 proxy', err);
});
if (configEnabled && !wanted) {
stop().catch(function (err) {
console.error('failed to stop socks5 proxy on config change', err);
});
configEnabled = false;
}
if (wanted && !configEnabled) {
start(config.socks5.port).catch(function (err) {
console.error('failed to start Socks5 proxy', err);
});
configEnabled = true;
}
} }
process.nextTick(updateConf);
return { return {
curState curState: curState
, start , start: start
, stop , stop: stop
, updateConf
}; };
}; };

View File

@ -1,242 +0,0 @@
'use strict';
module.exports.create = function (deps, config) {
console.log('config', config);
var listeners = require('../servers').listeners.tcp;
var domainUtils = require('../domain-utils');
var modules;
var addrProperties = [
'remoteAddress'
, 'remotePort'
, 'remoteFamily'
, 'localAddress'
, 'localPort'
, 'localFamily'
];
function nameMatchesDomains(name, domainList) {
return domainList.some(function (pattern) {
return domainUtils.match(pattern, name);
});
}
function proxy(mod, conn, opts) {
// First thing we need to add to the connection options is where to proxy the connection to
var newConnOpts = domainUtils.separatePort(mod.address || '');
newConnOpts.port = newConnOpts.port || mod.port;
newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
// Then we add all of the connection address information. We need to prefix all of the
// properties with '_' so we can provide the information to any connection `createConnection`
// implementation but not have the default implementation try to bind the same local port.
addrProperties.forEach(function (name) {
newConnOpts['_' + name] = opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
});
modules.proxy(conn, newConnOpts);
return true;
}
function checkTcpProxy(conn, opts) {
var proxied = false;
// TCP Proxying (ie routing based on domain name [vs local port]) only works for
// TLS wrapped connections, so if the opts don't give us a servername or don't tell us
// this is the decrypted side of a TLS connection we can't handle it here.
if (!opts.servername || !opts.encrypted) { return proxied; }
proxied = config.domains.some(function (dom) {
if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; }
if (!nameMatchesDomains(opts.servername, dom.names)) { return false; }
return dom.modules.tcp.some(function (mod) {
if (mod.type !== 'proxy') { return false; }
return proxy(mod, conn, opts);
});
});
proxied = proxied || config.tcp.modules.some(function (mod) {
if (mod.type !== 'proxy') { return false; }
if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; }
return proxy(mod, conn, opts);
});
return proxied;
}
function checkTcpForward(conn, opts) {
// TCP forwarding (ie routing connections based on local port) requires the local port
if (!conn.localPort) { return false; }
return config.tcp.modules.some(function (mod) {
if (mod.type !== 'forward') { return false; }
if (mod.ports.indexOf(conn.localPort) < 0) { return false; }
return proxy(mod, conn, opts);
});
}
// opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
function peek(conn, firstChunk, opts) {
opts.firstChunk = firstChunk;
conn.__opts = opts;
// TODO port/service-based routing can do here
// TLS byte 1 is handshake and byte 6 is client hello
if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) {
modules.tls.emit('connection', conn);
return;
}
// This doesn't work with TLS, but now that we know this isn't a TLS connection we can
// unshift the first chunk back onto the connection for future use. The unshift should
// happen after any listeners are attached to it but before any new data comes in.
if (!opts.hyperPeek) {
process.nextTick(function () {
conn.unshift(firstChunk);
});
}
// Connection is not TLS, check for HTTP next.
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
var firstStr = firstChunk.toString();
if (/HTTP\//i.test(firstStr)) {
modules.http.emit('connection', conn);
return;
}
}
console.warn('failed to identify protocol from first chunk', firstChunk);
conn.destroy();
}
function tcpHandler(conn, opts) {
function getProp(name) {
return opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
}
opts = opts || {};
var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' +
getProp('localAddress') + ':' + getProp('localPort');
console.log('[tcpHandler]', logName, 'connection started - encrypted: ' + (opts.encrypted || false));
var start = Date.now();
conn.on('timeout', function () {
console.log('[tcpHandler]', logName, 'connection timed out', (Date.now()-start)/1000);
});
conn.on('end', function () {
console.log('[tcpHandler]', logName, 'connection ended', (Date.now()-start)/1000);
});
conn.on('close', function () {
console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000);
});
if (checkTcpForward(conn, opts)) { return; }
if (checkTcpProxy(conn, opts)) { return; }
// XXX PEEK COMMENT XXX
// TODO we can have our cake and eat it too
// we can skip the need to wrap the TLS connection twice
// because we've already peeked at the data,
// but this needs to be handled better before we enable that
// (because it creates new edge cases)
if (opts.hyperPeek) {
console.log('hyperpeek');
peek(conn, opts.firstChunk, opts);
return;
}
function onError(err) {
console.error('[error] socket errored peeking -', err);
conn.destroy();
}
conn.once('error', onError);
conn.once('data', function (chunk) {
conn.removeListener('error', onError);
peek(conn, chunk, opts);
});
}
process.nextTick(function () {
modules = {};
modules.tcpHandler = tcpHandler;
modules.proxy = require('./proxy-conn').create(deps, config);
modules.tls = require('./tls').create(deps, config, modules);
modules.http = require('./http').create(deps, config, modules);
});
function updateListeners() {
var current = listeners.list();
var wanted = config.tcp.bind;
if (!Array.isArray(wanted)) { wanted = []; }
wanted = wanted.map(Number).filter((port) => port > 0 && port < 65356);
var closeProms = current.filter(function (port) {
return wanted.indexOf(port) < 0;
}).map(function (port) {
return listeners.close(port, 1000);
});
// We don't really need to filter here since listening on the same port with the
// same handler function twice is basically a no-op.
var openProms = wanted.map(function (port) {
return listeners.add(port, tcpHandler);
});
return Promise.all(closeProms.concat(openProms));
}
var mainPort;
function updateConf() {
updateListeners().catch(function (err) {
console.error('Error updating TCP listeners to match bind configuration');
console.error(err);
});
var unforwarded = {};
config.tcp.bind.forEach(function (port) {
unforwarded[port] = true;
});
config.tcp.modules.forEach(function (mod) {
if (['forward', 'proxy'].indexOf(mod.type) < 0) {
console.warn('unknown TCP module type specified', JSON.stringify(mod));
}
if (mod.type !== 'forward') { return; }
mod.ports.forEach(function (port) {
if (!unforwarded[port]) {
console.warn('trying to forward TCP port ' + port + ' multiple times or it is unbound');
} else {
delete unforwarded[port];
}
});
});
// Not really sure what we can reasonably do to prevent this. At least not without making
// our configuration validation more complicated.
if (!Object.keys(unforwarded).length) {
console.warn('no bound TCP ports are not being forwarded, admin interface will be inaccessible');
}
// If we are listening on port 443 make that the main port we respond to mDNS queries with
// otherwise choose the lowest number port we are bound to but not forwarding.
if (unforwarded['443']) {
mainPort = 443;
} else {
mainPort = Object.keys(unforwarded).map(Number).sort((a, b) => a - b)[0];
}
}
updateConf();
var result = {
updateConf
, handler: tcpHandler
};
Object.defineProperty(result, 'mainPort', {enumerable: true, get: () => mainPort});
return result;
};

View File

@ -6,52 +6,6 @@ module.exports.create = function (deps, config) {
var activeTunnels = {}; var activeTunnels = {};
var activeDomains = {}; var activeDomains = {};
var customNet = {
createConnection: function (opts, cb) {
console.log('[gl.tunnel] creating connection');
// 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;
function usePair(err, reader) {
if (err) {
process.nextTick(function () {
writer.emit('error', err);
});
return;
}
var wrapOpts = Object.assign({localAddress: '127.0.0.2', localPort: 'tunnel-0'}, opts);
wrapOpts.firstChunk = opts.data;
wrapOpts.hyperPeek = !!opts.data;
// Also override the remote and local address info. We use `defineProperty` because
// otherwise we run into problems of setting properties with only getters defined.
Object.defineProperty(reader, 'remoteAddress', { value: wrapOpts.remoteAddress });
Object.defineProperty(reader, 'remotePort', { value: wrapOpts.remotePort });
Object.defineProperty(reader, 'remoteFamiliy', { value: wrapOpts.remoteFamiliy });
Object.defineProperty(reader, 'localAddress', { value: wrapOpts.localAddress });
Object.defineProperty(reader, 'localPort', { value: wrapOpts.localPort });
Object.defineProperty(reader, 'localFamiliy', { value: wrapOpts.localFamiliy });
deps.tcp.handler(reader, wrapOpts);
process.nextTick(function () {
// 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();
});
}
// We used to use `stream-pair` for non-tls connections, but there are places
// that require properties/functions to be present on the socket that aren't
// present on a JSStream so it caused problems.
writer = require('socket-pair').create(usePair);
return writer;
}
};
function fillData(data) { function fillData(data) {
if (typeof data === 'string') { if (typeof data === 'string') {
data = { jwt: data }; data = { jwt: data };
@ -116,7 +70,7 @@ module.exports.create = function (deps, config) {
// get the promise that should tell us more about if it worked or not. // get the promise that should tell us more about if it worked or not.
activeTunnels[data.tunnelUrl] = stunnel.connect({ activeTunnels[data.tunnelUrl] = stunnel.connect({
stunneld: data.tunnelUrl stunneld: data.tunnelUrl
, net: customNet , net: deps.tunnel.net
// NOTE: the ports here aren't that important since we are providing a custom // NOTE: the ports here aren't that important since we are providing a custom
// `net.createConnection` that doesn't actually use the port. What is important // `net.createConnection` that doesn't actually use the port. What is important
// is that any services we are interested in are listed in this object and have // is that any services we are interested in are listed in this object and have

View File

@ -1,131 +1,61 @@
'use strict'; 'use strict';
function httpsTunnel(servername, conn) { module.exports.create = function (deps, config) {
console.error('tunnel server received encrypted connection to', servername); if (!config.tunnelServer || !Array.isArray(config.tunnelServer.servernames) || !config.tunnelServer.secret) {
conn.end(); return {
} isAdminDomain: function () { return false; }
function handleHttp(servername, conn) { , isClientDomain: function () { return false; }
console.error('tunnel server received un-encrypted connection to', servername); };
conn.end([ }
'HTTP/1.1 404 Not Found'
, 'Date: ' + (new Date()).toUTCString()
, 'Connection: close'
, 'Content-Type: text/html'
, 'Content-Length: 9'
, ''
, 'Not Found'
].join('\r\n'));
}
function rejectNonWebsocket(req, res) {
// status code 426 = Upgrade Required
res.statusCode = 426;
res.setHeader('Content-Type', 'application/json');
res.send({error: { message: 'Only websockets accepted for tunnel server' }});
}
var defaultConfig = { var tunnelOpts = Object.assign({}, config.tunnelServer);
servernames: [] // This function should not be called because connections to the admin domains
, secret: null
};
var tunnelFuncs = {
// These functions should not be called because connections to the admin domains
// should already be decrypted, and connections to non-client domains should never // should already be decrypted, and connections to non-client domains should never
// be given to us in the first place. // be given to us in the first place.
httpsTunnel: httpsTunnel tunnelOpts.httpsTunnel = function (servername, conn) {
, httpsInvalid: httpsTunnel console.error('tunnel server received encrypted connection to', servername);
// These function should not be called because ACME challenges should be handled conn.end();
};
tunnelOpts.httpsInvalid = tunnelOpts.httpsTunnel;
// This function should not be called because ACME challenges should be handled
// before admin domain connections are given to us, and the only non-encrypted // before admin domain connections are given to us, and the only non-encrypted
// client connections that should be given to us are ACME challenges. // client connections that should be given to us are ACME challenges.
, handleHttp: handleHttp tunnelOpts.handleHttp = function (servername, conn) {
, handleInsecureHttp: handleHttp console.error('tunnel server received un-encrypted connection to', servername);
}; conn.end([
'HTTP/1.1 404 Not Found'
, 'Date: ' + (new Date()).toUTCString()
, 'Connection: close'
, 'Content-Type: text/html'
, 'Content-Length: 9'
, ''
, 'Not Found'
].join('\r\n'));
};
tunnelOpts.handleInsecureHttp = tunnelOpts.handleHttp;
module.exports.create = function (deps, config) { var tunnelServer = require('stunneld').create(tunnelOpts);
var equal = require('deep-equal');
var enableDestroy = require('server-destroy');
var currentOpts = Object.assign({}, defaultConfig);
var httpServer, wsServer, stunneld; var httpServer = require('http').createServer(function (req, res) {
function start() { // status code 426 = Upgrade Required
if (httpServer || wsServer || stunneld) { res.statusCode = 426;
throw new Error('trying to start already started tunnel server'); res.setHeader('Content-Type', 'application/json');
} res.end(JSON.stringify({error: {
httpServer = require('http').createServer(rejectNonWebsocket); message: 'Only websockets accepted for tunnel server'
enableDestroy(httpServer); }}));
});
wsServer = new (require('ws').Server)({ server: httpServer }); var wsServer = new (require('ws').Server)({ server: httpServer });
wsServer.on('connection', tunnelServer.ws);
var tunnelOpts = Object.assign({}, tunnelFuncs, currentOpts);
stunneld = require('stunneld').create(tunnelOpts);
wsServer.on('connection', stunneld.ws);
}
function stop() {
if (!httpServer || !wsServer || !stunneld) {
throw new Error('trying to stop unstarted tunnel server (or it got into semi-initialized state');
}
wsServer.close();
wsServer = null;
httpServer.destroy();
httpServer = null;
// Nothing to close here, just need to set it to null to allow it to be garbage-collected.
stunneld = null;
}
function updateConf() {
var newOpts = Object.assign({}, defaultConfig, config.tunnelServer);
if (!Array.isArray(newOpts.servernames)) {
newOpts.servernames = [];
}
var trimmedOpts = {
servernames: newOpts.servernames.slice().sort()
, secret: newOpts.secret
};
if (equal(trimmedOpts, currentOpts)) {
return;
}
currentOpts = trimmedOpts;
// Stop what's currently running, then if we are still supposed to be running then we
// can start it again with the updated options. It might be possible to make use of
// the existing http and ws servers when the config changes, but I'm not sure what
// state the actions needed to close all existing connections would put them in.
if (httpServer || wsServer || stunneld) {
stop();
}
if (currentOpts.servernames.length && currentOpts.secret) {
start();
}
}
process.nextTick(updateConf);
return { return {
isAdminDomain: function (domain) { isAdminDomain: function (domain) {
return currentOpts.servernames.indexOf(domain) !== -1; return config.tunnelServer.servernames.indexOf(domain) !== -1;
} }
, handleAdminConn: function (conn) { , handleAdminConn: function (conn) {
if (!httpServer) { httpServer.emit('connection', conn);
console.error(new Error('handleAdminConn called with no active tunnel server'));
conn.end();
} else {
return httpServer.emit('connection', conn);
}
} }
, isClientDomain: function (domain) { , isClientDomain: tunnelServer.isClientDomain
if (!stunneld) { return false; } , handleClientConn: tunnelServer.tcp
return stunneld.isClientDomain(domain);
}
, handleClientConn: function (conn) {
if (!stunneld) {
console.error(new Error('handleClientConn called with no active tunnel server'));
conn.end();
} else {
return stunneld.tcp(conn);
}
}
, updateConf
}; };
}; };

View File

@ -1,57 +0,0 @@
'use strict';
module.exports.create = function (deps, config) {
var listeners = require('./servers').listeners.udp;
function packetHandler(port, msg) {
if (!Array.isArray(config.udp.modules)) {
return;
}
var socket = require('dgram').createSocket('udp4');
config.udp.modules.forEach(function (mod) {
if (mod.type !== 'forward') {
// To avoid logging bad modules every time we get a UDP packet we assign a warned
// property to the module (non-enumerable so it won't be saved to the config or
// show up in the API).
if (!mod.warned) {
console.warn('found bad DNS module', mod);
Object.defineProperty(mod, 'warned', {value: true, enumerable: false});
}
return;
}
if (mod.ports.indexOf(port) < 0) {
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 updateListeners() {
var current = listeners.list();
var wanted = config.udp.bind;
if (!Array.isArray(wanted)) { wanted = []; }
wanted = wanted.map(Number).filter((port) => port > 0 && port < 65356);
current.forEach(function (port) {
if (wanted.indexOf(port) < 0) {
listeners.close(port);
}
});
wanted.forEach(function (port) {
if (current.indexOf(port) < 0) {
listeners.add(port, packetHandler.bind(port));
}
});
}
updateListeners();
return {
updateConf: updateListeners
};
};

View File

@ -48,15 +48,13 @@ function create(conf) {
modules = { modules = {
storage: require('./storage').create(deps, conf) storage: require('./storage').create(deps, conf)
, proxy: require('./proxy-conn').create(deps, conf)
, socks5: require('./socks5-server').create(deps, conf) , socks5: require('./socks5-server').create(deps, conf)
, ddns: require('./ddns').create(deps, conf) , ddns: require('./ddns').create(deps, conf)
, mdns: require('./mdns').create(deps, conf)
, udp: require('./udp').create(deps, conf)
, tcp: require('./tcp').create(deps, conf)
, stunneld: require('./tunnel-server-manager').create(deps, config)
}; };
Object.assign(deps, modules); Object.assign(deps, modules);
require('./goldilocks.js').create(deps, conf);
process.removeListener('message', create); process.removeListener('message', create);
process.on('message', update); process.on('message', update);
} }

2316
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "goldilocks", "name": "goldilocks",
"version": "1.1.6", "version": "1.1.3",
"description": "The node.js webserver that's just right, Greenlock (HTTPS/TLS/SSL via ACME/Let's Encrypt) and tunneling (RVPN) included.", "description": "The node.js webserver that's just right, Greenlock (HTTPS/TLS/SSL via ACME/Let's Encrypt) and tunneling (RVPN) included.",
"main": "bin/goldilocks.js", "main": "bin/goldilocks.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git.coolaj86.com:coolaj86/goldilocks.js.git" "url": "git@git.daplie.com:Daplie/goldilocks.js.git"
}, },
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", "author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)",
"license": "(MIT OR Apache-2.0)", "license": "(MIT OR Apache-2.0)",
"scripts": { "scripts": {
"test": "node bin/goldilocks.js -p 8443 -d /tmp/" "test": "node bin/goldilocks.js -p 8443 -d /tmp/"
@ -34,41 +34,42 @@
"server" "server"
], ],
"bugs": { "bugs": {
"url": "https://git.coolaj86.com/coolaj86/goldilocks.js/issues" "url": "https://git.daplie.com/Daplie/server-https/issues"
}, },
"homepage": "https://git.coolaj86.com/coolaj86/goldilocks.js", "homepage": "https://git.daplie.com/Daplie/goldilocks.js",
"dependencies": { "dependencies": {
"bluebird": "^3.4.6", "bluebird": "^3.4.6",
"body-parser": "1", "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
"commander": "^2.9.0", "commander": "^2.9.0",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"dns-suite": "1", "dns-suite": "git+https://git@git.daplie.com/Daplie/dns-suite#v1",
"express": "4", "express": "git+https://github.com/expressjs/express.git#4.x",
"finalhandler": "^0.4.0", "finalhandler": "^0.4.0",
"greenlock": "2.1", "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
"http-proxy": "^1.16.2", "http-proxy": "^1.16.2",
"human-readable-ids": "1", "human-readable-ids": "git+https://git.daplie.com/Daplie/human-readable-ids-js#master",
"ipaddr.js": "v1.3", "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
"js-yaml": "^3.8.3", "js-yaml": "^3.8.3",
"jsonschema": "^1.2.0", "jsonschema": "^1.2.0",
"jsonwebtoken": "^7.4.0", "jsonwebtoken": "^7.4.0",
"le-challenge-fs": "2", "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", "le-challenge-sni": "^2.0.1",
"le-store-certbot": "2", "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master",
"localhost.daplie.me-certificates": "^1.3.5", "localhost.daplie.me-certificates": "^1.3.5",
"network": "^0.4.0", "network": "^0.4.0",
"recase": "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", "request": "^2.81.0",
"scmp": "1", "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", "sni": "^1.0.0",
"socket-pair": "^1.0.3", "socket-pair": "^1.0.3",
"socksv5": "0.0.6", "socksv5": "0.0.6",
"stunnel": "1.0", "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1",
"stunneld": "0.9", "stunneld": "git+https://git.daplie.com/Daplie/node-tunnel-server.git#v1",
"tunnel-packer": "^1.3.0", "tunnel-packer": "^1.3.0",
"ws": "^2.3.1" "ws": "^2.3.1"
} }

3
terms.sh Normal file
View File

@ -0,0 +1,3 @@
# adding TOS to TXT DNS Record
daplie dns:set -n _terms._cloud.localhost.foo.daplie.me -t TXT -a '{"url":"oauth3.org/tos/draft","explicit":true}' --ttl 3600
daplie dns:set -n _terms._cloud.localhost.alpha.daplie.me -t TXT -a '{"url":"oauth3.org/tos/draft","explicit":true}' --ttl 3600

17
test-chain.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
node serve.js \
--port 8443 \
--key node_modules/localhost.daplie.me-certificates/privkey.pem \
--cert node_modules/localhost.daplie.me-certificates/fullchain.pem \
--root node_modules/localhost.daplie.me-certificates/root.pem \
-c "$(cat node_modules/localhost.daplie.me-certificates/root.pem)" &
PID=$!
sleep 1
curl -s --insecure http://localhost.daplie.me:8443 > ./root.pem
curl -s https://localhost.daplie.me:8443 --cacert ./root.pem
rm ./root.pem
kill $PID 2>/dev/null