@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
1,061 lines (1,060 loc) • 50.6 kB
JavaScript
// LICENSE_CODE ZON ISC
'use strict'; /*jslint node:true, mocha:true*/
const nock = require('nock');
const fs = require('fs');
const os = require('os');
const path = require('path');
const assert = require('assert');
const request = require('request');
const sinon = require('sinon');
const winston = require('winston');
// needed to make lpm_file.js to set work dir to tmp dir
process.argv.push('--dir', os.tmpdir());
const Manager = require('../lib/manager.js');
const cities = require('../lib/cities');
sinon.stub(cities, 'ensure_data', ()=>null);
sinon.stub(process, 'exit');
const logger = require('../lib/logger.js');
const etask = require('../util/etask.js');
const zutil = require('../util/util.js');
const pkg = require('../package.json');
const qw = require('../util/string.js').qw;
const date = require('../util/date.js');
const user_agent = require('../util/user_agent.js');
const lpm_util = require('../util/lpm_util.js');
const util_lib = require('../lib/util.js');
const puppeteer = require('../lib/puppeteer.js');
const consts = require('../lib/consts.js');
const customer = 'abc';
const password = 'xyz';
const {assert_has} = require('./common.js');
const api_base = 'https://'+pkg.api_domain;
const {SEC} = date.ms;
describe('manager', ()=>{
let app, temp_files, logger_stub, sb;
const get_param = (args, param)=>{
let i = args.indexOf(param)+1;
return i ? args[i] : null;
};
const app_with_args = (args, opt={})=>etask(function*(){
let manager, {only_explicit, start_manager} = opt;
this.finally(()=>{
if (this.error && manager)
return manager.stop(true);
});
args = args||[];
if (!only_explicit)
{
if (!get_param(args, '--proxy'))
args = args.concat(['--proxy', '127.0.0.1']);
if (!get_param(args, '--proxy_port'))
args = args.concat(['--proxy_port', 24000]);
if (!get_param(args, '--config')&&!get_param(args, '--no-config'))
args.push('--no-config');
if (!get_param(args, '--customer'))
args = args.concat(['--customer', customer]);
if (!get_param(args, '--password'))
args = args.concat(['--password', password]);
if (!get_param(args, '--dropin')&&!get_param(args, '--no-dropin'))
args = args.concat(['--no-dropin']);
if (!get_param(args, '--cookie')&&!get_param(args, '--no-cookie'))
args.push('--no-cookie');
if (!get_param(args, '--local_login') &&
!get_param(args, '--no-local_login'))
{
args = args.concat(['--no-local_login']);
}
args = args.concat('--loki', '/tmp/testdb');
}
Manager.prototype.set_current_country = ()=>null;
Manager.prototype.lpm_users_get = ()=>null;
Manager.prototype.get_lpm_conf = function(){
return {_defaults: {zones: {
static: {},
foo: {},
}}};
};
manager = new Manager(lpm_util.init_args(args));
manager.lpm_conn.init = ()=>null;
manager.lpm_f.init = ()=>null;
if (start_manager!==false)
yield manager.start();
return {manager};
});
let tmp_file_counter = 0;
const temp_file_path = (ext='tmp')=>({
path: path.join(os.tmpdir(),
`test-${Date.now()}-${tmp_file_counter++}.${ext}`),
done(){
if (!this.path)
return;
try { fs.unlinkSync(this.path); }
catch(e){ console.error(e.message); }
this.path = null;
},
});
const temp_file = (content, ext)=>{
const temp = temp_file_path(ext);
fs.writeFileSync(temp.path, JSON.stringify(content));
return temp;
};
const app_with_config = (opt={})=>etask(function*(){
const args = [];
const cli = opt.cli||{};
Object.keys(cli).forEach(k=>{
if (typeof cli[k]=='boolean')
{
if (cli[k])
args.push('--'+k);
else
args.push('--no-'+k);
return;
}
args.push('--'+k);
if (Array.isArray(cli[k]))
args.push(...cli[k]);
else
args.push(cli[k]);
});
if (opt.config)
{
const config_file = temp_file(opt.config, 'json');
args.push('--config');
args.push(config_file.path);
temp_files.push(config_file);
}
return yield app_with_args(args, opt);
});
const app_with_proxies = (proxies, cli)=>etask(function*(){
return yield app_with_config({config: {proxies}, cli});
});
const api = (_path, method, data, json, headers)=>etask(function*(){
const admin = 'http://127.0.0.1:'+Manager.default.www;
const opt = {
url: admin+'/'+_path,
method: method||'GET',
json,
body: data,
headers: headers || {'x-lpm-fake': true},
};
return yield etask.nfn_apply(request, [opt]);
});
const api_json = (_path, opt={})=>etask(function*(){
return yield api(_path, opt.method, opt.body, true, opt.headers);
});
const json = (_path, method, data)=>etask(function*(){
const res = yield api(_path, method, data, true);
assert.equal(res.statusCode, 200);
return res.body;
});
const make_user_req = (port=24000, status=200)=>{
return api_json('api/test/'+port, {
method: 'POST',
body: {
url: 'http://lumtest.com/myip.json',
headers: {'x-lpm-fake': true, 'x-lpm-fake-status': status},
},
});
};
afterEach('after manager', etask._fn(function*(_this){
nock.cleanAll();
if (!app)
return;
yield app.manager.stop(true);
if (process.platform=='win32')
yield etask.sleep(10);
if (!app)
return;
app = null;
}));
beforeEach(()=>{
temp_files = [];
sb = sinon.sandbox.create();
sb.stub(os, 'cpus').returns([1, 1]);
nock(api_base).get('/').times(2).reply(200, {});
nock(api_base).get('/lpm/server_conf').query(true).reply(200, {});
});
afterEach('after manager 2', ()=>{
sb.verifyAndRestore();
temp_files.forEach(f=>f.done());
});
describe('get_params', ()=>{
const t = (name, _args, expected)=>it(name, etask._fn(function(_this){
const mgr = new Manager(lpm_util.init_args(_args));
assert.deepEqual(expected, mgr.get_params());
}));
const def = '--throttle 2';
t('default', qw(def), ['--throttle', 2]);
t('credentials', qw`${def} --customer test_user --password abcdefgh`,
['--throttle', 2]);
t('credentials with no-config',
['--no-config', '--customer', 'usr', '--password', 'abc',
'--zone', 'z'],
['--no-config', '--customer', 'usr', '--password', 'abc',
'--zone', 'z']);
});
describe('init proxy', ()=>{
it('proxy_port is returned in the correct format', etask._fn(
function*(_this){
app = yield app_with_proxies([{port: 24000}]);
const new_proxy = {port: 24001};
const res = yield app.manager.init_proxy(new_proxy);
assert.ok(Object.keys(res).length, 1);
assert_has(res.proxy_port.opt, new_proxy, 'proxy_port.opt');
}));
it('"error" should not be saved to the proxy', etask._fn(
function*(_this){
app = yield app_with_proxies([{port: 24000}]);
sinon.stub(app.manager, 'validate_proxy').returns('my_error');
const new_proxy = {port: 24001};
const res = yield app.manager.init_proxy(new_proxy);
assert_has(res, {proxy_port: new_proxy, proxy_err: 'my_error'});
assert.ok(!res.proxy_port.error);
app.manager.validate_proxy.restore();
}));
});
describe('config load', ()=>{
const t = (name, config, expected)=>it(name, etask._fn(
function*(_this){
_this.timeout(6000);
app = yield app_with_config(config);
const proxies = yield json('api/proxies_running');
assert.equal(proxies.length, expected.length);
assert_has(proxies, expected, 'proxies');
}));
const simple_proxy = {port: 24024};
t('cli only', {cli: simple_proxy, config: []},
[Object.assign({proxy_type: 'persist'}, simple_proxy)]);
t('main config only', {config: {proxies: [simple_proxy]}},
[Object.assign({proxy_type: 'persist'}, simple_proxy)]);
t('config file', {config: {proxies: [simple_proxy]}}, [simple_proxy]);
describe('default zone', ()=>{
const zone_static = {password: ['pass1']};
const zone_foo = {password: ['pass2']};
const zones = {
static: Object.assign({}, zone_static),
foo: Object.assign({}, zone_foo),
};
const t2 = (name, config, expected, _defaults={zone: 'static'})=>{
t(name, zutil.set(config, 'cli.customer', 'testc1'), expected);
};
t2('from defaults', {config: {
_defaults: {
zone: 'foo',
customer: 'testc1',
lpm_token: 'token',
},
proxies: [simple_proxy],
}}, [Object.assign({zone: 'foo'}, simple_proxy)],
{zone: 'static', zones});
t2('keep default', {config: {
_defaults: {
zone: 'foo',
customer: 'testc1',
lpm_token: 'token',
},
proxies: [simple_proxy]},
}, [Object.assign({zone: 'foo'}, simple_proxy)]);
});
describe('args as default params for proxy ports', ()=>{
it('should use proxy from args', etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_args(['--proxy', '1.2.3.4',
'--proxy_port', '3939', '--dropin']);
const dropin = app.manager.proxy_ports[22225];
assert.equal(dropin.opt.proxy, '1.2.3.4');
assert.equal(dropin.opt.proxy_port, 3939);
}));
});
it('invalid reverse_lookup_values doesnt break running proxies',
etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_proxies([
{
port: 24000,
har_limit: 9999,
reverse_lookup_values: ['1.1.1.1'],
},
{
port: 24001,
har_limit: 9999,
reverse_lookup_values: 'invalid_not_array',
},
]);
const proxies = yield json('api/proxies_running');
const proxy = port=>proxies.find(p=>p.port==port);
assert.equal(proxies.length, 2);
assert.ok(proxy(24000).reverse_lookup_values);
assert.deepEqual(proxy(24000).reverse_lookup_values, ['1.1.1.1']);
assert.ok(!proxy(24001).reverse_lookup_values);
}));
});
describe('default values', ()=>{
it('default har_limit is 1024', etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_args(['--port', '24000']);
assert.equal(app.manager.proxy_ports[24000].opt.har_limit, 1024);
}));
it('applies explicit mgr argv to defaults', etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_args(['--port', '24000', '--har_limit',
'1337', '--api_domain', 'invalid_domain']);
const {opt} = app.manager.proxy_ports[24000];
assert.equal(app.manager._defaults.api_domain,
pkg.api_domain_fallback);
assert.equal(opt.har_limit, 1337);
assert.equal(opt.api_domain, pkg.api_domain_fallback);
}));
});
describe('report_bug', ()=>{
// XXX krzysztof: get rid of this
before(()=>{
const console_t = logger.transports.find(
t=>t instanceof winston.transports.Console);
console_t.silent = true;
});
// can't rm current log file because transports rely on it
beforeEach(()=>fs.truncateSync(logger.lpm_filename, 0));
it('should send logs, har and config', etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_config({config: {}, cli: {log: 'notice'}});
const desc = 'bug description', email = 'test@luminati.io';
const ua = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) '+
'Gecko/20100101 Firefox/47.0';
const req = {body: {desc, email}, get: ()=>ua};
const res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
const request_stub = sinon.stub(app.manager, 'api_request');
request_stub.returns({statusCode: 200, body: 'ok'});
yield app.manager.report_bug_api(req, res);
const report = request_stub.firstCall.args[0].form.report;
assert.ok(report.config);
assert.ok(report.log);
assert.ok(report.har);
assert.equal(report.desc, desc);
assert.equal(report.email, email);
assert.equal(report.browser, user_agent.guess_browser(ua).browser);
assert.equal(report.os, util_lib.format_platform(os.platform()));
sinon.assert.calledWith(res.status, 200);
sinon.assert.calledWith(res.json, 'ok');
}));
});
describe('dropin', function(){
this.timeout(6000);
it('off', etask._fn(function*(_this){
app = yield app_with_args(['--no-dropin']);
assert.ok(!app.manager.proxy_ports[22225]);
}));
it('on', etask._fn(function*(_this){
app = yield app_with_args(['--dropin']);
assert.ok(!!app.manager.proxy_ports[22225]);
}));
it('dropin_port', etask._fn(function*(_this){
app = yield app_with_args(['--dropin', '--dropin_port', '25000']);
assert.ok(!!app.manager.proxy_ports[25000]);
assert.ok(!app.manager.proxy_ports[22225]);
}));
});
describe('api', function(){
this.timeout(6000);
it('ssl', etask._fn(function*(_this){
app = yield app_with_args();
const res = yield api('ssl');
assert_has(res.headers, {
'content-type': 'application/x-x509-ca-cert',
'content-disposition': 'filename=luminati.crt',
}, 'headers');
assert.equal(res.body, fs.readFileSync(path.join(__dirname,
'../bin/ca.crt')), 'certificate');
}));
describe('version info', ()=>{
it('current', ()=>etask(function*(){
app = yield app_with_args();
const body = yield json('api/version');
assert.equal(body.version, pkg.version);
}));
});
describe('proxies', ()=>{
describe('get', ()=>{
it('normal', etask._fn(function*(_this){
const proxies = [{port: 24024}];
app = yield app_with_proxies(proxies);
let res = yield json('api/proxies');
assert_has(res, proxies, 'proxies');
res = yield json('api/proxies_running');
assert_has(res, proxies, 'proxies_running');
}));
});
describe('post', ()=>{
it('normal persist', etask._fn(function*(_this){
let sample_proxy = {port: 24001};
const res_proxy = Object.assign({customer, password},
sample_proxy);
app = yield app_with_proxies([], {});
let res = yield json('api/proxies', 'post',
{proxy: sample_proxy});
assert_has(res, {data: res_proxy}, 'proxies');
res = yield json('api/proxies');
assert.equal(res.length, 1);
}));
it('conflict', etask._fn(function*(_this){
const sample_proxy = {port: 24000};
const proxies = [sample_proxy];
app = yield app_with_proxies(proxies, {});
const res = yield api_json('api/proxies',
{method: 'post', body: {proxy: sample_proxy}});
assert.equal(res.statusCode, 400);
assert_has(res.body, {errors: []}, 'proxies');
}));
const t = (name, status_code)=>it(name, ()=>etask(function*(){
const sample_proxy = {port: 24000,
ext_proxies: ['127.0.0.1:8888']};
nock(api_base).get('/cp/lum_local_conf').query(true)
.reply(200, {mock_result: true, _defaults: true});
nock(api_base).post('/ext_proxy_created').query(true)
.reply(status_code, {});
app = yield app_with_config();
let res = yield json('api/proxies', 'post',
{proxy: sample_proxy});
assert_has(res, {data: sample_proxy}, 'proxies');
res = yield json('api/proxies_running');
assert_has(res, [sample_proxy], 'proxies');
}));
t('external', 200);
t('external, backend is down', 500);
it('external over the limit', etask._fn(function*(_this){
app = yield app_with_proxies([{port: 24000}]);
const ext_proxies = Array(consts.MAX_EXT_PROXIES+1).fill()
.map((_, i)=>`${++i}`);
const res = yield api_json('api/proxies', {method: 'post',
body: {proxy: {port: 24001, ext_proxies}}});
assert.equal(res.statusCode, 400);
assert.ok(!!res.body.errors.length);
}));
});
describe('put', ()=>{
it('normal', etask._fn(function*(_this){
const put_proxy = {port: 24001};
const proxies = [{port: 24000}];
app = yield app_with_proxies(proxies, {});
let res = yield json('api/proxies/24000', 'put',
{proxy: put_proxy});
assert_has(res, {data: put_proxy});
res = yield json('api/proxies_running');
assert_has(res, [put_proxy], 'proxies');
}));
it('inherit defaults', ()=>etask(function*(){
const put_proxy = {port: 24001};
const proxies = [{port: 24000}];
const res_proxy = Object.assign({}, {customer, password},
put_proxy);
app = yield app_with_proxies(proxies, {});
let res = yield json('api/proxies/24000', 'put',
{proxy: put_proxy});
assert_has(res, {data: res_proxy});
}));
it('conflict', etask._fn(function*(_this){
let proxies = [{port: 24000}, {port: 24001}];
app = yield app_with_proxies(proxies, {});
let res = yield api_json('api/proxies/24001',
{method: 'put', body: {proxy: {port: 24000}}});
assert.equal(res.statusCode, 400);
assert_has(res.body, {errors: []}, 'proxies');
}));
});
describe('delete', ()=>{
it('normal', etask._fn(function*(_this){
const proxies = [{port: 24000}];
app = yield app_with_proxies(proxies, {});
const res = yield api_json('api/proxies/24000',
{method: 'delete'});
assert.equal(res.statusCode, 204);
}));
it('cannot delete not existing', etask._fn(function*(_this){
app = yield app_with_args();
const res = yield api_json('api/proxies/24001',
{method: 'delete'});
assert.equal(res.statusCode, 500);
assert.equal(res.body,
'Server error: this proxy does not exist');
}));
it('cannot delete duplicated', etask._fn(function*(_this){
const proxies = [{port: 24000, multiply: 2}];
app = yield app_with_proxies(proxies, {});
const res = yield api_json('api/proxies/24001',
{method: 'delete'});
assert.equal(res.statusCode, 500);
assert.equal(res.body,
'Server error: cannot delete this port');
}));
});
describe('banip', ()=>{
let t = (name, body, status_code)=>it(name, etask._fn(
function*(_this){
app = yield app_with_proxies([{port: 24000}], {});
let res = yield api_json('api/proxies/24000/banip',
{method: 'post', body});
assert.equal(res.statusCode, status_code);
}));
t('no ip', {}, 400);
t('ip', {ip: '1.1.1.1'}, 204);
t('no ip', {ip: 'r0123456789abcdef0123456789ABCDEF'}, 204);
});
describe('duplicate port', ()=>{
it('works after updating port', etask._fn(function*(_this){
app = yield app_with_proxies([{port: 24000}], {});
const put_proxy = {port: 24001};
yield json('api/proxies/24000', 'put', {proxy: put_proxy});
const res = yield api_json('api/proxy_dup',
{method: 'post', body: {port: 24001}});
assert.equal(res.statusCode, 200);
}));
it('does not hang on errors', etask._fn(function*(_this){
app = yield app_with_proxies([{port: 24000}], {});
const stub = sinon.stub(app.manager, 'create_new_proxy',
()=>{ throw new Error('error creating proxy'); });
const res = yield api_json('api/proxy_dup',
{method: 'post', body: {port: 24000}});
assert.equal(res.statusCode, 500);
assert.equal(res.body,
'Server error: error creating proxy');
stub.restore();
}));
});
describe('refresh_sessions', ()=>{
const t = (name, opt, eq)=>it(name, etask.fn(function*(){
const proxy = Object.assign({port: 24000}, opt);
app = yield app_with_proxies([proxy], {});
const {statusCode, body} =
yield api_json(`api/refresh_sessions/${proxy.port}`);
assert.equal(statusCode, eq.code);
assert.deepEqual(body, eq.body);
}));
t('returns session_id when not rotating', null,
{code: 200, body: {session_id: '24000_1'}});
t('does not return session_id when rotating',
{rotate_session: true}, {code: 204});
});
});
describe('har logs', function(){
this.timeout(6000);
beforeEach(()=>etask(function*(){
app = yield app_with_args(['--customer', 'mock_user',
'--port', '24000']);
app.manager.loki.requests_clear();
app.manager.proxy_ports[24000].emit('usage', {
timeline: null,
url: 'http://bbc.com',
username: 'lum-customer-test_user-zone-static-session-qwe',
request: {url: 'http://bbc.com'},
response: {},
});
}));
it('fetches all the logs', etask._fn(function*(_this){
const res = yield api_json(`api/logs_har`);
assert_has(res.body.log.entries[0],
{request: {url: 'http://bbc.com'}});
assert.equal(res.body.log.entries.length, 1);
}));
it('search by url', etask._fn(function*(_this){
const res = yield api_json('api/logs_har?search=bbc');
assert_has(res.body.log.entries[0],
{request: {url: 'http://bbc.com'}});
assert.equal(res.body.log.entries.length, 1);
}));
it('search by url, no results', etask._fn(function*(_this){
const res = yield api_json('api/logs_har?search=bbcc');
assert.equal(res.body.log.entries.length, 0);
}));
it('search by session', etask._fn(function*(_this){
const res = yield api_json('api/logs_har?search=qwe');
assert_has(res.body.log.entries[0],
{request: {url: 'http://bbc.com'}});
assert.equal(res.body.log.entries.length, 1);
}));
it('search only by session', etask._fn(function*(_this){
const res = yield api_json('api/logs_har?search=test_user');
assert.equal(res.body.log.entries.length, 0);
}));
});
describe('add_wip', ()=>{
it('forbidden when token is not set',
etask._fn(function*(_this){
app = yield app_with_config({config: {}});
const res = yield api_json('api/add_wip', {
method: 'POST',
headers: {Authorization: 'aaa'},
});
assert.equal(res.statusMessage, 'Forbidden');
assert.equal(res.statusCode, 403);
}));
it('forbidden when token is not correct',
etask._fn(function*(_this){
const config = {_defaults: {token_auth: 'aaa'}};
app = yield app_with_config({config});
const res = yield api_json('api/add_wip', {method: 'POST'});
assert.equal(res.statusMessage, 'Forbidden');
assert.equal(res.statusCode, 403);
}));
it('bad requests if no IP is passed', etask._fn(function*(_this){
const config = {_defaults: {token_auth: 'aaa'}};
app = yield app_with_config({config});
const res = yield api_json('api/add_wip', {
method: 'POST',
headers: {Authorization: 'aaa'},
});
assert.equal(res.statusMessage, 'Bad Request');
assert.equal(res.statusCode, 400);
}));
it('adds IP without a mask', etask._fn(function*(_this){
const config = {_defaults: {token_auth: 'aaa'}};
app = yield app_with_config({config});
const res = yield api_json('api/add_wip', {
method: 'POST',
headers: {Authorization: 'aaa'},
body: {ip: '1.1.1.1'},
});
assert.equal(res.statusCode, 200);
assert.equal(app.manager._defaults.whitelist_ips.length, 1);
assert.equal(app.manager._defaults.whitelist_ips[0],
'1.1.1.1');
}));
it('adds IP with a mask', etask._fn(function*(_this){
const config = {_defaults: {token_auth: 'aaa'}};
app = yield app_with_config({config});
const res = yield api_json('api/add_wip', {
method: 'POST',
headers: {Authorization: 'aaa'},
body: {ip: '1.1.1.1/20'},
});
assert.equal(res.statusCode, 200);
assert.equal(app.manager._defaults.whitelist_ips.length, 1);
assert.equal(app.manager._defaults.whitelist_ips[0],
'1.1.0.0/20');
}));
});
describe('open browser with custom opts', ()=>{
let launch_stub, open_stub;
beforeEach(()=>{
launch_stub = sinon.stub(puppeteer, 'launch', ()=>null);
open_stub = sinon.stub(puppeteer, 'open_page', ()=>null);
});
afterEach(()=>{
[launch_stub, open_stub].forEach(stub=>sinon.restore(stub));
});
const t = (name, opt, arg, expected)=>it(name, etask._fn(
function*(_this){
_this.timeout(6000);
app = yield app_with_proxies([Object.assign({port: 24000},
opt)]);
yield api_json('api/browser/24000');
const [[, , {[arg]: target_arg}]] = open_stub.args;
assert.deepEqual(target_arg, expected);
}));
t('country is defined and timezone is auto',
{country: 'as', timezone: 'auto'}, 'timezone',
'Pacific/Pago_Pago');
t('country is defined and timezone is defined',
{country: 'us', timezone: 'Asia/Tokyo'}, 'timezone',
'Asia/Tokyo');
t('country is defined and timezone is disabled',
{country: 'ca'}, 'timezone', undefined);
t('country is any and timezone is defined',
{timezone: 'America/Sao_Paulo'}, 'timezone',
'America/Sao_Paulo');
t('with custom resolution', {resolution: '800x600'},
'resolution', {width: 800, height: 600});
t('webrtc is enabled', {webrtc: true}, 'webrtc', true);
});
});
describe('proxy_update', ()=>{
it('return value format is consistent', etask._fn(function*(_this){
const [p1, p2] = [{port: 24000}, {port: 24001}];
app = yield app_with_proxies([p1, p2]);
const recreate = yield app.manager.proxy_update(p1, {port: 24500});
const in_place = yield app.manager.proxy_update(p2,
{port: 24001, ssl: true});
[recreate, in_place].forEach(res=>{
assert.equal(Object.keys(res).length, 1);
assert.ok(res.proxy_port);
});
}));
});
describe('flags', ()=>{
it('exits immediately with version on -v', etask._fn(function*(_this){
const exec = require('child_process').execFile;
exec('node', ['./bin/index.js', '--version'], (err, res)=>{
this.continue();
assert.equal(res, pkg.version+'\n');
});
yield this.wait();
}));
});
describe('banlist propagation on proxy changes', ()=>{
let port, to_date, new_banlist;
beforeEach(()=>etask(function*(){
const now_stub = sinon.stub(Date, 'now').returns(5000);
app = yield app_with_proxies([{port: 24000}]);
port = app.manager.proxy_ports[24000];
const {banlist} = port;
const ms_left = 100;
to_date = Date.now()+ms_left;
const new_proxy = Object.assign({}, {port: 24000}, {});
banlist.add('1.1.1.1', ms_left);
banlist.add('1.1.1.1', ms_left, 'lumtest.com');
const update_spy = sinon.spy();
port.on('updated', update_spy);
yield app.manager.proxy_update({port: 24000}, new_proxy);
sinon.assert.calledOnce(update_spy);
new_banlist = app.manager.proxy_ports[24000].banlist.cache;
now_stub.restore();
}));
it('sends updated banlist instance to workers via ipc', ()=>{
assert.equal(new_banlist.size, 2);
assert.equal(new_banlist.get('1.1.1.1').to_date, to_date);
const stub_worker = {on: ()=>null, send: sinon.spy()};
port.setup_worker(stub_worker);
const worker_send_spy = stub_worker.send;
const serialized_banlist = Object.keys(
worker_send_spy.args[0][0].opt.banlist);
assert.ok(worker_send_spy.called);
assert.equal(serialized_banlist.length, 2);
});
it('deletes expired banned ips using timeouts', ()=>etask(function*(){
const timeouts_exist = [...new_banlist.values()].every(({to={}})=>
to._destroyed===false);
assert.ok(timeouts_exist);
yield etask.sleep(500);
assert.ok(!new_banlist.size);
}));
it('doesnt delete indefinitely banned ips', ()=>etask(function*(){
let {banlist} = port;
banlist.clear();
banlist.add('1.2.3.4', 0);
banlist.add('1.2.3.4', 0, 'lumtest.com');
const new_proxy = Object.assign({port: 24000});
yield app.manager.proxy_update({port: 24000}, new_proxy);
({banlist} = app.manager.proxy_ports[24000]);
assert.ok(banlist.cache.size==2);
}));
});
describe('whitelisting', ()=>{
let t = (name, proxies, default_calls, wh, cli, www_whitelist)=>
it(name, etask._fn(function*(_this){
_this.timeout(6000);
const port = proxies[0].port;
app = yield app_with_proxies(proxies, cli);
app.manager.set_www_whitelist_ips(www_whitelist||[]);
for (const c of default_calls)
app.manager.set_whitelist_ips(c);
const {whitelist_ips} = app.manager.proxy_ports[port].opt;
assert.deepEqual(whitelist_ips, wh);
const res = yield make_user_req();
const whitelists = res.body.response.headers.find(
h=>h.name=='x-lpm-whitelist');
assert.ok(!!whitelists);
assert.equal(whitelists.value, wh.join(' '));
}));
const p = [{port: 24000}];
const p_w = [{port: 24000, whitelist_ips: ['1.1.1.1']}];
const p_wi = [{port: 24000, whitelist_ips: ['1.1.1.1', '300/40']}];
const w_cli = {whitelist_ips: ['1.2.3.4', '4.3.2.1']};
t('invalid ips in config', p_wi, [], ['1.1.1.1']);
t('sets from cmd', p, [], ['1.2.3.4', '4.3.2.1'], w_cli);
t('sets from www', p, [], ['1.1.1.1'], null, ['1.1.1.1']);
t('sets default', p, [['2.2.2.2']], ['2.2.2.2']);
t('sets specific', p_w, [], ['1.1.1.1']);
t('sets cmd and default', p, [['2.2.2.2']],
['1.2.3.4', '4.3.2.1', '2.2.2.2'], w_cli);
t('sets cmd and specific', p_w, [], ['1.2.3.4', '4.3.2.1', '1.1.1.1'],
w_cli);
t('sets cmd and www', p, [], ['1.2.3.4', '4.3.2.1', '1.1.1.1'],
w_cli, ['1.1.1.1']);
t('sets default and specific', p_w, [['2.2.2.2']],
['2.2.2.2', '1.1.1.1']);
t('sets default and www', p, [['2.2.2.2']],
['1.1.1.1', '2.2.2.2'], null, ['1.1.1.1']);
t('sets cmd, default and specific', p_w, [['2.2.2.2']],
['1.2.3.4', '4.3.2.1', '2.2.2.2', '1.1.1.1'], w_cli);
t('sets cmd, default, www and specific', p_w, [['2.2.2.2']],
['1.2.3.4', '4.3.2.1', '10.1.1.1', '2.2.2.2', '1.1.1.1'], w_cli,
['10.1.1.1']);
t('removes IPs from proxy port config when removed in default ', p_w,
[['2.2.2.2', '3.3.3.3'], []], ['1.2.3.4', '4.3.2.1', '1.1.1.1'],
w_cli);
it('updates proxy', ()=>etask(function*(){
app = yield app_with_proxies(p);
const whitelist_ips = ['1.1.1.1', '2.2.2.2', '3.0.0.0/8'];
const new_proxy = Object.assign({}, p[0], {whitelist_ips});
const {proxy_port} = yield app.manager.proxy_update(p[0],
new_proxy);
assert.deepEqual(proxy_port.whitelist_ips, whitelist_ips);
}));
t = (name, def, default_calls, expected)=>
it(name, etask._fn(function*(_this){
const proxies = [{port: 24000, whitelist_ips:
w_cli.whitelist_ips.concat(def).concat(expected)}];
app = yield app_with_proxies(proxies, w_cli);
const m = app.manager;
default_calls.forEach(d=>m.set_whitelist_ips(d));
const s = m.config._serialize(m.proxies, m._defaults);
const config = JSON.parse(s);
const proxy = config.proxies[0];
assert.equal(proxy.port, proxies[0].port);
assert.deepEqual(proxy.whitelist_ips, expected);
}));
const def = ['3.3.3.3', '4.4.4.4'];
t('should not save default/cmd whitelist', def, [def], ['7.8.9.10']);
t('should rm from port after rm from default/cmd whitelist', def,
[def, def.slice(1)], ['7.8.9.10']);
});
describe('pool ips', ()=>{
let t = (name, proxies, expected)=>it(name, etask._fn(function*(_this){
_this.timeout(6000);
app = yield app_with_proxies(proxies);
let data = {port: 24000, ip: '1.1.1.1'};
app.manager.proxy_ports[24000].emit('add_static_ip', data);
sinon.assert.match(app.manager.proxies,
[sinon.match(expected)]);
}));
t('add ip', [{port: 24000, pool_size: 1}],
{port: 24000, ips: ['1.1.1.1']});
t('overloading', [{port: 24000, pool_size: 1, ips: ['2.2.2.2']}],
{port: 24000, ips: ['2.2.2.2']});
});
describe('refresh_ip', ()=>{
it('refreshes ip & sessions and updates proxy port', etask._fn(
function*(_this){
const alloc_ip = '1.1.1.1', alloc_inet_addr = 16843009;
const new_ip = '2.2.2.2';
const proxy = {port: 24000, zone: 'abc', ips: [alloc_ip]};
app = yield app_with_proxies([proxy]);
const mgr = app.manager;
sb.stub(mgr, 'request_allocated_ips').returns({ips: [alloc_ip]});
const refresh_ips = sb.stub(mgr, 'refresh_ips')
.returns({ips: [{ip: new_ip}]});
const proxy_port = mgr.proxy_ports[proxy.port];
const proxy_conf = mgr.proxies.find(p=>p.port==proxy.port);
const refresh_sessions = sb.spy(proxy_port, 'refresh_sessions');
const expected_refresh_args = [proxy.zone, {ips: alloc_inet_addr}];
yield mgr.refresh_ip(alloc_ip, null, proxy.port);
assert.ok(refresh_ips.calledWithExactly(...expected_refresh_args));
assert.ok(refresh_sessions.called);
assert.deepEqual(proxy_port.opt.ips, [new_ip]);
assert.deepEqual(proxy_conf.ips, [new_ip]);
}));
});
describe('lpm_f', function(){
this.timeout(6*SEC);
const server_meta_conf = {config: {_defaults: {
customer: 'abc',
account_id: 'abc',
}}};
const get_local_conf = ts=>({
_defaults: {
lpm_token: '123|abc',
customer: 'abc',
sync_config: true,
},
proxies: [{port: 24000}],
ts,
});
const get_server_conf = ts=>({
_defaults: {log: 'debug'},
proxies: [{port: 25000}, {port: 25001}],
ts,
});
const spy_obj_methods = (obj, ...methods)=>
Object.fromEntries(methods.map(m=>[m, sb.spy(obj, m)]));
describe('config on server is newer than local config', ()=>{
let local_conf, server_conf;
beforeEach(()=>{
local_conf = get_local_conf(date.add(date(), {day: -1}));
server_conf = get_server_conf(date());
});
afterEach(()=>local_conf = server_conf = null);
it('starts with valid lpm_token and customer', etask.fn(
function*(){
app = yield app_with_config({config: local_conf,
start_manager: false});
const mgr = app.manager;
sb.stub(mgr.lpm_f, 'init');
sb.stub(mgr.lpm_f, 'get_conf').returns(server_conf);
sb.stub(mgr.lpm_f, 'get_meta_conf').returns(server_meta_conf);
const mgr_methods = qw`logged_update apply_cloud_config perr`;
const mgr_spies = spy_obj_methods(mgr, ...mgr_methods);
const {logged_update, apply_cloud_config, perr} = mgr_spies;
yield mgr.start();
sinon.assert.calledWith(perr, 'start_success');
sinon.assert.calledWithExactly(apply_cloud_config,
server_conf);
const logged_in = logged_update.returnValues[0].retval;
assert.strictEqual(logged_in, true);
assert.equal(+mgr.config_ts, +server_conf.ts);
assert_has(mgr, {
_defaults: server_conf._defaults,
proxies: server_conf.proxies,
}, 'app.manager');
}));
it('starts with invalid lpm_token', etask.fn(function*(){
app = yield app_with_config({config: local_conf,
start_manager: false});
const mgr = app.manager;
sb.stub(mgr.lpm_f, 'init');
sb.stub(mgr.lpm_f, 'get_conf').returns(false);
const get_meta_conf = sb.stub(mgr.lpm_f, 'get_meta_conf');
get_meta_conf.onCall(0).throws(new Error('not_authorized'));
get_meta_conf.onCall(1).returns(server_meta_conf);
const mgr_methods = qw`apply_cloud_config logged_update`;
const {apply_cloud_config, logged_update} =
spy_obj_methods(mgr, ...mgr_methods);
yield mgr.start();
const logged_in = logged_update.returnValues[0].retval;
assert.strictEqual(logged_in, false);
assert.strictEqual(mgr._defaults.lpm_token, undefined);
sinon.assert.notCalled(apply_cloud_config);
assert_has(mgr, {
_defaults: zutil.omit(local_conf._defaults, 'lpm_token'),
proxies: local_conf.proxies,
}, 'app.manager');
sb.stub(mgr.lpm_f, 'login').returns(server_conf);
sb.stub(mgr, 'login_user').returns('123|abc');
yield api_json('api/creds_user', {method: 'post', body: {
customer: 'abc',
token: '123',
}});
sinon.assert.calledWithExactly(apply_cloud_config,
server_conf);
const logged_in_2 = logged_update.returnValues[1].retval;
assert.strictEqual(logged_in_2, true);
assert_has(mgr, {
_defaults: server_conf._defaults,
proxies: server_conf.proxies,
}, 'app.manager');
}));
});
describe('local config is newer than config on the server', ()=>{
let local_conf, server_conf;
beforeEach(()=>{
local_conf = get_local_conf(date());
server_conf = get_server_conf(date.add(date(), {day: -1}));
});
afterEach(()=>local_conf = server_conf = null);
it('starts with valid lpm_token and customer', etask.fn(
function*(){
app = yield app_with_config({config: local_conf,
start_manager: false});
const mgr = app.manager;
sb.stub(mgr.lpm_f, 'init');
sb.stub(mgr.lpm_f, 'get_conf').returns(server_conf);
sb.stub(mgr.lpm_f, 'get_meta_conf').returns(server_meta_conf);
const mgr_methods = qw`logged_update apply_cloud_config perr`;
const mgr_spies = spy_obj_methods(mgr, ...mgr_methods);
const {logged_update, apply_cloud_config, perr} = mgr_spies;
yield mgr.start();
sinon.assert.calledWith(perr, 'start_success');
sinon.assert.calledWithExactly(apply_cloud_config,
server_conf);
const logged_in = logged_update.returnValues[0].retval;
assert.strictEqual(logged_in, true);
assert_has(mgr, {
_defaults: local_conf._defaults,
proxies: local_conf.proxies,
}, 'app.manager');
}));
it('synchronizes configs with the server after local change',
etask.fn(function*(){
app = yield app_with_config({config: local_conf,
start_manager: false});
const mgr = app.manager;
sb.stub(mgr.lpm_f, 'init');
sb.stub(mgr.lpm_f, 'get_conf').returns(server_conf);
sb.stub(mgr.lpm_f, 'get_meta_conf').returns(server_meta_conf);
yield mgr.start();
assert.deepEqual(date(mgr.config_ts), date(local_conf.ts));
let local_update_ts;
sb.stub(mgr.lpm_f, 'update_conf', ({_defaults, ts})=>{
local_update_ts = date(ts);
zutil.extend_deep(server_conf, {_defaults, ts});
});
yield json('api/settings', 'put', {har_limit: 123});
assert.deepEqual(local_update_ts, mgr.config_ts);
let new_server_ts;
zutil.extend_deep(server_conf, {
_defaults: {logs: 456},
ts: new_server_ts = date.add(date(server_conf.ts), {s: 1}),
});
yield mgr.apply_cloud_config(server_conf);
const expected_defaults = {har_limit: 123, logs: 456};
assert_has(mgr, {_defaults: expected_defaults}, 'app.manager');
assert.deepEqual(mgr.config_ts, new_server_ts);
}));
it('starts with invalid lpm_token', etask.fn(function*(){
app = yield app_with_config({config: local_conf,
start_manager: false});
const mgr = app.manager;
sb.stub(mgr.lpm_f, 'init');
sb.stub(mgr.lpm_f, 'get_conf').returns(false);
const get_meta_conf = sb.stub(mgr.lpm_f, 'get_meta_conf');
get_meta_conf.onCall(0).throws(new Error('not_authorized'));
get_meta_conf.onCall(1).returns(server_meta_conf);
const mgr_methods = qw`apply_cloud_config logged_update`;
const {apply_cloud_config, logged_update} =
spy_obj_methods(mgr, ...mgr_methods);
yield mgr.start();
const logged_in = mgr.logged_update.returnValues[0].retval;
assert.strictEqual(logged_in, false);
assert.strictEqual(mgr._defaults.lpm_token, undefined);
sinon.assert.notCalled(apply_cloud_config);
assert_has(mgr, {
_defaults: zutil.omit(local_conf._defaults, 'lpm_token'),
proxies: local_conf.proxies,
}, 'app.manager');
sb.stub(mgr.lpm_f, 'login').returns(server_conf);
sb.stub(mgr, 'login_user').returns('123|abc');
yield api_json('api/creds_user', {method: 'post', body: {
customer: 'abc',
token: '123',
}});
sinon.assert.calledWithExactly(mgr.apply_cloud_config,
server_conf);
const logged_in_2 = logged_update.returnValues[1].retval;
assert.strictEqual(logged_in_2, true);
assert_has(mgr, {
_defaults: local_conf._defaults,
proxies: local_conf.proxies,
}, 'app.manager');
}));
});
});
xdescribe('migrating', ()=>{
beforeEach(()=>{
logger_stub.reset();
});
const t = (name, should_run_migrations, config={}, cli={})=>
it(name, etask._fn(function*(_this){
const notice = 'NOTICE: Migrating config file 1.116.387';
const first_migration_match = sinon.match(notice);
app = yield app_with_config({config, cli});
if (should_run_migrations)
sinon.assert.calledWith(logger_stub, first_migration_match);
else
{
sinon.assert.neverCalledWith(logger_stub,
first_migration_match);
}
}));
t('should run migrations if config file exists and version is old',
true, {proxies: [{}]});
t('should not run migrations if --no-config flag is passed',
false, {proxies: [{}]}, {'no-config': true});
t('should not run migrations if config does not exist', false);
t('should not run migrations if config exists and version is new',
false, {_defaults: {version: '1.120.0'}});
});
describe('stop', ()=>{
afterEach(()=>{ app = null; });
let t = name=>it(name, etask._fn(function*(_this){
const proxies = [{port: 24000, mu