mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge pull request #108 from AikidoSec/proxy-http-requests
Allow the safe-chain to act as a regular http proxy too (besides the CONNECT tunneling implementation)
This commit is contained in:
commit
9cec5e4bc9
3 changed files with 102 additions and 9 deletions
69
packages/safe-chain/src/registryProxy/plainHttpProxy.js
Normal file
69
packages/safe-chain/src/registryProxy/plainHttpProxy.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import * as http from "http";
|
||||
import * as https from "https";
|
||||
|
||||
export function handleHttpProxyRequest(req, res) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// The protocol for the plainHttpProxy should usually only be http:
|
||||
// but when the client for some reason sends an https: request directly
|
||||
// instead of using the CONNECT method, we should handle it gracefully.
|
||||
let protocol;
|
||||
if (url.protocol === "http:") {
|
||||
protocol = http;
|
||||
} else if (url.protocol === "https:") {
|
||||
protocol = https;
|
||||
} else {
|
||||
res.writeHead(502);
|
||||
res.end(`Bad Gateway: Unsupported protocol ${url.protocol}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyRequest = protocol
|
||||
.request(
|
||||
req.url,
|
||||
{ method: req.method, headers: req.headers },
|
||||
(proxyRes) => {
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(res);
|
||||
|
||||
proxyRes.on("error", () => {
|
||||
// Proxy response stream error
|
||||
// Clean up client response stream
|
||||
if (res.writable) {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
proxyRes.on("close", () => {
|
||||
// Clean up if the proxy response stream closes
|
||||
if (res.writable) {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
.on("error", (err) => {
|
||||
res.writeHead(502);
|
||||
res.end(`Bad Gateway: ${err.message}`);
|
||||
});
|
||||
|
||||
req.on("error", () => {
|
||||
// Client request stream error
|
||||
// Abort the proxy request
|
||||
proxyRequest.destroy();
|
||||
});
|
||||
|
||||
res.on("error", () => {
|
||||
// Client response stream error (client disconnected)
|
||||
// Clean up proxy streams
|
||||
proxyRequest.destroy();
|
||||
});
|
||||
|
||||
res.on("close", () => {
|
||||
// Client disconnected
|
||||
// Abort the proxy request to avoid unnecessary work
|
||||
proxyRequest.destroy();
|
||||
});
|
||||
|
||||
req.pipe(proxyRequest);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import * as http from "http";
|
||||
import { tunnelRequest } from "./tunnelRequestHandler.js";
|
||||
import { mitmConnect } from "./mitmRequestHandler.js";
|
||||
import { handleHttpProxyRequest } from "./plainHttpProxy.js";
|
||||
import { getCaCertPath } from "./certUtils.js";
|
||||
import { auditChanges } from "../scanning/audit/index.js";
|
||||
import { knownRegistries, parsePackageFromUrl } from "./parsePackageFromUrl.js";
|
||||
|
|
@ -15,7 +16,6 @@ const state = {
|
|||
|
||||
export function createSafeChainProxy() {
|
||||
const server = createProxyServer();
|
||||
server.on("connect", handleConnect);
|
||||
|
||||
return {
|
||||
startServer: () => startServer(server),
|
||||
|
|
@ -54,13 +54,15 @@ export function mergeSafeChainProxyEnvironmentVariables(env) {
|
|||
}
|
||||
|
||||
function createProxyServer() {
|
||||
const server = http.createServer((_, res) => {
|
||||
res.writeHead(400, "Bad Request");
|
||||
res.write(
|
||||
"Safe-chain proxy: Direct http not supported. Only CONNECT requests are allowed."
|
||||
const server = http.createServer(
|
||||
// This handles direct HTTP requests (non-CONNECT requests)
|
||||
// This is normally http-only traffic, but we also handle
|
||||
// https for clients that don't properly use CONNECT
|
||||
handleHttpProxyRequest
|
||||
);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// This handles HTTPS requests via the CONNECT method
|
||||
server.on("connect", handleConnect);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,26 @@ export class DockerTestContainer {
|
|||
}
|
||||
}
|
||||
|
||||
dockerExec(command, daemon = false) {
|
||||
if (!this.isRunning) {
|
||||
throw new Error("Container is not running");
|
||||
}
|
||||
|
||||
try {
|
||||
const dockerExecCommand = `docker exec ${daemon ? "-d " : " "}${
|
||||
this.containerName
|
||||
} bash -c "${command}"`;
|
||||
const output = execSync(dockerExecCommand, {
|
||||
encoding: "utf-8",
|
||||
stdio: "pipe",
|
||||
timeout: 10000,
|
||||
});
|
||||
return output;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to execute command: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async openShell(shell) {
|
||||
let ptyProcess = pty.spawn(
|
||||
"docker",
|
||||
|
|
@ -96,9 +116,11 @@ export class DockerTestContainer {
|
|||
|
||||
const timeout = setTimeout(() => {
|
||||
// Fallback in case the command doesn't finish in a reasonable time
|
||||
// oxlint-disable-next-line no-console - having this log in CI helps diagnose issues
|
||||
console.log("Command timeout reached");
|
||||
resolve({ allData, output: parseShellOutput(allData), command });
|
||||
ptyProcess.removeListener("data", handleInput);
|
||||
}, 10000);
|
||||
}, 15000);
|
||||
|
||||
function handleInput(data) {
|
||||
allData.push(data);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue