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