Can now connect directly to an instance via http

This commit is contained in:
Zachary Boyd 2018-09-09 23:44:24 -04:00
parent 4553bcc82b
commit ece4d4ea02
9 changed files with 149 additions and 27 deletions

View file

@ -17,7 +17,6 @@
"devDependencies": { "devDependencies": {
"chai": "^4.1.2", "chai": "^4.1.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"proxy-agent": "^3.0.1",
"request": "^2.79.0", "request": "^2.79.0",
"request-promise": "^4.2.2" "request-promise": "^4.2.2"
}, },
@ -37,6 +36,7 @@
"socksv5": "git+https://github.com/znetstar/socksv5.git#431e541390314adbbe765650d8d810ec1df38d8a", "socksv5": "git+https://github.com/znetstar/socksv5.git#431e541390314adbbe765650d8d810ec1df38d8a",
"temp": "^0.8.3", "temp": "^0.8.3",
"winston": "^3.0.0-rc5", "winston": "^3.0.0-rc5",
"yargs": "^11.0.0" "yargs": "^11.0.0",
"proxy-agent": "^3.0.1"
} }
} }

View file

@ -144,14 +144,14 @@ class ControlServer {
} }
async createSOCKSServer(port, hostname) { async createSOCKSServer(port, hostname) {
this.socksServer = new SOCKSServer(this.torPool, this.logger); this.socksServer = new SOCKSServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : ""));
await this.socksServer.listen(port || default_ports.socks, hostname); await this.socksServer.listen(port || default_ports.socks, hostname);
this.logger.info(`[socks]: listening on socks5://${hostname}:${port}`); this.logger.info(`[socks]: listening on socks5://${hostname}:${port}`);
this.socksServer; this.socksServer;
} }
async createHTTPServer(port, hostname) { async createHTTPServer(port, hostname) {
this.httpServer = new HTTPServer(this.torPool, this.logger); this.httpServer = new HTTPServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : ""));
await this.httpServer.listen(port || default_ports.http, hostname); await this.httpServer.listen(port || default_ports.http, hostname);
this.logger.info(`[http]: listening on http://${hostname}:${port}`); this.logger.info(`[http]: listening on http://${hostname}:${port}`);
this.httpServer; this.httpServer;

View file

@ -4,7 +4,7 @@ const { Server } = http;
const Promise = require('bluebird'); const Promise = require('bluebird');
const socks = require('socksv5'); const socks = require('socksv5');
const SocksProxyAgent = require('socks-proxy-agent'); const ProxyAgent = require('proxy-agent');
const TOR_ROUTER_PROXY_AGENT = 'tor-router'; const TOR_ROUTER_PROXY_AGENT = 'tor-router';
@ -21,8 +21,57 @@ class HTTPServer extends Server {
}); });
} }
constructor(tor_pool, logger) { authenticate_user_http(req, res) {
return this.authenticate_user(req, () => {
res.writeHead(407, { 'Proxy-Authenticate': `Basic realm="Name of instance to route to"` });
res.end();
return false;
})
}
authenticate_user_connect(req, socket) {
return this.authenticate_user(req, () => {
socket.end();
return false;
})
}
authenticate_user(req, deny) {
if (!this.proxy_by_name)
return true;
let deny_un = this.proxy_by_name.deny_unidentified_users;
let header = req.headers['authorization'] || req.headers['proxy-authorization'];
if (!header && deny_un) return deny();
else if (!header) return true;
let token = header.split(/\s+/).pop();
if (!token && deny_un) return deny();
else if (!token) return true;
let buf = new Buffer.from(token, 'base64').toString();
let username = buf.split(/:/).shift();
if ( !username && deny_un ) return deny();
else if (!username) return true;
this.logger.verbose(`[http]: connected attempted to instance "${username}"`);
let instance = this.tor_pool.instance_by_name(username);
if (!instance) return deny();
req.instance = instance;
return true;
}
constructor(tor_pool, logger, proxy_by_name) {
let handle_http_connections = (req, res) => { let handle_http_connections = (req, res) => {
if (!this.authenticate_user_http(req, res))
return;
let { instance } = req;
let url = URL.parse(req.url); let url = URL.parse(req.url);
url.port = url.port || 80; url.port = url.port || 80;
@ -52,7 +101,7 @@ class HTTPServer extends Server {
port: url.port, port: url.port,
path: url.path, path: url.path,
headers: req.headers, headers: req.headers,
agent: new SocksProxyAgent(`socks://127.0.0.1:${socks_port}`) agent: new ProxyAgent(`socks://127.0.0.1:${socks_port}`)
}, (proxy_res) => { }, (proxy_res) => {
proxy_res.on('data', (chunk) => { proxy_res.on('data', (chunk) => {
res.write(chunk); res.write(chunk);
@ -83,8 +132,17 @@ class HTTPServer extends Server {
proxy_req.end(); proxy_req.end();
}; };
if (tor_pool.instances.length) { if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[socks]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
connect(tor_pool.next()); connect(tor_pool.next());
} else { } else {
this.logger.debug(`[http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`); this.logger.debug(`[http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
@ -93,6 +151,11 @@ class HTTPServer extends Server {
}; };
let handle_connect_connections = (req, inbound_socket, head) => { let handle_connect_connections = (req, inbound_socket, head) => {
if (!this.authenticate_user_connect(req, inbound_socket))
return;
let { instance } = req;
let hostname = req.url.split(':').shift(); let hostname = req.url.split(':').shift();
let port = Number(req.url.split(':').pop()); let port = Number(req.url.split(':').pop());
@ -135,11 +198,21 @@ class HTTPServer extends Server {
inbound_socket.pipe(outbound_socket); inbound_socket.pipe(outbound_socket);
}); });
}; };
if (tor_pool.instances.length) {
connect(tor_pool.next()); if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[socks]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
connect(this.tor_pool.next());
} else { } else {
this.logger.debug(`[http-connect]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`); this.logger.debug(`[http-connect]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
tor_pool.once('instance_created', connect); this.tor_pool.once('instance_created', connect);
} }
}; };
@ -148,6 +221,7 @@ class HTTPServer extends Server {
this.logger = logger || require('./winston-silent-logger'); this.logger = logger || require('./winston-silent-logger');
this.tor_pool = tor_pool; this.tor_pool = tor_pool;
this.proxy_by_name = proxy_by_name;
} }
}; };

View file

@ -15,10 +15,35 @@ class SOCKSServer extends Server{
}); });
} }
authenticate_user(username, password, callback) {
let deny_un = this.proxy_by_name.deny_unidentifed_users;
// No username and deny unindentifed then deny
if (!username && deny_un) callback(false);
// Otherwise if there is no username allow
else if (!username) callback(true);
this.logger.verbose(`[socks]: connected attempted to instance "${username}"`);
let instance = this.tor_pool.instance_by_name(username);
// If a username is specified but no instances match that username deny
if (!instance)
return callback(false);
// Otherwise allow
callback(true, instance);
}
constructor(tor_pool, logger, proxy_by_name) { constructor(tor_pool, logger, proxy_by_name) {
let handleConnection = (info, accept, deny) => { let handleConnection = (info, accept, deny) => {
let inbound_socket = accept(true); let inbound_socket = accept(true);
var outbound_socket; let instance;
if (inbound_socket.user)
instance = inbound_socket.user;
let outbound_socket;
let buffer = []; let buffer = [];
let onInboundData = (data) => buffer.push(data) let onInboundData = (data) => buffer.push(data)
@ -70,27 +95,37 @@ class SOCKSServer extends Server{
} }
}); });
}; };
if (tor_pool.instances.length) {
connect(tor_pool.next()); if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[socks]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
connect(this.tor_pool.next());
} else { } else {
this.logger.debug(`[socks]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`); this.logger.debug(`[socks]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
tor_pool.once('instance_created', connect); this.tor_pool.once('instance_created', connect);
} }
}; };
super(handleConnection); super(handleConnection);
this.logger = logger || require('./winston-silent-logger');; this.logger = logger || require('./winston-silent-logger');
this.tor_pool = tor_pool;
this.proxy_by_name = proxy_by_name;
let auth = socks.auth.None();
if (!proxy_by_name) { if (!proxy_by_name) {
this.logger.debug(`[socks]: connecting to a specific instance by name has ben turned off`); this.logger.debug(`[socks]: connecting to a specific instance by name has ben turned off`);
let auth = socks.auth.None();
} else { } else {
this.logger.debug(`[socks]: connecting to a specific instance by name has ben turned on`); this.logger.debug(`[socks]: connecting to a specific instance by name has ben turned on`);
let auth = socks.auth.UserPassword( auth = socks.auth.UserPassword(this.authenticate_user);
(username, password, cb) => {
}
);
} }
this.useAuth(auth); this.useAuth(auth);

View file

@ -124,11 +124,11 @@ class TorPool extends EventEmitter {
} }
instance_by_name(name) { instance_by_name(name) {
return this.instances.filter((i) => i.definition.Name === name)[0]; return this._instances.filter((i) => i.definition.Name === name)[0];
} }
instance_at(index) { instance_at(index) {
return this.instances[index]; return this._instances[index];
} }
async remove(instances) { async remove(instances) {

View file

@ -64,6 +64,8 @@ class TorProcess extends EventEmitter {
return this._controller || null; return this._controller || null;
} }
get ready() { return this._ready; }
/* Passthrough to granax */ /* Passthrough to granax */
async new_identity() { async new_identity() {
@ -133,7 +135,7 @@ class TorProcess extends EventEmitter {
}); });
this.once('ready', () => { this.once('ready', () => {
this.ready = true; this._ready = true;
this.logger.info(`[tor-${this.instance_name}]: tor is ready`); this.logger.info(`[tor-${this.instance_name}]: tor is ready`);
}); });

View file

@ -9,6 +9,8 @@ module.exports = {
"socksHost": null, "socksHost": null,
"dnsHost": null, "dnsHost": null,
"httpHost": null, "httpHost": null,
"proxyByName": true,
"denyUnidentifedUsers": false,
"logLevel": "info", "logLevel": "info",
"loadBalanceMethod": "round_robin", "loadBalanceMethod": "round_robin",
"torConfig": { "torConfig": {

View file

@ -177,6 +177,11 @@ let argv_config =
alias: "torPath", alias: "torPath",
describe: "Provide the path for the Tor executable that will be used", describe: "Provide the path for the Tor executable that will be used",
demand: false demand: false
},
n: {
alias: 'proxyByName',
describe: 'Allow connecting to a specific instance identified by the username field when connecting to a proxy',
demand: false
} }
}); });

View file

@ -8,6 +8,8 @@ const env_whitelist = [ "CONTROL_HOST",
'PARENT_DATA_DIRECTORIES', 'PARENT_DATA_DIRECTORIES',
'LOAD_BALANCE_METHOD', 'LOAD_BALANCE_METHOD',
"WEBSOCKET_CONTROL_PORT", "WEBSOCKET_CONTROL_PORT",
"PROXY_BY_NAME",
"DENY_UNIDENTIFIED_USERS",
"controlHost", "controlHost",
"torPath", "torPath",
"instances", "instances",
@ -17,7 +19,9 @@ const env_whitelist = [ "CONTROL_HOST",
"logLevel", "logLevel",
'parentDataDirectories', 'parentDataDirectories',
'loadBalanceMethod', 'loadBalanceMethod',
"websocketControlPort" "websocketControlPort",
"proxyByName",
"denyUnidentifedUsers"
]; ];
module.exports = (nconf) => { module.exports = (nconf) => {