Finishes proxy by name for groups.

This commit is contained in:
Zachary Boyd 2018-09-11 15:53:52 -04:00
parent be52091af1
commit f97272b5c0
7 changed files with 225 additions and 32 deletions

View file

@ -3,8 +3,9 @@
## [4.0.0] - 2018-09-09
### Added
- Instances can now added to one or multiple groups by setting the `Group` field in the instance definition to a single string or array.
- Instances can now added to one or more groups by setting the `Group` field in the instance definition to a single string or array.
- You can now proxy through a specific instance using the username field when connecting to a proxy. Setting `--proxyByName` or `-n` to false will disable this feature. For example to connect to an instance named `instance-1` via http use `http://instance-1:@localhost:9080`.
- You can also connect to a specific group of instances by setting `--proxyByName` or `-n` to "group". If enabled requests made to `://foo:@localhost:9080` would be routed to instances in the `foo` group.
- The control server will accept WebSocket connections if the `--websocketControlHost` or `-w` argument is set. If the argument is used without a hostname it will default to 9078 on all interfaces.
- All servers (DNS, HTTP, SOCKS and Control) all have a `listen` method which takes a port and optionally, a host returns a Promise that will resolve when the server is listening.

View file

@ -39,6 +39,8 @@ class HTTPServer extends Server {
if (!this.proxy_by_name)
return true;
this.logger.verbose(`[http]: connected attempted to instance "${username}"`);
let deny_un = this.proxy_by_name.deny_unidentified_users;
let header = req.headers['authorization'] || req.headers['proxy-authorization'];
@ -54,9 +56,17 @@ class HTTPServer extends Server {
if ( !username && deny_un ) return deny();
else if (!username) return true;
this.logger.verbose(`[http]: connected attempted to instance "${username}"`);
let instance;
let instance = this.tor_pool.instance_by_name(username);
if (this.proxy_by_name.mode === 'individual')
instance = this.tor_pool.instance_by_name(username);
else if (this.proxy_by_name.mode === 'group') {
if (!this.tor_pool.group_names.has(username)) return deny();
instance = this.tor_pool.next_by_group(username);
}
else
throw Error(`Unknown "proxy_by_name" mode ${this.proxy_by_name.mode}`);
if (!instance) return deny();
req.instance = instance;

View file

@ -15,21 +15,33 @@ class SOCKSServer extends Server{
});
}
get_instance_pbn(username) {
if (this.proxy_by_name.mode === 'individual')
return this.tor_pool.instance_by_name(username);
else if (this.proxy_by_name.mode === 'group') {
return this.tor_pool.next_by_group(username);
} else
throw Error(`Unknown "proxy_by_name" mode ${this.proxy_by_name.mode}`);
}
authenticate_user(username, password, callback) {
let deny_un = this.proxy_by_name.deny_unidentified_users;
this.logger.verbose(`[socks]: connected attempted to instance "${username}"`);
// 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);
if (this.proxy_by_name.mode === 'individual'){
if (!this.tor_pool.instance_names.includes(username)) return callback(false);
}
else if (this.proxy_by_name.mode === 'group') {
if (!this.tor_pool.group_names.has(username)) return callback(false);
}
else
throw Error(`Unknown "proxy_by_name" mode "${this.proxy_by_name.mode}"`);
// Otherwise allow
callback(true, true);
@ -41,7 +53,7 @@ class SOCKSServer extends Server{
let instance;
if (inbound_socket.user)
instance = this.tor_pool.instance_by_name(inbound_socket.user);
instance = this.get_instance_pbn(inbound_socket.user);
let outbound_socket;
let buffer = [];

View file

@ -149,8 +149,8 @@ class TorPool extends EventEmitter {
return instances.length;
if (prop === 'rotate') {
return () => {
instances.rotate(1);
return (num) => {
instances.rotate(typeof(num) === 'undefined' ? 1 : num);
save_index();
};
}
@ -167,7 +167,6 @@ class TorPool extends EventEmitter {
instances_in_group = this.instances.filter((instance) => instance.instance_group.indexOf(prop) !== -1);
}
instances_in_group = _.sortBy(instances_in_group, ['_index', 'instance_name']);
instances_in_group.group_name = prop;
@ -294,7 +293,7 @@ class TorPool extends EventEmitter {
}
next_by_group(group) {
this.groups[group].rotate();
this.groups[group].rotate(1);
return this.groups[group][0];
}

View file

@ -2,6 +2,7 @@ const nconf = require('nconf');
const request = require('request-promise');
const getPort = require('get-port');
const { assert } = require('chai');
const _ = require('lodash');
const { TorPool, HTTPServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
@ -89,12 +90,13 @@ describe('HTTPServer', function () {
let httpPort;
let instance_def = {
Name: 'instance-3'
Name: 'instance-3',
Group: 'foo'
};
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true });
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: 'individual' });
this.timeout(WAIT_FOR_CREATE * 3);
@ -105,17 +107,21 @@ describe('HTTPServer', function () {
await httpServer.listen(httpPort);
});
it(`should service a request for example.com through ${instance_def.Name}`, function (done) {
it(`should service a request for example.com through the instance named ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME);
let req;
httpServer.on('instance-connection', (instance, source) => {
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
req.cancel();
httpServer.removeAllListeners('instance-connection');;
done();
});
};
httpServer.on('instance-connection', connectionHandler);
req = request({
url: 'http://example.com',
@ -124,6 +130,57 @@ describe('HTTPServer', function () {
.catch(done)
});
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
httpServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({ proxy: `http://foo:@127.0.0.1:${httpPort}` });
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === httpServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
let names_in_group = httpServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
httpServer.removeAllListeners('instance-connection');
done();
}
};
httpServer.on('instance-connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
await request({
url: 'http://example.com'
});
i++;
}
})
.then(async () => {
await httpServerTorPool.remove_by_name('instance-4');
httpServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {
@ -169,12 +226,13 @@ describe('HTTPServer', function () {
let httpPort;
let instance_def = {
Name: 'instance-3'
Name: 'instance-3',
Group: 'foo'
};
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true });
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3);
@ -188,12 +246,16 @@ describe('HTTPServer', function () {
it(`should service a request for example.com through ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME);
let req;
httpServer.on('instance-connection', (instance, source) => {
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
req.cancel();
httpServer.removeAllListeners('instance-connection');
done();
});
};
httpServer.on('instance-connection', connectionHandler);
req = request({
url: 'https://example.com',
@ -202,6 +264,55 @@ describe('HTTPServer', function () {
.catch(done);
});
it(`four requests made to example.com through the group named "foo" should come from instances in the "foo" group`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
httpServer.proxy_by_name.mode = "group";
})()
.then(async () => {
let request = require('request-promise').defaults({ proxy: `http://foo:@127.0.0.1:${httpPort}` });
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === httpServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
let names_in_group = httpServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
httpServer.removeAllListeners('instance-connection');
done();
}
};
httpServer.on('instance-connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
await request({
url: 'https://example.com'
});
i++;
}
})
.then(async () => {
await httpServerTorPool.remove_by_name('instance-4');
httpServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {

View file

@ -3,6 +3,7 @@ const request = require('request-promise');
const getPort = require('get-port');
const { HttpAgent, auth } = require('socksv5');
const { assert } = require('chai');
const _ = require('lodash');
const { TorPool, SOCKSServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
@ -28,7 +29,6 @@ describe('SOCKSServer', function () {
await socksServer.listen(socksPort);
});
it('should service a request for example.com', async function () {
this.timeout(PAGE_LOAD_TIME);
@ -53,17 +53,18 @@ describe('SOCKSServer', function () {
});
});
describe('#authenticate_user(username, password) - proxy by name', function () {
describe('#authenticate_user(username, password)', function () {
let socksPort;
let socksServerTorPool;
let socksServer;
let instance_def = {
Name: 'instance-3'
Name: 'instance-3',
Group: "foo"
};
before('start up server', async function (){
socksServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true });
socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3);
@ -79,13 +80,15 @@ describe('SOCKSServer', function () {
this.timeout(PAGE_LOAD_TIME);
let req;
socksServer.on('instance-connection', (instance, source) => {
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
req.cancel();
socksServer.removeAllListeners('instance-connection');
done();
});
};
socksServer.on('instance-connection', connectionHandler);
req = request({
url: 'http://example.com',
@ -99,6 +102,63 @@ describe('SOCKSServer', function () {
.catch(done);
});
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
await socksServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
socksServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.UserPassword('foo', "doesn't mater") ]
})
});
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === socksServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
let names_in_group = socksServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
socksServer.removeAllListeners('instance-connection');
done();
}
};
socksServer.on('instance-connection', connectionHandler);
let i = 0;
while (i < socksServerTorPool.instances.length) {
await request({
url: 'http://example.com'
});
i++;
}
})
.then(async () => {
await socksServerTorPool.remove_by_name('instance-4');
socksServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
// it(`shouldn't be able to send a request without a username`, async function() {
// let f = () => {};
// try {

View file

@ -1,4 +1,4 @@
module.exports = {
WAIT_FOR_CREATE: 240000,
PAGE_LOAD_TIME: 60000
PAGE_LOAD_TIME: 60000 * 5
};