initial commit
This commit is contained in:
commit
56b5337d8f
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var express = require('express')
|
||||||
|
;
|
||||||
|
|
||||||
|
module.exports.create = function (server, host, port, publicDir) {
|
||||||
|
var app = express()
|
||||||
|
;
|
||||||
|
|
||||||
|
app.use(express.static(publicDir));
|
||||||
|
|
||||||
|
return app;
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('../walnut.js');
|
|
@ -0,0 +1 @@
|
||||||
|
walnut
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo rsync -v walnut /etc/init.d/
|
||||||
|
sudo chmod 755 /etc/init.d/walnut
|
||||||
|
sudo update-rc.d walnut defaults
|
|
@ -0,0 +1,39 @@
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: walnut
|
||||||
|
# Required-Start: $all
|
||||||
|
# Required-Stop:
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: WALNUT Home Cloud
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
export PATH=$PATH:/bin:/usr/bin:/usr/local/bin
|
||||||
|
|
||||||
|
PIDFILE=/var/run/walnut.pid
|
||||||
|
DATE=`date '+%F_%H-%M-%S'`
|
||||||
|
|
||||||
|
cd /srv/walnut
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
mkdir -p /srv/walnut/logs
|
||||||
|
mkdir -p /srv/walnut/.forever
|
||||||
|
exec forever -p /srv/walnut/.forever --minUptime=20000 --spinSleepTime=100 --workingDir=/srv/walnut/ -l "/srv/walnut/logs/access.${DATE}.log" -e "/srv/walnut/logs/error.${DATE}.log" --pidFile=$PIDFILE start /srv/walnut/bin/walnut.js
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
exec forever stopall
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
mkdir -p /srv/walnut/logs
|
||||||
|
mkdir -p /srv/walnut/.forever
|
||||||
|
exec forever stopall
|
||||||
|
exec forever -p /srv/walnut/.forever --minUptime=20000 --spinSleepTime=100 --workingDir=/srv/walnut/ -l "/srv/walnut/logs/access.${DATE}.log" -e "/srv/walnut/logs/error.${DATE}.log" --pidFile=$PIDFILE start /srv/walnut/bin/walnut.js
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
|
||||||
|
echo "Usage: /etc/init.d/walnut {start|stop}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,21 @@
|
||||||
|
description "WALNUT, by Daplie"
|
||||||
|
version "0.1"
|
||||||
|
author "Daplie Inc"
|
||||||
|
|
||||||
|
# Upstart has nothing in $PATH by default
|
||||||
|
env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
# Keep the server running on crash or machine reboot
|
||||||
|
respawn
|
||||||
|
respawn limit 10 120
|
||||||
|
start on runlevel [2345]
|
||||||
|
|
||||||
|
# Start the server using spark and redirect output to log files
|
||||||
|
script
|
||||||
|
DATE=`date '+%F_%H-%M-%S'`
|
||||||
|
cd /srv/walnut
|
||||||
|
mkdir -p logs
|
||||||
|
exec node bin/walnut \
|
||||||
|
> "./logs/access.${DATE}.log" \
|
||||||
|
2> "./logs/error.${DATE}.log"
|
||||||
|
end script
|
|
@ -0,0 +1,84 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise
|
||||||
|
, updateIp = require('./helpers/update-ip.js').update
|
||||||
|
, request = PromiseA.promisifyAll(require('request'))
|
||||||
|
, requestAsync = PromiseA.promisify(require('request'))
|
||||||
|
, upnpForward = require('./helpers/upnp-forward').upnpForward
|
||||||
|
, pmpForward = require('./helpers/pmp-forward').pmpForward
|
||||||
|
, loopbackHttps = require('./loopback-https')
|
||||||
|
//, checkip = require('check-ip-address')
|
||||||
|
;
|
||||||
|
|
||||||
|
function openPort(ip, port) {
|
||||||
|
if (!/tcp|https|http/.test(port.protocol || 'tcp')) {
|
||||||
|
throw new Error('not yet supported \'' + port.protocol + '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === port.testable) {
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loopbackHttps.create(ip, port.private, port.public).then(function () {
|
||||||
|
console.log('success');
|
||||||
|
}).catch(function (err) {
|
||||||
|
// TODO test err
|
||||||
|
return upnpForward(port).catch(function (err) {
|
||||||
|
console.error('[ERROR] UPnP Port Forward');
|
||||||
|
console.error(err);
|
||||||
|
// TODO test err
|
||||||
|
return pmpForward(port);
|
||||||
|
}).then(function () {
|
||||||
|
return loopbackHttps.create(ip, port.private, port.public);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. update dyndns
|
||||||
|
// 1.5. check ip every 5 min
|
||||||
|
// 2. loopback test on ip for http / https / ssh
|
||||||
|
// 3. if needed: discover gateway, map ports
|
||||||
|
function beacon(hostnames, ports) {
|
||||||
|
// test with
|
||||||
|
// dig -p 53 @redirect-www.org pi.nadal.daplie.com A
|
||||||
|
return updateIp({
|
||||||
|
updater: 'redirect-www.org'
|
||||||
|
, port: 65443
|
||||||
|
, ddns: hostnames.map(function (hostname) {
|
||||||
|
return { "name": hostname /*, "value": ipaddress, "type": "A"*/ };
|
||||||
|
})
|
||||||
|
}).then(function (data) {
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
console.log("Updated DynDNS");
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
ports.forEach(function (port) {
|
||||||
|
promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port));
|
||||||
|
});
|
||||||
|
|
||||||
|
return PromiseA.all(promises);
|
||||||
|
}).then(function () {
|
||||||
|
console.log('opened ports');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
request.getAsync('http://checkip.hellabit.com').spread(function (resp, data) {
|
||||||
|
console.log("External IP is", data);
|
||||||
|
}).then(function () {
|
||||||
|
return upnpForward().catch(function (err) {
|
||||||
|
console.error('ERROR: UPnP failure:');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
return pmpForward().catch(function (err) {
|
||||||
|
console.error('TODO: Notify user that their router is not compatible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO test roundtrip
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//setInterval(beacon, 5 * 60 * 1000);
|
||||||
|
exports.run = beacon;
|
|
@ -0,0 +1,57 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise
|
||||||
|
, natpmp = require('nat-pmp')
|
||||||
|
, exec = require('child_process').exec
|
||||||
|
;
|
||||||
|
|
||||||
|
exports.pmpForward = function (port) {
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
exec('ip route show default', function (err, stdout, stderr) {
|
||||||
|
var gw
|
||||||
|
;
|
||||||
|
|
||||||
|
if (err || stderr) { reject(err || stderr); return; }
|
||||||
|
|
||||||
|
// default via 192.168.1.1 dev eth0
|
||||||
|
gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1');
|
||||||
|
console.log('Possible PMP gateway is', gw);
|
||||||
|
|
||||||
|
// create a "client" instance connecting to your local gateway
|
||||||
|
var client = natpmp.connect(gw);
|
||||||
|
|
||||||
|
function setPortForward() {
|
||||||
|
// setup a new port mapping
|
||||||
|
client.portMapping({
|
||||||
|
private: port.private || port.public
|
||||||
|
, public: port.public || port.private
|
||||||
|
, ttl: port.ttl || 0 // 600
|
||||||
|
}, function (err, info) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(info);
|
||||||
|
// {
|
||||||
|
// type: 'tcp',
|
||||||
|
// epoch: 8922109,
|
||||||
|
// private: 22,
|
||||||
|
// public: 2222,
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicitly ask for the current external IP address
|
||||||
|
setTimeout(function () {
|
||||||
|
client.externalIp(function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log('Current external IP address: %s', info.ip.join('.'));
|
||||||
|
setPortForward();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise
|
||||||
|
, https = require('https')
|
||||||
|
, fs = require('fs')
|
||||||
|
, path = require('path')
|
||||||
|
;
|
||||||
|
|
||||||
|
module.exports.update = function (opts) {
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
var options
|
||||||
|
, hostname = opts.updater || 'redirect-www.org'
|
||||||
|
, port = opts.port || 65443
|
||||||
|
;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
host: hostname
|
||||||
|
, port: port
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
, path: '/api/ddns'
|
||||||
|
, auth: opts.auth || 'admin:secret'
|
||||||
|
, ca: [ fs.readFileSync(path.join(__dirname, '..', 'certs', 'ca', 'my-root-ca.crt.pem')) ]
|
||||||
|
};
|
||||||
|
options.agent = new https.Agent(options);
|
||||||
|
|
||||||
|
https.request(options, function(res) {
|
||||||
|
var textData = '';
|
||||||
|
|
||||||
|
res.on('error', function (err) {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
res.on('data', function (chunk) {
|
||||||
|
textData += chunk.toString();
|
||||||
|
// console.log(chunk.toString());
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
resolve(textData);
|
||||||
|
});
|
||||||
|
}).end(JSON.stringify(opts.ddns, null, ' '));
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var natUpnp = require('nat-upnp');
|
||||||
|
|
||||||
|
exports.upnpForward = function (port) {
|
||||||
|
return natUpnp.createClient({ timeout: 1800 }).then(function (client) {
|
||||||
|
return client.portMapping({
|
||||||
|
public: port.public,
|
||||||
|
private: port.private || port.public,
|
||||||
|
ttl: port.ttl || 0
|
||||||
|
})/*.then(function () {
|
||||||
|
var promitter = client.getMappings();
|
||||||
|
|
||||||
|
promitter.on('entry', function (entry, i) {
|
||||||
|
console.log('entry', i);
|
||||||
|
console.log(entry);
|
||||||
|
}).then(function (mappings) {
|
||||||
|
console.log('mappings');
|
||||||
|
console.log(mappings);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promitter;
|
||||||
|
})*/;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
client.portUnmapping({
|
||||||
|
public: 80
|
||||||
|
});
|
||||||
|
|
||||||
|
.findGateway().then(function (stuff) {
|
||||||
|
console.log('[a] gateway');
|
||||||
|
console.log(stuff.gateway);
|
||||||
|
console.log('[a] address');
|
||||||
|
console.log(stuff.address);
|
||||||
|
}).then(function () {
|
||||||
|
return client
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
client.getMappings({ local: true }, function(err, results) {
|
||||||
|
console.log('local mappings', results);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.externalIp(function(err, ip) {
|
||||||
|
console.log('ext-ip', ip);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
exports.upnpForward({ public: 65080, private: 65080, ttl: 0 }).then(function () {
|
||||||
|
console.log('done');
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('ERROR');
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var https = require('https');
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
var PromiseA = global.Promise || require('bluebird').Promise;
|
||||||
|
|
||||||
|
exports.create = function (ip, localPort, externalPort) {
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
var token = Math.random().toString(16).split('.')[1];
|
||||||
|
var tokenPath = Math.random().toString(16).split('.')[1];
|
||||||
|
var options;
|
||||||
|
var server;
|
||||||
|
var options;
|
||||||
|
var certsPath = path.join(__dirname, 'certs', 'server');
|
||||||
|
var caCertsPath = path.join(__dirname, 'certs', 'ca');
|
||||||
|
|
||||||
|
|
||||||
|
function testConnection() {
|
||||||
|
var awesome = false;
|
||||||
|
var timetok;
|
||||||
|
var webreq;
|
||||||
|
var options = {
|
||||||
|
// not hostname because we set headers.host on our own
|
||||||
|
host: ip
|
||||||
|
, headers: {
|
||||||
|
// whatever's on the fake cert
|
||||||
|
'Host': 'redirect-www.org'
|
||||||
|
}
|
||||||
|
, port: externalPort
|
||||||
|
, path: '/' + tokenPath
|
||||||
|
, ca: fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem'))
|
||||||
|
};
|
||||||
|
options.agent = new https.Agent(options);
|
||||||
|
|
||||||
|
timetok = setTimeout(function () {
|
||||||
|
reject(new Error("timed out while testing NAT loopback for port " + externalPort));
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function finishHim(err) {
|
||||||
|
clearTimeout(timetok);
|
||||||
|
server.close(function () {
|
||||||
|
if (!err && awesome) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (err || !awesome) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else if (!awesome) {
|
||||||
|
reject(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the ssl cert matched, so you've probably got two boxes and this isn't the right one"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webreq = https.request(options, function(res) {
|
||||||
|
res.on('data', function (resToken) {
|
||||||
|
if (resToken.toString() === token) {
|
||||||
|
awesome = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.on('error', function (err) {
|
||||||
|
console.error('[ERROR] https.request.response');
|
||||||
|
console.error(err);
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the connection was interrupted"));
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
finishHim();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
webreq.on('error', function (err) {
|
||||||
|
console.error('[ERROR] https.request');
|
||||||
|
console.error(err);
|
||||||
|
if (/ssl|cert|chain/i.test(err.message || err.toString())) {
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the ssl cert validation may have failed (might port-forward to the wrong box)"));
|
||||||
|
} else {
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "port forwarding isn't configured for " + ip + ":" + externalPort + " to " + localPort));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
webreq.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// SSL Certificates
|
||||||
|
//
|
||||||
|
options = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'my-server.key.pem'))
|
||||||
|
, ca: [ fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem')) ]
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'my-server.crt.pem'))
|
||||||
|
, requestCert: false
|
||||||
|
, rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Serve an Express App securely with HTTPS
|
||||||
|
//
|
||||||
|
server = https.createServer(options);
|
||||||
|
function listen(app) {
|
||||||
|
server.on('request', app);
|
||||||
|
server.listen(localPort, function () {
|
||||||
|
localPort = server.address().port;
|
||||||
|
setTimeout(testConnection, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(function (req, res) {
|
||||||
|
if (('/' + tokenPath) === req.url) {
|
||||||
|
res.end(token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end('loopback failure');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"name": "walnut",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "zero-config home cloud server",
|
||||||
|
"main": "walnut.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Daplie/walnut.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ssl",
|
||||||
|
"tls",
|
||||||
|
"https",
|
||||||
|
"rsa",
|
||||||
|
"pem",
|
||||||
|
"example",
|
||||||
|
"demo",
|
||||||
|
"test",
|
||||||
|
"openssl",
|
||||||
|
"crt",
|
||||||
|
"p12",
|
||||||
|
"csr",
|
||||||
|
"certificate",
|
||||||
|
"certs",
|
||||||
|
"cert",
|
||||||
|
"key",
|
||||||
|
"private",
|
||||||
|
"public"
|
||||||
|
],
|
||||||
|
"author": "AJ ONeal <aj@daplie.com> (https://daplie.com)",
|
||||||
|
"license": "Apache2",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Daplie/walnut/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Daplie/walnut",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^2.9.6",
|
||||||
|
"connect": "^3.3.4",
|
||||||
|
"foreachasync": "^5.0.5",
|
||||||
|
"ssl-root-cas": "^1.1.7",
|
||||||
|
"vhost": "^3.0.0",
|
||||||
|
"bluebird": "^2.9.3",
|
||||||
|
"check-ip-address": "^1.0.0",
|
||||||
|
"express": "^4.11.2",
|
||||||
|
"request": "^2.51.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<pre><code>My name is Marvin, not that you would care.
|
||||||
|
|
||||||
|
You've reached {{host}} on port {{port}}. Meh... Congratulations, I guess."</code></pre>
|
||||||
|
<script>
|
||||||
|
document.body.innerHTML = document.body.innerHTML
|
||||||
|
.replace(/{{(\s+)?host(\s+)?}}/, location.hostname)
|
||||||
|
.replace(/{{(\s+)?port(\s+)?}}/, location.host.replace(/.*:/, ''))
|
||||||
|
;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var https = require('https')
|
||||||
|
, http = require('http')
|
||||||
|
, path = require('path')
|
||||||
|
, port = process.argv[2] || 65443
|
||||||
|
, insecurePort = process.argv[3] || 65080
|
||||||
|
, fs = require('fs')
|
||||||
|
, path = require('path')
|
||||||
|
, checkip = require('check-ip-address')
|
||||||
|
, server
|
||||||
|
, insecureServer
|
||||||
|
, options
|
||||||
|
, certsPath = path.join(__dirname, 'certs', 'server')
|
||||||
|
, caCertsPath = path.join(__dirname, 'certs', 'ca')
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// SSL Certificates
|
||||||
|
//
|
||||||
|
options = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'my-server.key.pem'))
|
||||||
|
, ca: [ fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem')) ]
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'my-server.crt.pem'))
|
||||||
|
, requestCert: false
|
||||||
|
, rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Serve an Express App securely with HTTPS
|
||||||
|
//
|
||||||
|
server = https.createServer(options);
|
||||||
|
checkip.getExternalIp().then(function (ip) {
|
||||||
|
var host = ip || 'local.helloworld3000.com'
|
||||||
|
;
|
||||||
|
|
||||||
|
function listen(app) {
|
||||||
|
server.on('request', app);
|
||||||
|
server.listen(port, function () {
|
||||||
|
port = server.address().port;
|
||||||
|
console.log('Listening on https://127.0.0.1:' + port);
|
||||||
|
console.log('Listening on https://local.helloworld3000.com:' + port);
|
||||||
|
if (ip) {
|
||||||
|
console.log('Listening on https://' + ip + ':' + port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicDir = path.join(__dirname, 'public');
|
||||||
|
var app = require('./app').create(server, host, port, publicDir);
|
||||||
|
listen(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Redirect HTTP ot HTTPS
|
||||||
|
//
|
||||||
|
// This simply redirects from the current insecure location to the encrypted location
|
||||||
|
//
|
||||||
|
insecureServer = http.createServer();
|
||||||
|
insecureServer.on('request', function (req, res) {
|
||||||
|
var newLocation = 'https://'
|
||||||
|
+ req.headers.host.replace(/:\d+/, ':' + port) + req.url
|
||||||
|
;
|
||||||
|
|
||||||
|
var metaRedirect = ''
|
||||||
|
+ '<html>\n'
|
||||||
|
+ '<head>\n'
|
||||||
|
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||||
|
+ ' <META http-equiv="refresh" content="0;URL=' + newLocation + '">\n'
|
||||||
|
+ '</head>\n'
|
||||||
|
+ '<body style="display: none;">\n'
|
||||||
|
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
||||||
|
+ ' <a href="' + newLocation + '">' + newLocation + '</a></p>\n'
|
||||||
|
+ '</body>\n'
|
||||||
|
+ '</html>\n'
|
||||||
|
;
|
||||||
|
|
||||||
|
// DO NOT HTTP REDIRECT
|
||||||
|
/*
|
||||||
|
res.setHeader('Location', newLocation);
|
||||||
|
res.statusCode = 302;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// BAD NEWS BEARS
|
||||||
|
//
|
||||||
|
// When people are experimenting with the API and posting tutorials
|
||||||
|
// they'll use cURL and they'll forget to prefix with https://
|
||||||
|
// If we allow that, then many users will be sending private tokens
|
||||||
|
// and such with POSTs in clear text and, worse, it will work!
|
||||||
|
// To minimize this, we give browser users a mostly optimal experience,
|
||||||
|
// but people experimenting with the API get a message letting them know
|
||||||
|
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.end(metaRedirect);
|
||||||
|
});
|
||||||
|
insecureServer.listen(insecurePort, function(){
|
||||||
|
console.log("\nRedirecting all http traffic to https\n");
|
||||||
|
});
|
|
@ -0,0 +1,142 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var https = require('https')
|
||||||
|
, http = require('http')
|
||||||
|
, PromiseA = require('bluebird').Promise
|
||||||
|
, forEachAsync = require('foreachasync').forEachAsync.create(PromiseA)
|
||||||
|
, fs = require('fs')
|
||||||
|
, path = require('path')
|
||||||
|
, crypto = require('crypto')
|
||||||
|
, connect = require('connect')
|
||||||
|
, vhost = require('vhost')
|
||||||
|
|
||||||
|
// connect / express app
|
||||||
|
, app = connect()
|
||||||
|
|
||||||
|
// SSL Server
|
||||||
|
, secureContexts = {}
|
||||||
|
, secureOpts
|
||||||
|
, secureServer
|
||||||
|
, securePort = /*process.argv[2] ||*/ 443
|
||||||
|
|
||||||
|
// force SSL upgrade server
|
||||||
|
, insecureServer
|
||||||
|
, insecurePort = /*process.argv[3] ||*/ 80
|
||||||
|
|
||||||
|
// the ssl domains I have
|
||||||
|
// TODO read vhosts minus
|
||||||
|
, domains = fs.readdirSync(path.join(__dirname, 'vhosts')).filter(function (node) {
|
||||||
|
// not a hidden or private file
|
||||||
|
return '.' !== node[0] && '_' !== node[0];
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
require('ssl-root-cas')
|
||||||
|
.inject()
|
||||||
|
;
|
||||||
|
|
||||||
|
function getAppContext(domain) {
|
||||||
|
var localApp
|
||||||
|
;
|
||||||
|
|
||||||
|
localApp = require(path.join(__dirname, 'vhosts', domain, 'app.js'));
|
||||||
|
if (localApp.create) {
|
||||||
|
// TODO read local config.yml and pass it in
|
||||||
|
localApp = localApp.create(/*config*/);
|
||||||
|
}
|
||||||
|
if (!localApp.then) {
|
||||||
|
localApp = PromiseA.resolve(localApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachAsync(domains, function (domain) {
|
||||||
|
secureContexts[domain] = crypto.createCredentials({
|
||||||
|
key: fs.readFileSync(path.join(__dirname, 'vhosts', domain, 'certs/server/my-server.key.pem'))
|
||||||
|
, cert: fs.readFileSync(path.join(__dirname, 'vhosts', domain, 'certs/server/my-server.crt.pem'))
|
||||||
|
, ca: fs.readdirSync(path.join(__dirname, 'vhosts', domain, 'certs/ca')).map(function (node) {
|
||||||
|
return fs.readFileSync(path.join(__dirname, 'vhosts', domain, 'certs/ca', node));
|
||||||
|
})
|
||||||
|
}).context;
|
||||||
|
|
||||||
|
return getAppContext(domain).then(function (localApp) {
|
||||||
|
app.use(vhost('www.' + domain, localApp));
|
||||||
|
app.use(vhost(domain, localApp));
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
// fallback / default domain
|
||||||
|
/*
|
||||||
|
app.use('/', function (req, res) {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end("<html><body><h1>Hello, World... This isn't the domain you're looking for.</h1></body></html>");
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
//provide a SNICallback when you create the options for the https server
|
||||||
|
secureOpts = {
|
||||||
|
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
||||||
|
SNICallback: function (domain) {
|
||||||
|
//console.log('SNI:', domain);
|
||||||
|
return secureContexts[domain];
|
||||||
|
}
|
||||||
|
// fallback / default domain
|
||||||
|
, key: fs.readFileSync(path.join(__dirname, 'certs/server', 'dummy-server.key.pem'))
|
||||||
|
, cert: fs.readFileSync(path.join(__dirname, 'certs/server', 'dummy-server.crt.pem'))
|
||||||
|
, ca: fs.readdirSync(path.join(__dirname, 'certs/ca')).map(function (node) {
|
||||||
|
return fs.readFileSync(path.join(__dirname, 'certs/ca', node));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
secureServer = https.createServer(secureOpts);
|
||||||
|
secureServer.on('request', app);
|
||||||
|
secureServer.listen(securePort, function () {
|
||||||
|
console.log("Listening on https://localhost:" + secureServer.address().port);
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Redirect HTTP ot HTTPS
|
||||||
|
//
|
||||||
|
// This simply redirects from the current insecure location to the encrypted location
|
||||||
|
//
|
||||||
|
insecureServer = http.createServer();
|
||||||
|
insecureServer.on('request', function (req, res) {
|
||||||
|
var newLocation = 'https://'
|
||||||
|
+ req.headers.host.replace(/:\d+/, ':' + port) + req.url
|
||||||
|
;
|
||||||
|
|
||||||
|
var metaRedirect = ''
|
||||||
|
+ '<html>\n'
|
||||||
|
+ '<head>\n'
|
||||||
|
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||||
|
+ ' <META http-equiv="refresh" content="0;URL=' + newLocation + '">\n'
|
||||||
|
+ '</head>\n'
|
||||||
|
+ '<body style="display: none;">\n'
|
||||||
|
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
||||||
|
+ ' <a href="' + newLocation + '">' + newLocation + '</a></p>\n'
|
||||||
|
+ '</body>\n'
|
||||||
|
+ '</html>\n'
|
||||||
|
;
|
||||||
|
|
||||||
|
// DO NOT HTTP REDIRECT
|
||||||
|
/*
|
||||||
|
res.setHeader('Location', newLocation);
|
||||||
|
res.statusCode = 302;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// BAD NEWS BEARS
|
||||||
|
//
|
||||||
|
// When people are experimenting with the API and posting tutorials
|
||||||
|
// they'll use cURL and they'll forget to prefix with https://
|
||||||
|
// If we allow that, then many users will be sending private tokens
|
||||||
|
// and such with POSTs in clear text and, worse, it will work!
|
||||||
|
// To minimize this, we give browser users a mostly optimal experience,
|
||||||
|
// but people experimenting with the API get a message letting them know
|
||||||
|
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.end(metaRedirect);
|
||||||
|
});
|
||||||
|
insecureServer.listen(insecurePort, function(){
|
||||||
|
console.log("\nRedirecting all http traffic to https\n");
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
var holepunch = require('./holepunch/beacon');
|
||||||
|
var config = require('./device.json')
|
||||||
|
var ports ;
|
||||||
|
|
||||||
|
ports = [
|
||||||
|
{ private: 22
|
||||||
|
, public: 65022
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'ssh' }
|
||||||
|
, testable: false
|
||||||
|
}
|
||||||
|
, { private: 65443
|
||||||
|
, public: 65443
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'https' }
|
||||||
|
}
|
||||||
|
, { private: 65080
|
||||||
|
, public: 65080
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'http' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
holepunch.run([
|
||||||
|
'aj.daplie.com'
|
||||||
|
, 'coolaj86.com'
|
||||||
|
, 'prod.coolaj86.com'
|
||||||
|
, 'production.coolaj86.com'
|
||||||
|
], ports).then(function () {
|
||||||
|
require('./vhost-sni-server.js');
|
||||||
|
});
|
Loading…
Reference in New Issue