Inject the corporate CA certificate into containers
Build App / compute-version (pull_request) Successful in 6s
Build App / build-macos (pull_request) Successful in 2m30s
Build App / build-windows (pull_request) Successful in 5m16s
Build Container / build-container (pull_request) Successful in 10m15s
Build App / build-linux (pull_request) Successful in 6m35s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Build App / compute-version (pull_request) Successful in 6s
Build App / build-macos (pull_request) Successful in 2m30s
Build App / build-windows (pull_request) Successful in 5m16s
Build Container / build-container (pull_request) Successful in 10m15s
Build App / build-linux (pull_request) Successful in 6m35s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Behind a TLS-terminating corporate proxy every HTTPS call inside a container fails — npm, pip, git, curl, the browser-view pane, and Claude Code's own API requests. There was no mechanism at all: installing the certificate by hand inside a container is lost on Reset and had to be repeated per project. A global CA path in AppSettings with a per-project override on Project, taking either a single certificate file or a directory. It is bind-mounted read-only at /tmp/.host-ca (mirroring /tmp/.host-ssh and /tmp/.host-aws) and applied by entrypoint.sh on every start, so it survives recreation, migration and Reset. Four things this gets right that are easy to get wrong: * update-ca-certificates globs *.crt case-sensitively, so a .pem that is merely copied in is ignored in silence. Certificates are renamed, by container_cert_name() in Rust and a mirrored few lines of shell. * The system store only serves curl/git/apt. Node — and so Claude Code itself — needs NODE_EXTRA_CA_CERTS, Python needs REQUESTS_CA_BUNDLE/SSL_CERT_FILE, and Chromium reads neither: it wants ~/.pki/nssdb, seeded with certutil (libnss3-tools, added to the image). * Those vars are set from Rust at creation, never exported by the entrypoint — a terminal is a docker exec and sees nothing the entrypoint exported. They are emitted empty when no CA is configured, since docker commit bakes env into the snapshot image. * triple-c.ca-fingerprint hashes the certificate bytes as well as the path, so a CA rotated in at the same location still forces a recreation. Verified end to end against a real container and a self-signed CA: curl, node, python and git all complete a TLS handshake against a server signed by it and all three fail in the same container without it; the env vars are visible from a docker exec session; the store is cleaned when the setting is cleared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSP2KNPhuWKQ4DL5TZEn3k
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSettings } from "../../hooks/useSettings";
|
||||
import CaCertPathInput from "./CaCertPathInput";
|
||||
|
||||
const INPUT_CLASS =
|
||||
"flex-1 min-w-0 px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:border-[var(--accent)]";
|
||||
|
||||
/**
|
||||
* Global corporate CA certificate setting.
|
||||
*
|
||||
* Applies to every project unless one overrides it in Project Home → Config →
|
||||
* Access. Changing it recreates each container on its next start — the
|
||||
* certificate is copied into the container's trust store once, at start, so
|
||||
* there is nowhere else for a change to land.
|
||||
*/
|
||||
export default function CertificateSettings() {
|
||||
const { appSettings, saveSettings } = useSettings();
|
||||
const [path, setPath] = useState(appSettings?.ca_cert_path ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
setPath(appSettings?.ca_cert_path ?? "");
|
||||
}, [appSettings?.ca_cert_path]);
|
||||
|
||||
const commit = async (value: string) => {
|
||||
if (!appSettings) return;
|
||||
const next = value.trim() || null;
|
||||
if (next === appSettings.ca_cert_path) return;
|
||||
await saveSettings({ ...appSettings, ca_cert_path: next });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="global-ca-cert-path"
|
||||
>
|
||||
Corporate CA Certificate
|
||||
</label>
|
||||
<p className="text-xs text-[var(--text-secondary)] mb-1.5">
|
||||
A certificate file, or a folder of them, for organisations whose network
|
||||
inspects TLS. Mounted read-only into every container and trusted by
|
||||
curl, git, npm, pip, Chromium and Claude Code itself. Per-project
|
||||
settings override this; changing it recreates containers on next start.
|
||||
</p>
|
||||
<CaCertPathInput
|
||||
id="global-ca-cert-path"
|
||||
value={path}
|
||||
onChange={setPath}
|
||||
onCommit={commit}
|
||||
placeholder="/etc/ssl/certs/corp-root.pem"
|
||||
emptyHint="Not set — containers trust only the public CAs shipped with the image."
|
||||
inputClassName={INPUT_CLASS}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user