mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Skeleton
This commit is contained in:
parent
76acf43128
commit
e04c4b6f21
7 changed files with 92 additions and 19 deletions
|
|
@ -39,3 +39,7 @@ export function setEcoSystem(setting) {
|
||||||
export const LOGGING_SILENT = "silent";
|
export const LOGGING_SILENT = "silent";
|
||||||
export const LOGGING_NORMAL = "normal";
|
export const LOGGING_NORMAL = "normal";
|
||||||
export const LOGGING_VERBOSE = "verbose";
|
export const LOGGING_VERBOSE = "verbose";
|
||||||
|
|
||||||
|
// OS trust store paths
|
||||||
|
export const DARWIN_CA_PATH = "/Library/Keychains/System.keychain";
|
||||||
|
export const LINUX_CA_PATH = "/usr/local/share/ca-certificates/safe-chain-ca.crt";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
import { installSafeChainCA } from "../../registryProxy/certUtils.js";
|
||||||
|
|
||||||
|
function shouldMockCAInstall() {
|
||||||
|
return process.env.SAFE_CHAIN_TEST_SKIP_CA_INSTALL === "1";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} command
|
* @param {string} command
|
||||||
|
|
@ -11,15 +15,11 @@ import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||||
*/
|
*/
|
||||||
export async function runPip(command, args) {
|
export async function runPip(command, args) {
|
||||||
try {
|
try {
|
||||||
|
// Install Safe Chain CA in OS trust store before running pip, unless in test mode
|
||||||
|
if (!shouldMockCAInstall()) {
|
||||||
|
await installSafeChainCA();
|
||||||
|
}
|
||||||
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
||||||
|
|
||||||
// Always provide Python with a complete CA bundle (Safe Chain CA + Mozilla + Node built-in roots)
|
|
||||||
// so that any network request made by pip, including those outside explicit CLI args,
|
|
||||||
// validates correctly under both MITM'd and tunneled HTTPS.
|
|
||||||
const combinedCaPath = getCombinedCaBundlePath();
|
|
||||||
env.REQUESTS_CA_BUNDLE = combinedCaPath;
|
|
||||||
env.SSL_CERT_FILE = combinedCaPath;
|
|
||||||
|
|
||||||
const result = await safeSpawn(command, args, {
|
const result = await safeSpawn(command, args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
env,
|
env,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import forge from "node-forge";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
|
import { safeSpawn } from "../utils/safeSpawn.js";
|
||||||
|
import { DARWIN_CA_PATH, LINUX_CA_PATH } from "../config/settings.js";
|
||||||
|
|
||||||
const certFolder = path.join(os.homedir(), ".safe-chain", "certs");
|
const certFolder = path.join(os.homedir(), ".safe-chain", "certs");
|
||||||
const ca = loadCa();
|
const ca = loadCa();
|
||||||
|
|
@ -116,3 +118,69 @@ function generateCa() {
|
||||||
certificate: cert,
|
certificate: cert,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Safe Chain CA certificate is already installed in the OS trust store.
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
export async function isSafeChainCAInstalled() {
|
||||||
|
const platform = os.platform();
|
||||||
|
try {
|
||||||
|
if (platform === "darwin") {
|
||||||
|
// macOS: check System Keychain for cert
|
||||||
|
const res = await safeSpawn("security", ["find-certificate", "-c", "safe-chain proxy", DARWIN_CA_PATH], { stdio: "pipe" });
|
||||||
|
return res.stdout.includes("safe-chain proxy");
|
||||||
|
} else if (platform === "linux") {
|
||||||
|
// Linux: check for CA file
|
||||||
|
return fs.existsSync(LINUX_CA_PATH);
|
||||||
|
} else if (platform === "win32") {
|
||||||
|
// Windows: check Root store for cert
|
||||||
|
return await safeSpawn("certutil", ["-store", "Root", "safe-chain proxy"], { stdio: "pipe" }).then(res => res.stdout.includes("safe-chain proxy"));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// If check fails, assume not installed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the Safe Chain CA certificate in the OS trust store.
|
||||||
|
* Uses platform-specific commands. Optionally uses npm packages if available.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function installSafeChainCA() {
|
||||||
|
const caPath = getCaCertPath();
|
||||||
|
const platform = os.platform();
|
||||||
|
try {
|
||||||
|
const alreadyInstalled = await isSafeChainCAInstalled();
|
||||||
|
if (alreadyInstalled) {
|
||||||
|
console.log("Safe Chain CA already installed in OS trust store.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (platform === "darwin") {
|
||||||
|
// macOS: use security CLI
|
||||||
|
await safeSpawn("sudo", [
|
||||||
|
"security",
|
||||||
|
"add-trusted-cert",
|
||||||
|
"-d",
|
||||||
|
"-r", "trustRoot",
|
||||||
|
"-k", DARWIN_CA_PATH,
|
||||||
|
caPath
|
||||||
|
], { stdio: "inherit" });
|
||||||
|
} else if (platform === "linux") {
|
||||||
|
// Linux: use update-ca-certificates
|
||||||
|
await safeSpawn("sudo", ["cp", caPath, LINUX_CA_PATH], { stdio: "inherit" });
|
||||||
|
await safeSpawn("sudo", ["update-ca-certificates"], { stdio: "inherit" });
|
||||||
|
} else if (platform === "win32") {
|
||||||
|
// Windows: use certutil
|
||||||
|
await safeSpawn("certutil", ["-addstore", "-f", "Root", caPath], { stdio: "inherit" });
|
||||||
|
} else {
|
||||||
|
throw new Error("Unsupported OS for automatic CA installation. Please install manually.");
|
||||||
|
}
|
||||||
|
console.log("Safe Chain CA installed in OS trust store.");
|
||||||
|
} catch (/** @type any */ error) {
|
||||||
|
console.error("Failed to install Safe Chain CA:", error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,12 @@ const yarnVersion = process.env.YARN_VERSION || "latest";
|
||||||
const pnpmVersion = process.env.PNPM_VERSION || "latest";
|
const pnpmVersion = process.env.PNPM_VERSION || "latest";
|
||||||
|
|
||||||
export class DockerTestContainer {
|
export class DockerTestContainer {
|
||||||
constructor() {
|
constructor({ asRootUser = false } = {}) {
|
||||||
this.containerName = `safe-chain-test-${Math.random()
|
this.containerName = `safe-chain-test-${Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.substring(2, 15)}`;
|
.substring(2, 15)}`;
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
this.asRootUser = asRootUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildImage() {
|
static buildImage() {
|
||||||
|
|
@ -50,8 +51,9 @@ export class DockerTestContainer {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start a long-running container that we can exec commands into
|
// Start a long-running container that we can exec commands into
|
||||||
|
const userFlag = this.asRootUser ? "--user root" : "";
|
||||||
execSync(
|
execSync(
|
||||||
`docker run -d --name ${this.containerName} ${imageName} sleep infinity`,
|
`docker run -d --name ${this.containerName} ${userFlag} ${imageName} sleep infinity`,
|
||||||
{ stdio: "ignore" }
|
{ stdio: "ignore" }
|
||||||
);
|
);
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ ARG PYTHON_VERSION=3
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
ENV BASH_ENV=~/.bashrc
|
ENV BASH_ENV=~/.bashrc
|
||||||
|
|
||||||
# Install a proxy
|
# Install a proxy and sudo
|
||||||
RUN apt-get update && apt-get install tinyproxy -y
|
RUN apt-get update && apt-get install -y tinyproxy sudo
|
||||||
|
|
||||||
# Install zsh
|
# Install zsh
|
||||||
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.1/zsh-in-docker.sh)"
|
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.1/zsh-in-docker.sh)"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = new DockerTestContainer();
|
container = new DockerTestContainer({ asRootUser: true });
|
||||||
await container.start();
|
await container.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ describe("E2E: pip coverage", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Run a new Docker container for each test
|
// Run a new Docker container for each test as root user
|
||||||
container = new DockerTestContainer();
|
container = new DockerTestContainer({ asRootUser: true });
|
||||||
await container.start();
|
await container.start();
|
||||||
|
|
||||||
const installationShell = await container.openShell("zsh");
|
const installationShell = await container.openShell("zsh");
|
||||||
|
|
@ -316,10 +316,9 @@ describe("E2E: pip coverage", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should NOT contain certificate verification errors
|
// Should NOT contain certificate verification errors
|
||||||
|
const sslErrorPattern = /certificate verify failed|CERTIFICATE_VERIFY_FAILED|SSLError/i;
|
||||||
assert.ok(
|
assert.ok(
|
||||||
!result.output.match(
|
!sslErrorPattern.test(result.output),
|
||||||
/SSL|certificate verify failed|CERTIFICATE_VERIFY_FAILED/i
|
|
||||||
),
|
|
||||||
`Should not have SSL/certificate errors for tunneled hosts. Output was:\n${result.output}`
|
`Should not have SSL/certificate errors for tunneled hosts. Output was:\n${result.output}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue