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:
@@ -1,5 +1,5 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, GatewayStatus, InstallOptions, ClaudeSession, ContainerCapabilities, ScheduledTask, ScheduledTaskInput, SchedulerNotification, AuthBridgeStatus, BrowserViewStatus, PlaywrightDetection, ContainerStaleness, MigrationOptions, MigrationReport, MigrationState, ClearTokenOutcome } from "./types";
|
||||
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, GatewayStatus, InstallOptions, ClaudeSession, ContainerCapabilities, ScheduledTask, ScheduledTaskInput, SchedulerNotification, AuthBridgeStatus, BrowserViewStatus, PlaywrightDetection, ContainerStaleness, MigrationOptions, MigrationReport, MigrationState, ClearTokenOutcome, CaCertInfo } from "./types";
|
||||
|
||||
// Docker
|
||||
export const checkDocker = () => invoke<boolean>("check_docker");
|
||||
@@ -37,6 +37,10 @@ export const detectAwsConfig = () =>
|
||||
invoke<string | null>("detect_aws_config");
|
||||
export const listAwsProfiles = () =>
|
||||
invoke<string[]>("list_aws_profiles");
|
||||
/** Check a corporate CA path and report what would be installed. Never
|
||||
* rejects for a bad path — the reason comes back in `error`. */
|
||||
export const inspectCaCertPath = (path: string) =>
|
||||
invoke<CaCertInfo>("inspect_ca_cert_path", { path });
|
||||
export const detectHostTimezone = () =>
|
||||
invoke<string>("detect_host_timezone");
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ export interface Project {
|
||||
/** null = not set → falls back to `full_permissions` (true → "bypass"). */
|
||||
permission_mode: PermissionMode | null;
|
||||
ssh_key_path: string | null;
|
||||
/** Per-project override for the corporate CA certificate path (a single
|
||||
* certificate file or a directory of them). null falls back to
|
||||
* `AppSettings.ca_cert_path`. Changing it recreates the container. */
|
||||
ca_cert_path: string | null;
|
||||
git_token: string | null;
|
||||
git_user_name: string | null;
|
||||
git_user_email: string | null;
|
||||
@@ -190,6 +194,12 @@ export interface GlobalOpenAiCompatibleSettings {
|
||||
|
||||
export interface AppSettings {
|
||||
default_ssh_key_path: string | null;
|
||||
/** Corporate root CA — a single certificate file or a directory of them —
|
||||
* mounted read-only into every container and installed into the system
|
||||
* trust store, Node's `NODE_EXTRA_CA_CERTS`, Python's
|
||||
* `REQUESTS_CA_BUNDLE`/`SSL_CERT_FILE` and Chrome's NSS database.
|
||||
* Needed when the host is behind a TLS-terminating corporate proxy. */
|
||||
ca_cert_path: string | null;
|
||||
default_git_user_name: string | null;
|
||||
default_git_user_email: string | null;
|
||||
docker_socket_path: string | null;
|
||||
@@ -212,6 +222,20 @@ export interface AppSettings {
|
||||
global_claude_code_settings: ClaudeCodeSettings | null;
|
||||
}
|
||||
|
||||
/** What `inspect_ca_cert_path` reports about a corporate CA path. Errors ride
|
||||
* in the payload rather than rejecting, so the field can render them inline
|
||||
* while the user is still typing. */
|
||||
export interface CaCertInfo {
|
||||
exists: boolean;
|
||||
is_directory: boolean;
|
||||
cert_count: number;
|
||||
/** The `.crt` names the certificates are installed as inside the container —
|
||||
* surfacing the silent `.pem` → `.crt` rename that
|
||||
* `update-ca-certificates` requires. */
|
||||
installed_names: string[];
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface SttSettings {
|
||||
enabled: boolean;
|
||||
model: string;
|
||||
|
||||
Reference in New Issue
Block a user