Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加代理认证模块 #137

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ coverage
build/Release
node_modules
.lock-wscript
temp
temp
tags
auth.db
68 changes: 66 additions & 2 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ program
.option('-t, --type [value]', 'http|https, http for default')
.option('-p, --port [value]', 'proxy port, 8001 for default')
.option('-w, --web [value]' , 'web GUI port, 8002 for default')
.option('-I, --no-webinterface', 'disable WebInterface')
.option('-P, --no-persistence', 'disable data persistence, will also disable WebInterface')
.option('-f, --file [value]', 'save request data to a specified file, will use in-memory db if not specified')
.option('-r, --rule [value]', 'path for rule file,')
.option('-g, --root [value]', 'generate root CA')
Expand All @@ -23,9 +25,16 @@ program
.option('-s, --silent', 'do not print anything into terminal')
.option('-c, --clear', 'clear all the tmp certificates')
.option('-o, --global', 'set as global proxy for system')
.option('-A, --auth', 'enable proxy authorization')
.option('-a, --adduser <user> <password> [<user> <password>]...', 'add proxy user')
.option('-m, --moduser <user> <password>', 'modify proxy user password')
.option('-d, --deluser <user> [user]...', 'delete proxy user')
.option('-F, --authFile [value]', 'save proxy auth data to a specified file, if not specified use __dirname/auth.db')
.option('install', '[alpha] install node modules')
.parse(process.argv);

var authFile = program.authFile ? path.resolve(process.cwd(), program.authFile) : path.resolve(__dirname, 'auth.db');

if(program.clear){
require("./lib/certMgr").clearCerts(function(){
console.log( color.green("all certs cleared") );
Expand All @@ -46,6 +55,58 @@ if(program.clear){
});
npm.registry.log.on("log", function (message) {});
});
}else if (program.adduser || program.deluser || program.moduser){
var db, Datastore = require('nedb'),
args = program.args,
users = [];

try {
db = new Datastore({filename: authFile, autoload: true});
db.ensureIndex({ fieldName: 'username', unique: true});
db.persistence.setAutocompactionInterval(5001);

console.log("proxy auth file : " + authFile);
} catch (e) {
console.log('create proxy auth database file failed, ' + e);
process.exit(-1);
}

if (program.adduser) {
args.unshift(program.adduser);

if (args.length % 2 !== 0) {
console.log('add user failed, every user must be set a password.');
process.exit(-1);
}

for (var i = 0; i < args.length; i += 2) {
users.push({username: args[i], password: args[i + 1]});
}

db.insert(users, function (err) {
err && console.log('create proxy user failed: ' + err.message);
});
} else if (program.deluser) {
args.push(program.deluser);

args.forEach(function (user) {
db.remove({username: user}, {}, function (err) {
err && console.log('delete user failed: ' + err.message);
});
});
} else {
if (args.length <= 0) {
console.log('user password must be set.');
process.exit(-1);
}

db.update({username: program.moduser}, {username: program.moduser, password: args[0]}, {upsert: true}, function (err) {
err && console.log('modify proxy user failed: ' + err.message);
});
}

// db.persistence.compactDatafile();
db.persistence.stopAutocompaction();
}else{
var proxy = require("./proxy.js");
var ruleModule;
Expand Down Expand Up @@ -91,9 +152,12 @@ if(program.clear){
throttle : program.throttle,
webPort : program.web,
rule : ruleModule,
disableWebInterface : false,
disableWebInterface : !program.persistence || !program.webinterface,
disablePersistence : !program.persistence,
setAsGlobalProxy : program.global,
interceptHttps : program.intercept,
silent : program.silent
silent : program.silent,
auth : program.auth,
authFile : authFile
});
}
78 changes: 69 additions & 9 deletions lib/requestHandler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var http = require("http"),
https = require("https"),
https = require("https"),
net = require("net"),
fs = require("fs"),
url = require("url"),
Expand Down Expand Up @@ -49,26 +49,26 @@ function userRequestHandler(req,userRes){
req : req,
startTime : new Date().getTime()
};
if(global.recorder){
resourceInfoId = global.recorder.appendRecord(resourceInfo);
}
resourceInfoId = global.recorder ? global.recorder.appendRecord(resourceInfo) : -1;

logUtil.printLog(color.green("\nreceived request to : " + host + path));

//get request body and route to local or remote
async.series([
function (callback) {
auth(callback, req, userRes, resourceInfo, resourceInfoId, protocol);
},
fetchReqData,
routeReq
],function(){
//mark some ext info
if(req.anyproxy_map_local){
global.recorder.updateExtInfo(resourceInfoId, {map : req.anyproxy_map_local});
}
req.anyproxy_map_local && global.recorder && global.recorder.updateExtInfo(resourceInfoId, {map : req.anyproxy_map_local});
});

//get request body
function fetchReqData(callback){
var postData = [];

req.on("data",function(chunk){
postData.push(chunk);
});
Expand Down Expand Up @@ -297,15 +297,17 @@ function connectReqHandler(req, socket, head){
req : req,
startTime : new Date().getTime()
};
resourceInfoId = global.recorder.appendRecord(resourceInfo);
resourceInfoId = global.recorder ? global.recorder.appendRecord(resourceInfo) : -1;

var proxyPort,
proxyHost,
internalHttpsPort,
httpsServerMgrInstance;

async.series([

function (callback) {
auth(callback, req, socket, resourceInfo, resourceInfoId, 'http');
},
//check if internal https server exists
function(callback){
if(!shouldIntercept){
Expand Down Expand Up @@ -428,6 +430,64 @@ function setRules(newRule){
}
}

// proxy authorization
function auth(cb, req, res, info, id, protocol) {
if (protocol === 'https' || !global.auth) {
return cb();
}

var index, header = req.headers["proxy-authorization"],
end = function (code) {
info.endTime = new Date().getTime();
info.statusCode = code;
info.resHeader = {};
info.resBody = "";
info.length = 0;

global.recorder && global.recorder.updateRecord(id, info);

if (res.writeHead) {
res.removeHeader('Date');
res.writeHead(code, {"Connection": "closed", "Content-Length": 0});
} else {
res.write('HTTP/' + req.httpVersion + ' ' + code + ' '
+ (code === 401 ? 'Unauthorized' : 'Proxy Authentication Required')
+ '\r\n\r\n', 'UTF-8');
}

res.end();
return false;
};

if (!header) {
return end(407);
}

if (!header.startsWith("Basic ")) {
return end(401);
}

header = Buffer.from(header.split(" ", 2)[1], "base64");

if (header.length <= 0) {
return end(401);
}

header = header.toString("ascii");
index = header.indexOf(":");

if (index === -1) {
return end(401);
}

global.auth.findOne({username: header.slice(0, index++), password: header.slice(index)}, function (err, doc) {
if (err || doc === null) {
return end(401);
}
cb();
});
}

function getRuleSummary(){
return userRule.summary();
}
Expand Down
30 changes: 22 additions & 8 deletions proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ var requestHandler = util.freshRequire('./requestHandler');
//option.disableWebInterface
//option.silent : false(default)
//option.interceptHttps ,internal param for https
//option.auth : false(default)
//option.authFile : __dirname/auth.db(default)
function proxyServer(option){
option = option || {};

Expand All @@ -63,13 +65,21 @@ function proxyServer(option){
proxyWebPort = option.webPort || DEFAULT_WEB_PORT, //port for web interface
socketPort = option.socketPort || DEFAULT_WEBSOCKET_PORT, //port for websocket
proxyConfigPort = option.webConfigPort || DEFAULT_CONFIG_PORT, //port to ui config server
disableWebInterface = !!option.disableWebInterface,
disableWebInterface = !!option.disablePersistence || !!option.disableWebInterface,
disablePersistence = !!option.disablePersistence,
ifSilent = !!option.silent;

if(ifSilent){
logUtil.setPrintStatus(false);
}

if (option.auth) {
var Datastore = require('nedb');
global.auth = new Datastore({filename: option.authFile, autoload: true});
global.auth.persistence.setAutocompactionInterval(5001);
logUtil.printLog('proxy auth file loaded : ' + option.authFile);
}

// copy the rule to keep the original proxyRules independent
proxyRules = Object.assign({}, proxyRules);

Expand Down Expand Up @@ -110,16 +120,18 @@ function proxyServer(option){
//clear cache dir, prepare recorder
function(callback){
util.clearCacheDir(function(){
if(option.dbFile){
global.recorder = new Recorder({filename: option.dbFile});
}else{
global.recorder = new Recorder();
if (!option.disablePersistence) {
if(option.dbFile){
global.recorder = new Recorder({filename: option.dbFile});
}else{
global.recorder = new Recorder();
}
}
callback();
});
},

//creat proxy server
//create proxy server
function(callback){
if(proxyType == T_TYPE_HTTPS){
certMgr.getCertificate(proxyHost,function(err,keyContent,crtContent){
Expand Down Expand Up @@ -153,8 +165,10 @@ function proxyServer(option){

//start web socket service
function(callback){
self.ws = new wsServer({port : socketPort});
callback(null);
if (!disableWebInterface) {
self.ws = new wsServer({port : socketPort});
callback(null);
}
},

//start web interface
Expand Down
11 changes: 11 additions & 0 deletions rule_sample/rule_remove_proxy_auth_header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//rule scheme :

module.exports = {
replaceRequestOption : function(req,option){
var newOption = option;
delete newOption.headers['proxy-authorization'];
delete newOption.headers['proxy-connection'];

return newOption;
}
};
1 change: 1 addition & 0 deletions test/data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!*
2 changes: 2 additions & 0 deletions test/data/auth.db
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"username":"chopin","password":"ngo","_id":"WkKABWbVTmecX1Ee"}
{"$$indexCreated":{"fieldName":"username","unique":true,"sparse":false}}
Loading