AJ ONeal
6 years ago
15 changed files with 736 additions and 330 deletions
@ -1,21 +1,41 @@ |
|||
Copyright 2016 AJ ONeal |
|||
|
|||
This is open source software; you can redistribute it and/or modify it under the |
|||
terms of either: |
|||
|
|||
a) the "MIT License" |
|||
b) the "Apache-2.0 License" |
|||
|
|||
MIT License |
|||
|
|||
Copyright (c) 2016 Daplie, Inc |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
|||
Apache-2.0 License Summary |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
@ -1,85 +1,124 @@ |
|||
# Telebit™ Relay |
|||
# Telebit Relay |
|||
|
|||
A server that works in combination with [stunnel.js](https://git.coolaj86.com/coolaj86/tunnel-client.js) |
|||
Friends don't let friends localhost™ |
|||
|
|||
A server that works in combination with [Telebit Remote](https://git.coolaj86.com/coolaj86/telebit.js) |
|||
to allow you to serve http and https from any computer, anywhere through a secure tunnel. |
|||
|
|||
| Sponsored by [ppl](https://ppl.family) | **Telebit Relay** | [Telebit](https://git.coolaj86.com/coolaj86/tunnel-client.js) | |
|||
| Sponsored by [ppl](https://ppl.family) | **Telebit Relay** | [Telebit Remote](https://git.coolaj86.com/coolaj86/telebit.js) | |
|||
|
|||
CLI |
|||
=== |
|||
Features |
|||
======== |
|||
|
|||
Installs as `stunnel.js` with the alias `jstunnel` |
|||
(for those that regularly use `stunnel` but still like commandline completion). |
|||
* [x] Expose your bits even in the harshest of network environments |
|||
* [x] NAT, Home Routers |
|||
* [x] College Dorms, HOAs |
|||
* [x] Corporate Firewalls, Public libraries, Airports |
|||
* [x] and even Airplanes, yep |
|||
* [x] Automated HTTPS (Free SSL) |
|||
|
|||
### Install |
|||
Install |
|||
======= |
|||
|
|||
Mac & Linux |
|||
----------- |
|||
|
|||
Open Terminal and run this install script: |
|||
|
|||
```bash |
|||
npm install -g stunneld |
|||
curl -fsS https://get.telebit.cloud/ | bash |
|||
``` |
|||
|
|||
Then `dist/etc/systemd/system/stunneld.service` should be copied to `/etc/systemd/system/stunneld.service` and |
|||
the ARGUMENTS, such as SECRET, MUST BE CHANGED. |
|||
This will install Telebit Relay to `/opt/telebitd` and |
|||
put a symlink to `/opt/telebitd/bin/telebitd` in `/usr/local/bin/telebitd` |
|||
for convenience. |
|||
|
|||
*TODO*: make `--config /path/to/config` the only argument (and have the secret auto-generated on first run?) |
|||
You can customize the installation: |
|||
|
|||
## Note: Use node.js v8.x |
|||
```bash |
|||
export NODEJS_VER=v8.11.1 |
|||
export TELEBITD_PATH=/opt/telebitd |
|||
curl -fsS https://get.telebit.cloud/ | bash |
|||
``` |
|||
|
|||
There is a bug in node v9.x that causes stunneld to crash. |
|||
This will change which version of node.js is bundled with Telebit Relay |
|||
and the path to which Telebit Relay installs. |
|||
|
|||
https://github.com/nodejs/node/issues/20241 |
|||
Windows & Node.js |
|||
----------------- |
|||
|
|||
### Advanced Usage |
|||
1. Install [node.js](https://nodejs.org) |
|||
2. Open _Node.js_ |
|||
2. Run the command `npm install -g telebitd` |
|||
|
|||
How to use `stunnel.js` with your own instance of `stunneld.js`: |
|||
**Note**: Use node.js v8.x or v10.x |
|||
|
|||
```bash |
|||
stunneld.js --servernames tunnel.example.com --protocols wss --secret abc123 |
|||
``` |
|||
There is [a bug](https://github.com/nodejs/node/issues/20241) in node v9.x that causes telebitd to crash. |
|||
|
|||
Options |
|||
Service Install |
|||
=== |
|||
|
|||
``` |
|||
--secret the same secret used by stunnel client (used for authentication) |
|||
--serve comma separated list of <proto>:<servername>:<port> to which |
|||
incoming http and https should be forwarded |
|||
``` |
|||
TODO automate this: |
|||
|
|||
`./dist/etc/systemd/system/telebitd.service` should be copied to `/etc/systemd/system/telebitd.service`. |
|||
|
|||
### Privileged Ports without sudo |
|||
The user and group `telebit` should be created. |
|||
|
|||
**Privileged Ports without sudo**: |
|||
|
|||
```bash |
|||
# Linux |
|||
sudo setcap 'cap_net_bind_service=+ep' $(which node) |
|||
``` |
|||
|
|||
### Alterntive Methods |
|||
|
|||
**NOT YET IMPLEMENTED** |
|||
Usage |
|||
==== |
|||
|
|||
We created this for anyone to use on their own server or VPS, |
|||
but those generally cost $5 - $20 / month and so it's probably |
|||
cheaper to purchase data transfer (which we supply, obviously), |
|||
which is only $1/month for most people. |
|||
```bash |
|||
telebitd --config /etc/telebit/telebitd.yml |
|||
``` |
|||
|
|||
Just use the client ([stunnel.js](https://git.coolaj86.com/coolaj86/tunnel-client.js)) |
|||
with this tunneling service (the default) and save yourself the monthly fee |
|||
by only paying for the data you need. |
|||
Options |
|||
|
|||
* Node WS Tunnel (zero setup) |
|||
* Heroku (zero cost) |
|||
* Chunk Host (best deal per TB/month) |
|||
`/etc/telebit/telebitd.yml:` |
|||
``` |
|||
servernames: |
|||
- telebit.example.com |
|||
- telebit.example.net |
|||
email: 'jon@example.com' |
|||
agree_tos: true |
|||
community_member: true |
|||
secret: 'xxxyyyzzzaaabbbccc' |
|||
``` |
|||
|
|||
Security |
|||
======== |
|||
|
|||
The bottom line: As with everything in life, there is no such thing as anonymity |
|||
or absolute security. Only use stunneld services that you trust. :D |
|||
or absolute security. Only use Telebit Relays that you trust or self-host. :D |
|||
|
|||
Even though the traffic is encrypted end-to-end, you can't just trust any stunneld service |
|||
Even though the traffic is encrypted end-to-end, you can't just trust any Telebit Relay |
|||
willy-nilly. |
|||
|
|||
A man-in-the-middle attack is possible using Let's Encrypt since an evil stunneld service |
|||
A man-in-the-middle attack is possible using Let's Encrypt since an evil Telebit Relay |
|||
would be able to complete the http-01 and tls-sni-01 challenges without a problem |
|||
(since that's where your DNS is pointed when you use the service). |
|||
|
|||
Also, the traffic could still be copied and stored for decryption is some era when quantum |
|||
computers exist (probably never). |
|||
|
|||
Why? |
|||
==== |
|||
|
|||
We created this for anyone to use on their own server or VPS, |
|||
but those generally cost $5 - $20 / month and so it's probably |
|||
cheaper to purchase data transfer (which we supply, obviously), |
|||
which is only $1/month for most people. |
|||
|
|||
TODO show how to do on |
|||
|
|||
* Node WS Tunnel (zero setup) |
|||
* Heroku (zero cost) |
|||
* Chunk Host (best deal per TB/month) |
|||
|
|||
|
|||
|
@ -0,0 +1,9 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>Telebit Relay</title> |
|||
</head> |
|||
<body> |
|||
[TODO: Admin Interface] |
|||
</body> |
|||
</html> |
@ -0,0 +1,16 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>Telebit Relay</title> |
|||
</head> |
|||
<body> |
|||
[TODO: Setup Interface] |
|||
<br> |
|||
<ul> |
|||
<li>Admin Server Name</li> |
|||
<li>Administrator Email</li> |
|||
<li>SSL ToS Agree</li> |
|||
<li>Community Member</li> |
|||
</ul> |
|||
</body> |
|||
</html> |
@ -1,13 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
rm -rf ./node-installer.sh |
|||
curl -fsSL bit.ly/node-installer -o ./node-installer.sh |
|||
bash ./node-installer.sh --dev-deps |
|||
|
|||
git clone https://git.coolaj86.com/coolaj86/tunnel-server.js.git |
|||
pushd tunnel-server.js/ |
|||
npm install |
|||
my_secret=$(node bin/generate-secret.js) |
|||
echo "Your secret is:\n\n\t"$my_secret |
|||
echo "node bin/server.js --servernames tunnel.example.com --secret $my_secret" |
|||
popd |
@ -1,194 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
(function () { |
|||
'use strict'; |
|||
|
|||
var pkg = require('../package.json'); |
|||
|
|||
var program = require('commander'); |
|||
var stunneld = require('../wstunneld.js'); |
|||
var greenlock = require('greenlock'); |
|||
|
|||
function collectServernames(val, memo) { |
|||
var lowerCase = val.split(/,/).map(function (servername) { |
|||
return servername.toLowerCase(); |
|||
}); |
|||
|
|||
return memo.concat(lowerCase); |
|||
} |
|||
|
|||
function collectProxies(val, memo) { |
|||
var vals = val.split(/,/g); |
|||
vals.map(function (location) { |
|||
// http:john.example.com:3000
|
|||
// http://john.example.com:3000
|
|||
var parts = location.split(':'); |
|||
if (1 === parts.length) { |
|||
parts[1] = parts[0]; |
|||
parts[0] = 'wss'; |
|||
} |
|||
if (2 === parts.length) { |
|||
if (/\./.test(parts[0])) { |
|||
parts[2] = parts[1]; |
|||
parts[1] = parts[0]; |
|||
parts[0] = 'wss'; |
|||
} |
|||
if (!/\./.test(parts[1])) { |
|||
throw new Error("bad --serve option Example: wss://tunnel.example.com:1337"); |
|||
} |
|||
} |
|||
parts[0] = parts[0].toLowerCase(); |
|||
parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*'; |
|||
parts[2] = parseInt(parts[2], 10) || 0; |
|||
if (!parts[2]) { |
|||
// TODO grab OS list of standard ports?
|
|||
if (-1 !== [ 'ws', 'http' ].indexOf(parts[0])) { |
|||
//parts[2] = 80;
|
|||
} |
|||
else if (-1 !== [ 'wss', 'https' ].indexOf(parts[0])) { |
|||
//parts[2] = 443;
|
|||
} |
|||
else { |
|||
throw new Error("port must be specified - ex: tls:*:1337"); |
|||
} |
|||
} |
|||
|
|||
return { |
|||
protocol: parts[0] |
|||
, hostname: parts[1] |
|||
, port: parts[2] |
|||
}; |
|||
}).forEach(function (val) { |
|||
memo.push(val); |
|||
}); |
|||
|
|||
return memo; |
|||
} |
|||
|
|||
function collectPorts(val, memo) { |
|||
return memo.concat(val.split(/,/g).map(Number).filter(Boolean)); |
|||
} |
|||
|
|||
program |
|||
.version(pkg.version) |
|||
.option('--agree-tos', "Accept the Daplie and Let's Encrypt Terms of Service") |
|||
.option('--email <EMAIL>', "Email to use for Daplie and Let's Encrypt accounts") |
|||
.option('--serve <URL>', 'comma separated list of <proto>:<//><servername>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https://john.example.com,tls:*:1337', collectProxies, [ ]) |
|||
.option('--ports <PORT>', 'comma separated list of ports on which to listen. Ex: 80,443,1337', collectPorts, [ ]) |
|||
.option('--servernames <STRING>', 'comma separated list of servernames to use for the admin interface. Ex: tunnel.example.com,tunnel.example.net', collectServernames, [ ]) |
|||
.option('--secret <STRING>', 'the same secret used by stunneld (used for JWT authentication)') |
|||
.parse(process.argv) |
|||
; |
|||
|
|||
var portsMap = {}; |
|||
var servernamesMap = {}; |
|||
program.serve.forEach(function (proxy) { |
|||
servernamesMap[proxy.hostname] = true; |
|||
if (proxy.port) { |
|||
portsMap[proxy.port] = true; |
|||
} |
|||
}); |
|||
program.servernames.forEach(function (name) { |
|||
servernamesMap[name] = true; |
|||
}); |
|||
program.ports.forEach(function (port) { |
|||
portsMap[port] = true; |
|||
}); |
|||
|
|||
program.servernames = Object.keys(servernamesMap); |
|||
if (!program.servernames.length) { |
|||
throw new Error('You must give this server at least one servername for its admin interface. Example:\n\n\t--servernames tunnel.example.com,tunnel.example.net'); |
|||
} |
|||
|
|||
program.ports = Object.keys(portsMap); |
|||
if (!program.ports.length) { |
|||
program.ports = [ 80, 443 ]; |
|||
} |
|||
|
|||
if (!program.secret) { |
|||
// TODO randomly generate and store in file?
|
|||
console.warn("[SECURITY] you must provide --secret '" + require('crypto').randomBytes(16).toString('hex') + "'"); |
|||
process.exit(1); |
|||
return; |
|||
} |
|||
|
|||
// TODO letsencrypt
|
|||
program.tlsOptions = require('localhost.daplie.me-certificates').merge({}); |
|||
|
|||
function approveDomains(opts, certs, cb) { |
|||
// 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 !== program.servernames.indexOf(opts.domain)) { |
|||
opts.email = program.email; |
|||
opts.agreeTos = program.agreeTos; |
|||
} |
|||
} |
|||
|
|||
// NOTE: you can also change other options such as `challengeType` and `challenge`
|
|||
// opts.challengeType = 'http-01';
|
|||
// opts.challenge = require('le-challenge-fs').create({});
|
|||
|
|||
cb(null, { options: opts, certs: certs }); |
|||
} |
|||
|
|||
if (!program.email || !program.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(""); |
|||
} |
|||
else { |
|||
program.greenlock = greenlock.create({ |
|||
|
|||
version: 'draft-11' |
|||
, server: 'https://acme-v02.api.letsencrypt.org/directory' |
|||
|
|||
, challenges: { |
|||
// TODO dns-01
|
|||
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }) |
|||
} |
|||
|
|||
, store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' }) |
|||
|
|||
, email: program.email |
|||
|
|||
, agreeTos: program.agreeTos |
|||
|
|||
, approveDomains: approveDomains |
|||
|
|||
//, approvedDomains: program.servernames
|
|||
|
|||
}); |
|||
} |
|||
//program.tlsOptions.SNICallback = program.greenlock.httpsOptions.SNICallback;
|
|||
/* |
|||
program.middleware = program.greenlock.middleware(function (req, res) { |
|||
res.end('Hello, World!'); |
|||
}); |
|||
*/ |
|||
|
|||
require('../handlers').create(program); // adds directly to program for now...
|
|||
|
|||
//require('cluster-store').create().then(function (store) {
|
|||
//program.store = store;
|
|||
|
|||
var net = require('net'); |
|||
var netConnHandlers = stunneld.create(program); // { tcp, ws }
|
|||
var WebSocketServer = require('ws').Server; |
|||
var wss = new WebSocketServer({ server: (program.httpTunnelServer || program.httpServer) }); |
|||
wss.on('connection', netConnHandlers.ws); |
|||
program.ports.forEach(function (port) { |
|||
var tcp3000 = net.createServer(); |
|||
tcp3000.listen(port, function () { |
|||
console.log('listening on ' + port); |
|||
}); |
|||
tcp3000.on('connection', netConnHandlers.tcp); |
|||
}); |
|||
//});
|
|||
|
|||
}()); |
@ -0,0 +1,280 @@ |
|||
#!/usr/bin/env node
|
|||
(function () { |
|||
'use strict'; |
|||
|
|||
var pkg = require('../package.json'); |
|||
|
|||
var argv = process.argv.slice(2); |
|||
var telebitd = require('../telebitd.js'); |
|||
var greenlock = require('greenlock'); |
|||
|
|||
var confIndex = argv.indexOf('--config'); |
|||
var confpath; |
|||
if (-1 === confIndex) { |
|||
confIndex = argv.indexOf('-c'); |
|||
} |
|||
confpath = argv[confIndex + 1]; |
|||
|
|||
function help() { |
|||
console.info(''); |
|||
console.info('Usage:'); |
|||
console.info(''); |
|||
console.info('\ttelebitd --config <path>'); |
|||
console.info(''); |
|||
console.info('Example:'); |
|||
console.info(''); |
|||
console.info('\ttelebitd --config /etc/telebit/telebitd.yml'); |
|||
console.info(''); |
|||
console.info('Config:'); |
|||
console.info(''); |
|||
console.info('\tSee https://git.coolaj86.com/coolaj86/telebitd.js'); |
|||
console.info(''); |
|||
console.info(''); |
|||
process.exit(0); |
|||
} |
|||
|
|||
if (-1 === confIndex || -1 !== argv.indexOf('-h') || -1 !== argv.indexOf('--help')) { |
|||
help(); |
|||
} |
|||
if (!confpath || /^--/.test(confpath)) { |
|||
help(); |
|||
} |
|||
|
|||
function applyConfig(config) { |
|||
var state = { ports: [ 80, 443 ], tcp: {} }; |
|||
state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
|
|||
state.config = config; |
|||
state.servernames = config.servernames || []; |
|||
state.secret = state.config.secret; |
|||
if (state.secret) { |
|||
state.secret = require('crypto').randomBytes(16).toString('hex'); |
|||
console.info(""); |
|||
console.info("Secret for this session:"); |
|||
console.info(""); |
|||
console.info("\t" + state.secret); |
|||
console.info(""); |
|||
console.info(""); |
|||
} |
|||
require('../handlers').create(state); // adds directly to config for now...
|
|||
|
|||
//require('cluster-store').create().then(function (store) {
|
|||
//program.store = store;
|
|||
|
|||
var net = require('net'); |
|||
var netConnHandlers = telebitd.create(state); // { tcp, ws }
|
|||
var WebSocketServer = require('ws').Server; |
|||
var wss = new WebSocketServer({ server: (state.httpTunnelServer || state.httpServer) }); |
|||
wss.on('connection', netConnHandlers.ws); |
|||
state.ports.forEach(function (port) { |
|||
if (state.tcp[port]) { |
|||
console.error("skipping previously added port " + port); |
|||
return; |
|||
} |
|||
state.tcp[port] = net.createServer(); |
|||
state.tcp[port].listen(port, function () { |
|||
console.log('listening plain TCP on ' + port); |
|||
}); |
|||
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) { |
|||
var config; |
|||
|
|||
var recase = require('recase').create({}); |
|||
var camelCopy = recase.camelCopy.bind(recase); |
|||
|
|||
if (err) { |
|||
console.error("\nCouldn't load config:\n\n\t" + err.message + "\n"); |
|||
process.exit(1); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
config = JSON.parse(text); |
|||
} catch(e1) { |
|||
try { |
|||
config = require('js-yaml').safeLoad(text); |
|||
} catch(e2) { |
|||
console.error(e1.message); |
|||
console.error(e2.message); |
|||
process.exit(1); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
applyConfig(camelCopy(config)); |
|||
}); |
|||
|
|||
|
|||
|
|||
function adjustArgs() { |
|||
function collectServernames(val, memo) { |
|||
var lowerCase = val.split(/,/).map(function (servername) { |
|||
return servername.toLowerCase(); |
|||
}); |
|||
|
|||
return memo.concat(lowerCase); |
|||
} |
|||
|
|||
function collectProxies(val, memo) { |
|||
var vals = val.split(/,/g); |
|||
vals.map(function (location) { |
|||
// http:john.example.com:3000
|
|||
// http://john.example.com:3000
|
|||
var parts = location.split(':'); |
|||
if (1 === parts.length) { |
|||
parts[1] = parts[0]; |
|||
parts[0] = 'wss'; |
|||
} |
|||
if (2 === parts.length) { |
|||
if (/\./.test(parts[0])) { |
|||
parts[2] = parts[1]; |
|||
parts[1] = parts[0]; |
|||
parts[0] = 'wss'; |
|||
} |
|||
if (!/\./.test(parts[1])) { |
|||
throw new Error("bad --serve option Example: wss://tunnel.example.com:1337"); |
|||
} |
|||
} |
|||
parts[0] = parts[0].toLowerCase(); |
|||
parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*'; |
|||
parts[2] = parseInt(parts[2], 10) || 0; |
|||
if (!parts[2]) { |
|||
// TODO grab OS list of standard ports?
|
|||
if (-1 !== [ 'ws', 'http' ].indexOf(parts[0])) { |
|||
//parts[2] = 80;
|
|||
} |
|||
else if (-1 !== [ 'wss', 'https' ].indexOf(parts[0])) { |
|||
//parts[2] = 443;
|
|||
} |
|||
else { |
|||
throw new Error("port must be specified - ex: tls:*:1337"); |
|||
} |
|||
} |
|||
|
|||
return { |
|||
protocol: parts[0] |
|||
, hostname: parts[1] |
|||
, port: parts[2] |
|||
}; |
|||
}).forEach(function (val) { |
|||
memo.push(val); |
|||
}); |
|||
|
|||
return memo; |
|||
} |
|||
|
|||
function collectPorts(val, memo) { |
|||
return memo.concat(val.split(/,/g).map(Number).filter(Boolean)); |
|||
} |
|||
|
|||
program |
|||
.version(pkg.version) |
|||
.option('--agree-tos', "Accept the Daplie and Let's Encrypt Terms of Service") |
|||
.option('--email <EMAIL>', "Email to use for Daplie and Let's Encrypt accounts") |
|||
.option('--serve <URL>', 'comma separated list of <proto>:<//><servername>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https://john.example.com,tls:*:1337', collectProxies, [ ]) |
|||
.option('--ports <PORT>', 'comma separated list of ports on which to listen. Ex: 80,443,1337', collectPorts, [ ]) |
|||
.option('--servernames <STRING>', 'comma separated list of servernames to use for the admin interface. Ex: tunnel.example.com,tunnel.example.net', collectServernames, [ ]) |
|||
.option('--secret <STRING>', 'the same secret used by telebitd (used for JWT authentication)') |
|||
.parse(process.argv) |
|||
; |
|||
|
|||
var portsMap = {}; |
|||
var servernamesMap = {}; |
|||
program.serve.forEach(function (proxy) { |
|||
servernamesMap[proxy.hostname] = true; |
|||
if (proxy.port) { |
|||
portsMap[proxy.port] = true; |
|||
} |
|||
}); |
|||
program.servernames.forEach(function (name) { |
|||
servernamesMap[name] = true; |
|||
}); |
|||
program.ports.forEach(function (port) { |
|||
portsMap[port] = true; |
|||
}); |
|||
|
|||
program.servernames = Object.keys(servernamesMap); |
|||
if (!program.servernames.length) { |
|||
throw new Error('You must give this server at least one servername for its admin interface. Example:\n\n\t--servernames tunnel.example.com,tunnel.example.net'); |
|||
} |
|||
|
|||
program.ports = Object.keys(portsMap); |
|||
if (!program.ports.length) { |
|||
program.ports = [ 80, 443 ]; |
|||
} |
|||
|
|||
if (!program.secret) { |
|||
// TODO randomly generate and store in file?
|
|||
console.warn("[SECURITY] you must provide --secret '" + require('crypto').randomBytes(16).toString('hex') + "'"); |
|||
process.exit(1); |
|||
return; |
|||
} |
|||
|
|||
//program.tlsOptions.SNICallback = program.greenlock.httpsOptions.SNICallback;
|
|||
/* |
|||
program.middleware = program.greenlock.middleware(function (req, res) { |
|||
res.end('Hello, World!'); |
|||
}); |
|||
*/ |
|||
} |
|||
|
|||
//adjustArgs();
|
|||
|
|||
}()); |
@ -1,23 +0,0 @@ |
|||
[Unit] |
|||
Description=Daplie Tunnel Server |
|||
After=network-online.target |
|||
Wants=network-online.target systemd-networkd-wait-online.service |
|||
|
|||
[Service] |
|||
# Always restart, unless it's restarting fast enough for us to believe it's completely broken |
|||
Restart=always |
|||
StartLimitInterval=10 |
|||
StartLimitBurst=3 |
|||
|
|||
User=www-data |
|||
Group=www-data |
|||
WorkingDirectory=/srv/stunneld |
|||
# TODO needs --config option and these options should go in a config file |
|||
ExecStart=/srv/stunneld/bin/stunneld.js --servernames tunnel.example.com --secret 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' --email tunnel@example.com --agree-tos |
|||
|
|||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE |
|||
AmbientCapabilities=CAP_NET_BIND_SERVICE |
|||
NoNewPrivileges=true |
|||
|
|||
[Install] |
|||
WantedBy=multi-user.target |
@ -0,0 +1,66 @@ |
|||
# Pre-req |
|||
# sudo adduser telebit --home /opt/telebitd |
|||
# sudo mkdir -p /opt/telebitd/ |
|||
# sudo chown -R telebit:telebit /opt/telebitd/ |
|||
|
|||
[Unit] |
|||
Description=Telebit Relay |
|||
Documentation=https://git.coolaj86.com/coolaj86/telebitd.js/ |
|||
After=network-online.target |
|||
Wants=network-online.target systemd-networkd-wait-online.service |
|||
|
|||
[Service] |
|||
# Restart on crash (bad signal), but not on 'clean' failure (error exit code) |
|||
# Allow up to 3 restarts within 10 seconds |
|||
# (it's unlikely that a user or properly-running script will do this) |
|||
Restart=on-abnormal |
|||
StartLimitInterval=10 |
|||
StartLimitBurst=3 |
|||
|
|||
# User and group the process will run as |
|||
# (git is the de facto standard on most systems) |
|||
User=telebit |
|||
Group=telebit |
|||
|
|||
WorkingDirectory=/opt/telebitd |
|||
# custom directory cannot be set and will be the place where gitea exists, not the working directory |
|||
ExecStart=/opt/telebitd/bin/node /opt/telebitd/bin/telebitd.js --config /etc/telebit/telebitd.yml |
|||
ExecReload=/bin/kill -USR1 $MAINPID |
|||
|
|||
# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. |
|||
# Unmodified gitea is not expected to use more than this. |
|||
LimitNOFILE=1048576 |
|||
LimitNPROC=64 |
|||
|
|||
# Use private /tmp and /var/tmp, which are discarded after gitea stops. |
|||
PrivateTmp=true |
|||
# Use a minimal /dev |
|||
PrivateDevices=true |
|||
# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. |
|||
ProtectHome=true |
|||
# Make /usr, /boot, /etc and possibly some more folders read-only. |
|||
ProtectSystem=full |
|||
# ... except /opt/gitea because we want a place for the database |
|||
# and /var/log/gitea because we want a place where logs can go. |
|||
# This merely retains r/w access rights, it does not add any new. |
|||
# Must still be writable on the host! |
|||
ReadWriteDirectories=/opt/telebitd |
|||
|
|||
# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories |
|||
; ReadWritePaths=/opt/telebitd |
|||
|
|||
# The following additional security directives only work with systemd v229 or later. |
|||
# They further retrict privileges that can be gained by gitea. |
|||
# Note that you may have to add capabilities required by any plugins in use. |
|||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE |
|||
AmbientCapabilities=CAP_NET_BIND_SERVICE |
|||
NoNewPrivileges=true |
|||
|
|||
# Caveat: Some features may need additional capabilities. |
|||
# For example an "upload" may need CAP_LEASE |
|||
; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE |
|||
; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE |
|||
; NoNewPrivileges=true |
|||
|
|||
[Install] |
|||
WantedBy=multi-user.target |
@ -0,0 +1,3 @@ |
|||
servernames: [] |
|||
email: 'jon@example.com' |
|||
agree_tos: true |
@ -0,0 +1,145 @@ |
|||
#!/bin/bash |
|||
|
|||
# This is a 3 step process |
|||
# 1. First we need to figure out whether to use wget or curl for fetching remote files |
|||
# 2. Next we need to figure out whether to use unzip or tar for downloading releases |
|||
# 3. We need to actually install the stuff |
|||
|
|||
set -e |
|||
set -u |
|||
|
|||
############################### |
|||
# # |
|||
# http_get # |
|||
# boilerplate for curl / wget # |
|||
# # |
|||
############################### |
|||
|
|||
# See https://git.coolaj86.com/coolaj86/snippets/blob/master/bash/http-get.sh |
|||
|
|||
_my_http_get="" |
|||
_my_http_opts="" |
|||
_my_http_out="" |
|||
|
|||
detect_http_get() |
|||
{ |
|||
set +e |
|||
if type -p curl >/dev/null 2>&1; then |
|||
_my_http_get="curl" |
|||
_my_http_opts="-fsSL" |
|||
_my_http_out="-o" |
|||
elif type -p wget >/dev/null 2>&1; then |
|||
_my_http_get="wget" |
|||
_my_http_opts="--quiet" |
|||
_my_http_out="-O" |
|||
else |
|||
echo "Aborted, could not find curl or wget" |
|||
return 7 |
|||
fi |
|||
set -e |
|||
} |
|||
|
|||
http_get() |
|||
{ |
|||
$_my_http_get $_my_http_opts $_my_http_out "$2" "$1" |
|||
touch "$2" |
|||
} |
|||
|
|||
http_bash() |
|||
{ |
|||
_http_url=$1 |
|||
my_args=${2:-} |
|||
rm -rf my-tmp-runner.sh |
|||
$_my_http_get $_my_http_opts $_my_http_out my-tmp-runner.sh "$_http_url"; bash my-tmp-runner.sh $my_args; rm my-tmp-runner.sh |
|||
} |
|||
|
|||
detect_http_get |
|||
|
|||
############################### |
|||
## END HTTP_GET ## |
|||
############################### |
|||
|
|||
echo "" |
|||
echo "" |
|||
echo "" |
|||
|
|||
my_app="telebitd" |
|||
my_bin="telebitd.js" |
|||
my_name="Telebit Relay" |
|||
my_repo="telebitd.js" |
|||
|
|||
if [ -z "${TELEBITD_PATH:-}" ]; then |
|||
echo 'TELEBITD_PATH="'${TELEBITD_PATH:-}'"' |
|||
TELEBITD_PATH=/opt/$my_app |
|||
fi |
|||
|
|||
echo "Installing $my_name to '$TELEBITD_PATH'" |
|||
echo "" |
|||
|
|||
echo "sudo mkdir -p '$TELEBITD_PATH'" |
|||
sudo mkdir -p "$TELEBITD_PATH" |
|||
echo "sudo chown -R $(whoami) '$TELEBITD_PATH'" |
|||
sudo chown -R $(whoami) "$TELEBITD_PATH" |
|||
|
|||
echo "Installing node.js dependencies into $TELEBITD_PATH" |
|||
# until node v10.x gets fix for ursa we have no advantage to switching from 8.x |
|||
export NODEJS_VER=v8.11.1 |
|||
export NODE_PATH="$TELEBITD_PATH/lib/node_modules" |
|||
export NPM_CONFIG_PREFIX="$TELEBITD_PATH" |
|||
export PATH="$TELEBITD_PATH/bin:$PATH" |
|||
sleep 1 |
|||
http_bash https://git.coolaj86.com/coolaj86/node-installer.sh/raw/branch/master/install.sh --no-dev-deps >/dev/null 2>/dev/null |
|||
|
|||
my_tree="master" |
|||
my_node="$TELEBITD_PATH/bin/node" |
|||
my_npm="$my_node $TELEBITD_PATH/bin/npm" |
|||
my_tmp="$TELEBITD_PATH/tmp" |
|||
mkdir -p $my_tmp |
|||
|
|||
echo "Installing $my_name into $TELEBITD_PATH" |
|||
set +e |
|||
my_unzip=$(type -p unzip) |
|||
my_tar=$(type -p tar) |
|||
if [ -n "$my_unzip" ]; then |
|||
rm -f $my_tmp/$my_app-$my_tree.zip |
|||
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$my_tree.zip $my_tmp/$my_app-$my_tree.zip |
|||
# -o means overwrite, and there is no option to strip |
|||
$my_unzip -o $my_tmp/$my_app-$my_tree.zip -d $TELEBITD_PATH/ > /dev/null |
|||
cp -ar $TELEBITD_PATH/$my_repo/* $TELEBITD_PATH/ > /dev/null |
|||
rm -rf $TELEBITD_PATH/$my_bin |
|||
elif [ -n "$my_tar" ]; then |
|||
rm -f $my_tmp/$my_app-$my_tree.tar.gz |
|||
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$my_tree.tar.gz $my_tmp/$my_app-$my_tree.tar.gz |
|||
ls -lah $my_tmp/$my_app-$my_tree.tar.gz |
|||
$my_tar -xzf $my_tmp/$my_app-$my_tree.tar.gz --strip 1 -C $TELEBITD_PATH/ |
|||
else |
|||
echo "Neither tar nor unzip found. Abort." |
|||
exit 13 |
|||
fi |
|||
set -e |
|||
|
|||
pushd $TELEBITD_PATH >/dev/null |
|||
$my_npm install >/dev/null 2>/dev/null |
|||
popd >/dev/null |
|||
|
|||
cat << EOF > $TELEBITD_PATH/bin/$my_app |
|||
#!/bin/bash |
|||
$my_node $TELEBITD_PATH/bin/$my_bin |
|||
EOF |
|||
chmod a+x $TELEBITD_PATH/bin/$my_app |
|||
echo "Creating link to '$my_app' in /usr/local/bin" |
|||
ln -sf $TELEBITD_PATH/bin/$my_app /usr/local/bin/$my_app |
|||
|
|||
echo "" |
|||
echo "" |
|||
echo "Installed successfully. Try it out:" |
|||
echo "" |
|||
echo " $my_app --help" |
|||
echo "" |
|||
echo "" |
|||
|
|||
#sudo setcap cap_net_bind_service=+ep $TELEBITD_PATH/bin/node |
|||
|
|||
#https://git.coolaj86.com/coolaj86/telebitd.js.git |
|||
#https://git.coolaj86.com/coolaj86/telebitd.js/archive/:tree:.tar.gz |
|||
#https://git.coolaj86.com/coolaj86/telebitd.js/archive/:tree:.zip |
Loading…
Reference in new issue