Add support of encryption and HTTP Auth

This commit is contained in:
FullGreaM 2025-01-11 10:07:16 +03:00
parent 57e8cd39df
commit 4392035512
9 changed files with 183 additions and 31 deletions

View File

@ -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) };
}
};

View File

@ -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}`);
})

View File

@ -1,3 +1,5 @@
module.exports = {
"revHTTP": require("./http-reverse"),
"simpleHTTP": require("./http-reverse"),
"redirectHTTP": require("./http-reverse"),
};

22
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

@ -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) ?? [])

View File

@ -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
View 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
View 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-----