kaven-utils
Version:
Utils for Node.js.
261 lines (260 loc) • 9.62 kB
JavaScript
/********************************************************************
* @author: Kaven
* @email: kaven@wuwenkai.com
* @website: http://blog.kaven.xyz
* @file: [Kaven-Utils] /src/KavenUtility.Proxy.ts
* @create: 2022-04-22 11:02:50.040
* @modify: 2025-10-14 22:58:04.816
* @version: 6.1.0
* @times: 40
* @lines: 323
* @copyright: Copyright © 2022-2025 Kaven. All Rights Reserved.
* @description: [description]
* @license: [license]
********************************************************************/
import { CombinePath, GetBaseDir, GetFileName } from "kaven-basic";
import { isAbsolute, join, resolve } from "node:path";
import { GeneratedCertificateFiles } from "./GeneratedCertificateFiles.js";
import { GetEntryDirectory } from "./KavenUtility.Constant.js";
import { GenerateCertificate } from "./KavenUtility.Crypto.js";
import { IsDirectory, IsPathExistSync } from "./KavenUtility.FileSystem.js";
import { LoadJsonConfig, SaveJsonConfig } from "./KavenUtility.Server.js";
import { TlsProxyClient } from "./net/proxy/TlsProxyClient.js";
import { TlsProxyServer } from "./net/proxy/TlsProxyServer.js";
const DEFAULT_PROXY_CONFIG = {
SERVER: true,
SERVER_HOST: "0.0.0.0",
SERVER_PORT: 8558,
SERVER_CERT_GENERATE_DIR: "./generated",
SERVER_CERT_JSON_FILE: "./generated/cert.json",
SERVER_VALID_CLIENTS: [],
CLIENT: true,
CLIENT_CONNECT_HOST: "127.0.0.1",
CLIENT_CONNECT_PORT: 8558,
CLIENT_HTTP_HOST: "127.0.0.1",
CLIENT_HTTP_PORT: 8765,
CLIENT_CERT_JSON_FILE: "./generated/cert.json",
CLIENT_CHECK_CERT_CN: false,
CLIENT_VALID_SERVERS: [],
};
export async function StartTlsProxyServer(options) {
options ??= {};
if (options.port === undefined) {
options.port = 8558;
}
if (options.certGenerateDir === undefined) {
options.certGenerateDir = CombinePath(GetEntryDirectory(), "generated");
}
if (options.certJsonFile === undefined) {
options.certJsonFile = CombinePath(options.certGenerateDir, "cert.json");
}
let cert = await GeneratedCertificateFiles.Load(options.certJsonFile);
if (cert === undefined) {
cert = await GenerateCertificate(options);
await cert.Save(options.certJsonFile);
}
const tlsServer = new TlsProxyServer({
key: await cert.ServerKey.GetContent(), //: fs.readFileSync("server-key.pem"),
cert: await cert.ServerCert.GetContent(), //: fs.readFileSync("server-cert.pem"),
// This is necessary only if using client certificate authentication.
requestCert: true,
// This is necessary only if the client uses a self-signed certificate.
ca: [
//fs.readFileSync("client-cert.pem"),
await cert.CaCert.GetContent(),
],
});
const validClients = options.validClients;
if (validClients !== undefined && validClients.length > 0) {
tlsServer.Authenticate = (address) => {
return validClients.includes(address);
};
}
tlsServer.Start(options.port, options.host);
return { server: tlsServer, cert };
}
export async function StartTlsProxyClient(options) {
const cert = options.cert;
if (cert === undefined
|| cert.ClientKey === undefined
|| cert.ClientCert === undefined
|| cert.CaCert === undefined) {
throw new Error();
}
const tlsClient = new TlsProxyClient({
host: options.tlsServer?.host,
port: options.tlsServer?.port ?? 8558,
// Necessary only if the server requires client certificate authentication.
key: await cert.ClientKey.GetContent(), // fs.readFileSync("client-key.pem"),
cert: await cert.ClientCert.GetContent(), // fs.readFileSync("client-cert.pem"),
// Necessary only if the server uses a self-signed certificate.
ca: [
//fs.readFileSync("server-cert.pem"),
await cert.CaCert.GetContent(),
],
// Necessary only if the server's cert isn't for "localhost".
checkServerIdentity: (hostname, cert) => {
const lowerCaseHostname = hostname.toLowerCase();
if (options.checkCertCn) {
if (lowerCaseHostname === cert.subject.CN) {
return undefined;
}
}
if (options.validServers && options.validServers.length > 0) {
for (const server of options.validServers) {
if (server.toLowerCase() === lowerCaseHostname) {
return undefined;
}
}
}
else {
return undefined;
}
return new Error(`Hostname: "${lowerCaseHostname}" doesn't match!`);
},
});
// tlsClient.Authorization = new KavenBasicAuthentication("admin", "Xm4lMOdYdJHx");
// tlsClient.Authorization.HeaderName = HttpHeader_ProxyAuthenticate;
// tlsClient.Verbose = true;
tlsClient.Start(options.httpListen?.port, options.httpListen?.host);
return tlsClient;
}
/**
*
* @param dirOrFile
* @since 4.3.4
* @version 2025-10-14
*/
export async function StartProxy(dirOrFile, logger) {
dirOrFile = resolve(dirOrFile);
if (!IsPathExistSync(dirOrFile)) {
throw new Error(`path not exists: ${dirOrFile}`);
}
logger?.Info(`StartProxy: ${dirOrFile}`);
const configFileName = "kaven-proxy.config";
let config;
let dir = "";
if (await IsDirectory(dirOrFile)) {
dir = dirOrFile;
config = await LoadJsonConfig(logger, dir, configFileName);
if (config === undefined) {
await SaveJsonConfig(DEFAULT_PROXY_CONFIG, join(dir, configFileName));
config = await LoadJsonConfig(logger, dir, configFileName);
}
}
else {
dir = GetBaseDir(dirOrFile);
const name = GetFileName(dirOrFile);
config = await LoadJsonConfig(logger, dir, name);
}
if (config === undefined) {
throw new Error("Load config failed");
}
const resolvePath = (path) => {
if (!path) {
return undefined;
}
if (isAbsolute(path)) {
return path;
}
return resolve(CombinePath(dir, path));
};
const SERVER = config.SERVER;
const CLIENT = config.CLIENT;
if (!SERVER && !CLIENT) {
throw new Error("Neither server nor client is enabled.");
}
const serverOptions = {};
const clientOptions = {};
// #region load configs
if (SERVER) {
const SERVER_HOST = config.SERVER_HOST;
const SERVER_PORT = config.SERVER_PORT;
const SERVER_CERT_GENERATE_DIR = resolvePath(config.SERVER_CERT_GENERATE_DIR);
const SERVER_CERT_JSON_FILE = resolvePath(config.SERVER_CERT_JSON_FILE);
const SERVER_VALID_CLIENTS = config.SERVER_VALID_CLIENTS;
if (SERVER_HOST) {
serverOptions.host = SERVER_HOST;
}
if (SERVER_PORT) {
serverOptions.port = Number(SERVER_PORT);
}
if (SERVER_CERT_GENERATE_DIR) {
serverOptions.certGenerateDir = SERVER_CERT_GENERATE_DIR;
}
if (SERVER_CERT_JSON_FILE) {
serverOptions.certJsonFile = SERVER_CERT_JSON_FILE;
}
if (SERVER_VALID_CLIENTS) {
serverOptions.validClients = SERVER_VALID_CLIENTS;
}
}
if (CLIENT) {
const CLIENT_CONNECT_HOST = config.CLIENT_CONNECT_HOST;
const CLIENT_CONNECT_PORT = config.CLIENT_CONNECT_PORT;
const CLIENT_HTTP_HOST = config.CLIENT_HTTP_HOST;
const CLIENT_HTTP_PORT = config.CLIENT_HTTP_PORT;
const CLIENT_CERT_JSON_FILE = resolvePath(config.CLIENT_CERT_JSON_FILE);
const CLIENT_VALID_SERVERS = config.CLIENT_VALID_SERVERS;
if (CLIENT_CONNECT_HOST) {
clientOptions.tlsServer = {
host: CLIENT_CONNECT_HOST,
};
}
if (CLIENT_CONNECT_PORT) {
const port = Number(CLIENT_CONNECT_PORT);
if (clientOptions.tlsServer) {
clientOptions.tlsServer.port = port;
}
else {
clientOptions.tlsServer = {
port: port,
};
}
}
if (CLIENT_HTTP_HOST) {
clientOptions.httpListen = {
host: CLIENT_HTTP_HOST,
};
}
if (CLIENT_HTTP_PORT) {
const port = Number(CLIENT_HTTP_PORT);
if (clientOptions.httpListen) {
clientOptions.httpListen.port = port;
}
else {
clientOptions.httpListen = {
port: port,
};
}
}
if (CLIENT_CERT_JSON_FILE) {
clientOptions.cert = await GeneratedCertificateFiles.Load(CLIENT_CERT_JSON_FILE);
}
if (CLIENT_VALID_SERVERS) {
clientOptions.validServers = CLIENT_VALID_SERVERS;
}
}
// #endregion
let pServer;
let pClient;
if (SERVER) {
const { server, cert } = await StartTlsProxyServer(serverOptions);
pServer = server;
if (CLIENT) {
if (!clientOptions.cert) {
clientOptions.cert = cert;
}
if (!clientOptions.tlsServer?.port) {
clientOptions.tlsServer = serverOptions;
}
}
}
if (CLIENT) {
pClient = await StartTlsProxyClient(clientOptions);
}
return {
server: pServer,
client: pClient,
};
}