UNPKG

kaven-utils

Version:

Utils for Node.js.

261 lines (260 loc) 9.62 kB
/******************************************************************** * @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, }; }