Add support of encryption and HTTP Auth
This commit is contained in:
parent
57e8cd39df
commit
4392035512
@ -4,5 +4,15 @@ module.exports = class Component {
|
||||
this.syntax = syntaxTree;
|
||||
}
|
||||
|
||||
// Run server
|
||||
run () {}
|
||||
|
||||
// Working with domains
|
||||
domainAction (context, domain) {}
|
||||
|
||||
// Call this function where need works with domain (DO NOT REWRITE)
|
||||
callDomain (domain, domainST) {
|
||||
const component = new domainST.Type(this.adr, domainST);
|
||||
return { argv: (...argv) => component.domainAction(this, domain, ...argv) };
|
||||
}
|
||||
};
|
||||
|
@ -1,22 +1,82 @@
|
||||
const Component = require("./_component");
|
||||
|
||||
const https = require("https");
|
||||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const bodyHand = require('body');
|
||||
const url = require('url');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
function logger (...p) {
|
||||
return;
|
||||
//return;
|
||||
console.log("[DEBUG.main]:", ...p);
|
||||
}
|
||||
|
||||
function auth (self, req, res, next) {
|
||||
if (self.syntax.auth) {
|
||||
const ACCESS = "Basic " + Buffer.from(`${self.syntax.auth.login}:${self.syntax.auth.password}`).toString('base64');
|
||||
if (req.headers['authorization'] === ACCESS) {
|
||||
return next();
|
||||
}
|
||||
else {
|
||||
if (self.syntax.auth.mode === "strict" && req.headers['authorization'] !== undefined) {
|
||||
return res.status(403).send("<h1>Permission denied</h1><hr/><p>Permission denied.</p>");
|
||||
}
|
||||
res.set("WWW-Authenticate", "Basic");
|
||||
return res.status(401).send();
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function fetchProxy (req, res, reverseTo) {
|
||||
const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20, compress: false };
|
||||
delete params.headers.host;
|
||||
delete params.headers.referer;
|
||||
if (!["GET", "HEAD"].includes(params.method))
|
||||
params.body = req.body;
|
||||
|
||||
const reqAddress = url.resolve(reverseTo, path.join("/", req.originalUrl));
|
||||
fetch(reqAddress, params)
|
||||
.then(async result => {
|
||||
const content = await result.arrayBuffer();
|
||||
//logger("result:", result);
|
||||
//logger("result.arrayBuffer():", content);
|
||||
//logger("result.Buffer:", Buffer.from(content).toString());
|
||||
|
||||
const headers = Object.fromEntries(result.headers);
|
||||
|
||||
//logger("headers Headers", result.headers);
|
||||
//logger("headers Headers.raw()", result.headers.raw());
|
||||
logger("headers object", headers);
|
||||
|
||||
Object.entries(headers).forEach(([header, value]) => {
|
||||
res.set(header, value);
|
||||
});
|
||||
|
||||
const finalContent = Buffer.from(content);
|
||||
res.status(result.status).send(finalContent);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
res.status(500).send("<head><title>Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = class HTTPReverse extends Component {
|
||||
constructor (adr, syntaxTree) {
|
||||
super(adr, syntaxTree);
|
||||
}
|
||||
|
||||
domainAction (context, domain, req, res) {
|
||||
logger("DOMAIN ACT:", this.syntax);
|
||||
if (!(context instanceof HTTPReverse))
|
||||
throw new SyntaxError("You can't call revHTTP domain from unsupported type. Supported types: revHTTP, simpleHTTP, redirectHTTP");
|
||||
fetchProxy(req, res, this.syntax.target);
|
||||
}
|
||||
|
||||
run () {
|
||||
this.app = express();
|
||||
|
||||
@ -38,39 +98,32 @@ module.exports = class HTTPReverse extends Component {
|
||||
});
|
||||
});
|
||||
|
||||
this.app.use((req, res, next) => auth(this, req, res, next));
|
||||
|
||||
this.app.use("*", async (req, res) => {
|
||||
logger("REQ HEADERS:>", req.headers);
|
||||
logger("originalUrl:>", req.originalUrl);
|
||||
const currentDomain = req.hostname;
|
||||
const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20 };
|
||||
delete params.headers.host;
|
||||
delete params.headers.referer;
|
||||
if (!["GET", "HEAD"].includes(params.method))
|
||||
params.body = req.body;
|
||||
logger("hostname:", currentDomain);
|
||||
const reverseToLoc = !this.syntax.domains[currentDomain] ? reverseTo : this.syntax.domains[currentDomain].target;
|
||||
const reqAddress = url.resolve(reverseToLoc, path.join("/", req.originalUrl));
|
||||
fetch(reqAddress, params)
|
||||
.then(async result => {
|
||||
const content = await result.text();
|
||||
logger("result:", result);
|
||||
logger("result.text():", content);
|
||||
Object.entries(result?.headers ? result.headers : new Object()).forEach(([header, value]) => {
|
||||
res.set(header, value);
|
||||
});
|
||||
res.status(result.status).send(content);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
res.status(500).send("<head><title>Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>");
|
||||
});
|
||||
const isNotDomained = !this.syntax.domains[currentDomain];
|
||||
if (!isNotDomained) {
|
||||
return this
|
||||
.callDomain(currentDomain, this.syntax.domains[currentDomain])
|
||||
.argv(req, res);
|
||||
}
|
||||
return fetchProxy(req, res, reverseTo);
|
||||
});
|
||||
|
||||
this.app.listen(this.adr.port, address, (err) => {
|
||||
const server = !this.syntax.encryption ? this.app : https.createServer({
|
||||
cert : fs.readFileSync(this.syntax.encryption.public, 'utf-8'),
|
||||
key : fs.readFileSync(this.syntax.encryption.private, 'utf-8')
|
||||
}, this.app);
|
||||
|
||||
server.listen(this.adr.port, address, (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log(`Koboldcpp reverse proxy server listened at ${
|
||||
console.log(`HTTP reverse proxy server listened at ${
|
||||
address
|
||||
}:${this.adr.port}`);
|
||||
})
|
||||
|
@ -1,3 +1,5 @@
|
||||
module.exports = {
|
||||
"revHTTP": require("./http-reverse"),
|
||||
"simpleHTTP": require("./http-reverse"),
|
||||
"redirectHTTP": require("./http-reverse"),
|
||||
};
|
||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -12,7 +12,8 @@
|
||||
"body": "^5.1.0",
|
||||
"body-parser": "^1.20.3",
|
||||
"express": "^4.21.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
"node-fetch": "^2.6.7",
|
||||
"sse": "^0.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
@ -816,6 +817,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/options": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
|
||||
"integrity": "sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@ -1083,6 +1092,17 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sse": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/sse/-/sse-0.0.8.tgz",
|
||||
"integrity": "sha512-cviG7JH31TUhZeaEVhac3zTzA+2FwA7qvHziAHpb7mC7RNVJ/RbHN+6LIGsS2ugP4o2H15DWmrSMK+91CboIcg==",
|
||||
"dependencies": {
|
||||
"options": "0.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
|
@ -23,7 +23,8 @@
|
||||
"body": "^5.1.0",
|
||||
"body-parser": "^1.20.3",
|
||||
"express": "^4.21.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
"node-fetch": "^2.6.7",
|
||||
"sse": "^0.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
|
@ -6,14 +6,14 @@ const components = require("./components");
|
||||
// const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
||||
const DOMAINER_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
||||
const DOMAINER_VALUE_FIND_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{/gmi;
|
||||
const ADDRESS_WITH_PORT_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}/gmi
|
||||
const ADDRESS_WITH_PORT_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}/mi;
|
||||
const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{([\s\n\r0-9a-z;:\/\."',]|domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}){0,}\}/gmi;
|
||||
const COMMENT_REGEX = /#.{0,}(\n|\r|$)/gmi;
|
||||
const CONTENT_INTO_CURLY_BRACES = /\{[\s\n\r0-9a-z;:\/\."',]{0,}\}{0,}\}/gmi;
|
||||
|
||||
// Instruction regexps
|
||||
function readInstructions (instructions, fullCode, forDomains=false) {
|
||||
const syntaxTree = { firewall: false };
|
||||
const syntaxTree = { firewall: false, encryption: {} };
|
||||
|
||||
for (let instruction of instructions) {
|
||||
if (!instruction) {
|
||||
@ -39,6 +39,22 @@ function readInstructions (instructions, fullCode, forDomains=false) {
|
||||
syntaxTree.target = instructionParts[1];
|
||||
break;
|
||||
case "cert":
|
||||
if (instructionParts.length < 3)
|
||||
throw new SyntaxError("Invalid syntax of operator `cert`: cert <type:(public|private)> <path_to_cert>");
|
||||
if (!["public", "private"].includes(instructionParts[1]))
|
||||
throw new SyntaxError("Cert type can be only private or public, not " + instructionParts[1]);
|
||||
syntaxTree.encryption[instructionParts[1]] = instructionParts.slice(2,).join(" ");
|
||||
break;
|
||||
case "auth":
|
||||
if (instructionParts.length < 4 || instructionParts.length < 2)
|
||||
throw new SyntaxError("Invalid syntax of operator `auth`: auth <mode:(loopback|strict)> <login> <password> OR auth null");
|
||||
if (!["loopback", "strict", "null"].includes(instructionParts[1]))
|
||||
throw new SyntaxError("Invalid mode: " + instructionParts[1] + ", valid modes: loopback, strict, null");
|
||||
syntaxTree.auth = instructionParts[1] !== "null" ? {
|
||||
mode: instructionParts[1],
|
||||
login: instructionParts[2],
|
||||
password: instructionParts.slice(3,).join(" ")
|
||||
} : null;
|
||||
break;
|
||||
case "firewall":
|
||||
break;
|
||||
@ -51,6 +67,11 @@ function readInstructions (instructions, fullCode, forDomains=false) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(syntaxTree.encryption).length === 0)
|
||||
syntaxTree.encryption = null;
|
||||
else if (Object.keys(syntaxTree.encryption).length === 1)
|
||||
throw new SyntaxError("No private or public key specified");
|
||||
|
||||
if (!forDomains) {
|
||||
syntaxTree.domains = new Object();
|
||||
const allDomains = Object.fromEntries((fullCode.match(DOMAINER_REGEX) ?? [])
|
||||
|
@ -1,6 +1,5 @@
|
||||
127.0.0.1:9889 {
|
||||
type revHTTP;
|
||||
|
||||
target https://example.org; # RRR
|
||||
domain localhost {
|
||||
firewall whitelist GLOBAL;
|
||||
@ -12,6 +11,7 @@
|
||||
127.0.0.1:7889 {
|
||||
type revHTTP;
|
||||
target https://google.com;
|
||||
cert public /path/to/cert.pub;
|
||||
cert private /path/to/cert.priv;
|
||||
auth loopback admin qwerty;
|
||||
cert public ./tests/pub.crt;
|
||||
cert private ./tests/priv.key;
|
||||
}
|
||||
|
27
tests/priv.key
Normal file
27
tests/priv.key
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2tGTaIEgV/Ar5hF5gL60pUJPYQuQ/8qCrKIB65i2KCTd/EIF
|
||||
TmlcXVF+drSKyfBaPxee8FuuxR9gcw4mzPqYwRrqC1OPfANYEs+VUzBYRdI6uBgU
|
||||
FWNVvWsDqpmHcGWgiZ+g/GRrfGBwN3xqpk+jPkTuXunBztZcvSyGCJnbpgKTbBrd
|
||||
niRT6KX3KyZmwxSqvOQA7+ebUOUSrFY+t1Bf+BXD/QrECJ6RWbddrbFGSPE/QDei
|
||||
8AlOWLVY8SHMtcC/0T4NSonR0yVVJ4NxkUO4P+kvdrqaKkY8UYXV4LLWt80J1Pky
|
||||
YqhGq4g9nNnggpuDt4Xgp4uMI2hsb8H7Hn3JhwIDAQABAoIBAFEsnfqOrGjy99YJ
|
||||
dYGFPY5w4vGlUM0TWxafowa7SBfsVU1XUJCeFV2o9bkjkLkJKPFu2c9gs5z83tLP
|
||||
1SUFcdVxHpHFFd1zgCC6UTLjJFwCeTYB+LFWgGAcaufGvK+9g6iKRzZaKmWhgP+U
|
||||
cBvyPyTCfYtIPFV2wECNW8ulCA61BgC8ecggCLkE3BBCVpz9pTcIKbNH/698EIIJ
|
||||
EnS6VB2FBu0j1UGQ9cYOa5i1u+AW7GTfJxmO6zcdh5aNsWZQkshDmpyCbL3xHELK
|
||||
okHvgbcoJDvNjW25UXcW78UJ+jHJoZJ3x61xVKFMaJ67ZlGNljlIhumhik2QPbvG
|
||||
KLK8DAECgYEA+0EQN0lFOCIngowB/5QOxzHAg7UAQ7bXKsaAbZmJ5XGXrK3OltRi
|
||||
jvxTw8z4BtW3+sW+5fkTmdCoz8Q9++Cg7eWecq5BWiToW8IB6hU73rpGoMTyLYX+
|
||||
xmt3+QY84qVWFMHQfBB4tD3JHJc0otkyD4DbOOcxLE7pPO7dtlkEj8ECgYEA3vOr
|
||||
xV3zO4Wn7nXh3eHmcdtcbTBFktOiP9zOGphgNFtELC2CvjSXE+WO4dORhfr8fAki
|
||||
RJSstGI32RMOsYm11jUuMGKG3/a0p06wZZKWgsVMrtT5xme5nKTZeoFnDSKusApi
|
||||
Tv1ueeM/fOSU7gElpP/XNxolbmmn7BIT0O80q0cCgYAPfBAKyDo63mf+9K/+11od
|
||||
zqQludb9VVxnZ3psxsSn3ltRqhiMVSSJ2WU0MQKsMSm4i81bEQGzPhfZTiffyRVB
|
||||
9B/Sw4iFjLbBSPCjQ5HABUzrBZhDU/nCssbfExJXiYcHmnZF3yIBQbmOSIdW7fnW
|
||||
2MMp+52dNHPNbI8/eLb+QQKBgQCKMyFWxlXvn3npJslLcKwYB5HTXQjDasw4KfbY
|
||||
JXv8+FXZzJwuqwq7GqDm+u5JzrqTaFYFjONBLWDQ/i8tfd9pWK248FoKmjKBQxW0
|
||||
4JjLluFhvkLV6ZX0s3rbpTrruYPvO4CU1iOVCnhgQpDOt4dOMAiH730kY/njK5F9
|
||||
Acn0IQKBgQCzZzRFkRDWn8H+79rW32arVSG+RBxrG3gPL4kLla5EHODeHYMlUf5I
|
||||
qNypqfsw/wGTn9TWYyfx3SB4Of1B68T//BXghXMjm8YCWSGXJytBu/rFMgBpW5da
|
||||
8OY8LpNaIIpmEKd8esq6B+VtEUEOoSlN1Yv84EG6yjCkHBNqpwqC4g==
|
||||
-----END RSA PRIVATE KEY-----
|
18
tests/pub.crt
Normal file
18
tests/pub.crt
Normal file
@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDhDCCAmygAwIBAgIEb2ZnLjANBgkqhkiG9w0BAQsFADBbMScwJQYDVQQDDB5SZWdlcnkgU2Vs
|
||||
Zi1TaWduZWQgQ2VydGlmaWNhdGUxIzAhBgNVBAoMGlJlZ2VyeSwgaHR0cHM6Ly9yZWdlcnkuY29t
|
||||
MQswCQYDVQQGEwJVQTAgFw0yNTAxMTEwMDAwMDBaGA8yMTI1MDExMTA2MzUxN1owRjESMBAGA1UE
|
||||
AwwJbG9jYWxob3N0MSMwIQYDVQQKDBpSZWdlcnksIGh0dHBzOi8vcmVnZXJ5LmNvbTELMAkGA1UE
|
||||
BhMCVUEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa0ZNogSBX8CvmEXmAvrSlQk9h
|
||||
C5D/yoKsogHrmLYoJN38QgVOaVxdUX52tIrJ8Fo/F57wW67FH2BzDibM+pjBGuoLU498A1gSz5VT
|
||||
MFhF0jq4GBQVY1W9awOqmYdwZaCJn6D8ZGt8YHA3fGqmT6M+RO5e6cHO1ly9LIYImdumApNsGt2e
|
||||
JFPopfcrJmbDFKq85ADv55tQ5RKsVj63UF/4FcP9CsQInpFZt12tsUZI8T9AN6LwCU5YtVjxIcy1
|
||||
wL/RPg1KidHTJVUng3GRQ7g/6S92upoqRjxRhdXgsta3zQnU+TJiqEariD2c2eCCm4O3heCni4wj
|
||||
aGxvwfsefcmHAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1Ud
|
||||
DgQWBBTrZLnKR4WyMdxMIHutC14pjwAfIzAfBgNVHSMEGDAWgBTrZLnKR4WyMdxMIHutC14pjwAf
|
||||
IzANBgkqhkiG9w0BAQsFAAOCAQEAFxch816B5Y3qNnKSBiynDhFwZJoRGln4bcq/U4IACGczuzbI
|
||||
3BMtFah6I3FHDF2rrLfzhwnWct5GRqfIQSdqR4jImXbIvDVxJh1EW/5IhnmRGc/xpVr8Pb2CgwU5
|
||||
dh2DiIfQzEGId+BuP1M/t55x4rjI1bdRwmb1MdZ4sQbVXX+IY+zQ6rOx+BAJ5ne+TQMJYDQbhc3O
|
||||
AETJ77uvSaROghKPYZ+S1GKQEXSPI+MgPV2SBRL6x5cCiV+Vwio2ld6v+E+V9m5dkQxv1Z1MMv6Z
|
||||
vqmLsM3tUE1zB7+vnZcu/rZcm1OkTZ7lQLYm9KQQeeJ9q70SBSwqrLTOd1w5fEkKBw==
|
||||
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue
Block a user