From e5fed2a876539e72dc6e7080f6c1c078980160df Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 28 May 2025 09:43:48 +0200 Subject: [PATCH 1/4] fix internal ssl, add internal ssl test --- .github/workflows/test.yml | 4 ++ .gitignore | 1 + lib/configproxy.js | 8 ++-- lib/testutil.js | 13 ++++-- test/make_internal_ssl.py | 96 ++++++++++++++++++++++++++++++++++++++ test/proxy_spec.js | 37 +++++++++++++++ 6 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 test/make_internal_ssl.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index beb0831a..c0567f6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,6 +79,10 @@ jobs: restore-keys: | ${{ runner.os }}-npm- + - name: setup internal ssl + run: | + pipx run test/make_internal_ssl.py + - name: "Install dependencies (npm ci)" run: | npm ci diff --git a/.gitignore b/.gitignore index 9cfe6b6f..95b431d9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ bench/html coverage dist .nyc_output +test/ssl diff --git a/lib/configproxy.js b/lib/configproxy.js index 6e493505..c88fe9ac 100644 --- a/lib/configproxy.js +++ b/lib/configproxy.js @@ -557,11 +557,11 @@ export class ConfigurableProxy extends EventEmitter { } target = new URL(target); - var proxyOptions = { target: target }; + var proxyOptions = { target }; if (that.options.clientSsl) { - proxyOptions.key = that.options.clientSsl.key; - proxyOptions.cert = that.options.clientSsl.cert; - proxyOptions.ca = that.options.clientSsl.ca; + target.key = that.options.clientSsl.key; + target.cert = that.options.clientSsl.cert; + target.ca = that.options.clientSsl.ca; } // add config argument diff --git a/lib/testutil.js b/lib/testutil.js index 008739fa..fac9e68a 100644 --- a/lib/testutil.js +++ b/lib/testutil.js @@ -1,14 +1,17 @@ "use strict"; import http from "node:http"; +import https from "node:https"; import { WebSocketServer } from "ws"; import { ConfigurableProxy } from "./configproxy.js"; import { defaultLogger } from "./log.js"; var servers = []; -export function addTarget(proxy, path, port, websocket, targetPath) { - var target = "http://127.0.0.1:" + port; +// TODO: make this an options dict +export function addTarget(proxy, path, port, websocket, targetPath, sslOptions) { + var proto = sslOptions ? "https" : "http"; + var target = proto + "://127.0.0.1:" + port; if (targetPath) { target = target + targetPath; } @@ -17,8 +20,12 @@ export function addTarget(proxy, path, port, websocket, targetPath) { target: target, path: path, }; + var createServer = http.createServer; + if (sslOptions) { + createServer = (cb) => https.createServer(sslOptions, cb); + } - server = http.createServer(function (req, res) { + server = createServer(function (req, res) { var reply = {}; Object.assign(reply, data); reply.url = req.url; diff --git a/test/make_internal_ssl.py b/test/make_internal_ssl.py new file mode 100644 index 00000000..8b8e3710 --- /dev/null +++ b/test/make_internal_ssl.py @@ -0,0 +1,96 @@ +""" +Regenerate internal ssl certificates for tests +""" + +# PEP 773 dependencies +# /// script +# dependencies = [ +# "certipy", +# ] +# /// + +import asyncio +import shutil +import ssl +from pathlib import Path + +from certipy import Certipy + +ssl_dir = Path(__file__).parent.resolve() / "ssl" +port = 12345 + + +def make_certs(): + """Create certificates for proxy client and ssl backend""" + # start fresh + shutil.rmtree(ssl_dir) + alt_names = [ + "IP:127.0.0.1", + "IP:0:0:0:0:0:0:0:1", + "DNS:localhost", + ] + certipy = Certipy(store_dir=ssl_dir) + _trust_bundles = certipy.trust_from_graph({ + "backend-ca": ["proxy-client-ca"], + "proxy-client-ca": ["backend-ca"], + }) + for name in ("backend", "proxy-client"): + certipy.create_signed_pair( + name, f"{name}-ca", alt_names=alt_names + ) + + + +async def client_connected(reader, writer): + """Callback for ssl server""" + print("client connected") + msg = await reader.read(5) + print("server received", msg.decode()) + writer.write(b"pong") + + +async def ssl_backend(): + """Run a test ssl server""" + ssl_context = ssl.create_default_context( + ssl.Purpose.CLIENT_AUTH, cafile=ssl_dir / "backend-ca_trust.crt" + ) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.load_default_certs() + ssl_context.load_cert_chain( + ssl_dir / "backend/backend.crt", ssl_dir / "backend/backend.key" + ) + await asyncio.start_server( + client_connected, host="localhost", port=port, ssl=ssl_context + ) + + +async def ssl_client(): + """Run a test ssl client""" + ssl_context = ssl.create_default_context( + ssl.Purpose.SERVER_AUTH, cafile=ssl_dir / "proxy-client-ca_trust.crt" + ) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.load_default_certs() + ssl_context.check_hostname = True + ssl_context.load_cert_chain( + ssl_dir / "proxy-client/proxy-client.crt", + ssl_dir / "proxy-client/proxy-client.key", + ) + reader, writer = await asyncio.open_connection("localhost", port, ssl=ssl_context) + writer.write(b"ping") + msg = await reader.read(5) + print("client received", msg.decode()) + + +async def main(): + # make the certs + print(f"Making internal ssl certificates in {ssl_dir}") + make_certs() + print("Testing internal ssl setup") + await ssl_backend() + await ssl_client() + print("OK") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 3396b26f..eccb046e 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -1,3 +1,4 @@ +import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import fetch from "node-fetch"; @@ -475,4 +476,40 @@ describe("Proxy Tests", function () { done(); }); }); + + it("internal ssl test", function (done) { + if (!fs.existsSync(path.resolve(__dirname, "ssl"))) { + console.log("skipping ssl test without ssl certs. Run make_internal_ssl.py first."); + done(); + return; + } + var proxyPort = 55556; + var testPort = proxyPort + 20; + var options = { + clientSsl: { + key: fs.readFileSync(path.resolve(__dirname, "ssl/proxy-client/proxy-client.key")), + cert: fs.readFileSync(path.resolve(__dirname, "ssl/proxy-client/proxy-client.crt")), + ca: fs.readFileSync(path.resolve(__dirname, "ssl/proxy-client-ca_trust.crt")), + }, + }; + + util + .setupProxy(proxyPort, options, []) + .then((proxy) => + util.addTarget(proxy, "/backend/", testPort, false, null, { + key: fs.readFileSync(path.resolve(__dirname, "ssl/backend/backend.key")), + cert: fs.readFileSync(path.resolve(__dirname, "ssl/backend/backend.crt")), + ca: fs.readFileSync(path.resolve(__dirname, "ssl/backend-ca_trust.crt")), + requestCert: true, + }) + ) + .then(() => fetch("http://127.0.0.1:" + proxyPort + "/backend/urlpath/")) + .then((res) => { + expect(res.status).toEqual(200); + }) + .catch((err) => { + done.fail(err); + }) + .then(done); + }); }); From 9102f1b43658ea1d71fb0161069f3ed87973b8c9 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 28 May 2025 09:45:41 +0200 Subject: [PATCH 2/4] delete ssl dir only if it exists --- test/make_internal_ssl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/make_internal_ssl.py b/test/make_internal_ssl.py index 8b8e3710..7693f652 100644 --- a/test/make_internal_ssl.py +++ b/test/make_internal_ssl.py @@ -23,7 +23,8 @@ def make_certs(): """Create certificates for proxy client and ssl backend""" # start fresh - shutil.rmtree(ssl_dir) + if ssl_dir.exists(): + shutil.rmtree(ssl_dir) alt_names = [ "IP:127.0.0.1", "IP:0:0:0:0:0:0:0:1", From c267d1ffc600c5209fb70f70c411521ac92c63dc Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 28 May 2025 10:04:36 +0200 Subject: [PATCH 3/4] it's pep 723 https://peps.python.org/pep-0723/ --- test/make_internal_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/make_internal_ssl.py b/test/make_internal_ssl.py index 7693f652..73699fd7 100644 --- a/test/make_internal_ssl.py +++ b/test/make_internal_ssl.py @@ -2,7 +2,7 @@ Regenerate internal ssl certificates for tests """ -# PEP 773 dependencies +# PEP 723 dependencies # /// script # dependencies = [ # "certipy", From 1e6516a5115b9a4e1c220d0c3656747cc1e23888 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 28 May 2025 10:08:26 +0200 Subject: [PATCH 4/4] format --- test/make_internal_ssl.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/make_internal_ssl.py b/test/make_internal_ssl.py index 73699fd7..522b1845 100644 --- a/test/make_internal_ssl.py +++ b/test/make_internal_ssl.py @@ -31,15 +31,14 @@ def make_certs(): "DNS:localhost", ] certipy = Certipy(store_dir=ssl_dir) - _trust_bundles = certipy.trust_from_graph({ - "backend-ca": ["proxy-client-ca"], - "proxy-client-ca": ["backend-ca"], - }) + _trust_bundles = certipy.trust_from_graph( + { + "backend-ca": ["proxy-client-ca"], + "proxy-client-ca": ["backend-ca"], + } + ) for name in ("backend", "proxy-client"): - certipy.create_signed_pair( - name, f"{name}-ca", alt_names=alt_names - ) - + certipy.create_signed_pair(name, f"{name}-ca", alt_names=alt_names) async def client_connected(reader, writer):