Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions agent-vm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
let
net = import ./network.nix;
privateMatch = { Name = net.agentPrivateInterface; };
caBundle = "/etc/ssl/certs/ca-certificates.crt";
caDir = "/etc/ssl/certs";
in
{
imports =
Expand Down Expand Up @@ -95,14 +97,19 @@ in

# Common SDK trust-store env vars. NixOS's security.pki.certificateFiles
# adds the CA to /etc/ssl/certs/ca-certificates.crt, which curl, git,
# and OpenSSL pick up automatically. Node, Python `requests`, and a few
# other ecosystems read their CA bundle from a hardcoded path or env
# var instead, so set those explicitly. Pi, Claude Code, Codex, etc.
# are all Node CLIs — without NODE_EXTRA_CA_CERTS they'd fail TLS to
# every allowlisted host the moment MITM is enabled.
# and OpenSSL pick up automatically. Node, Python, pip, uv, botocore,
# and Nix dev shells can otherwise prefer package-local CA bundles
# that do not include our per-instance MITM CA.
environment.variables = {
NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
NODE_EXTRA_CA_CERTS = caBundle;
NIX_SSL_CERT_FILE = caBundle;
SSL_CERT_FILE = caBundle;
SSL_CERT_DIR = caDir;
REQUESTS_CA_BUNDLE = caBundle;
CURL_CA_BUNDLE = caBundle;
GIT_SSL_CAINFO = caBundle;
PIP_CERT = caBundle;
AWS_CA_BUNDLE = caBundle;
UV_NATIVE_TLS = "true";
};
}
47 changes: 47 additions & 0 deletions home.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
let
net = import ./network.nix;
pi-coding-agent = import ./pi/pi-coding-agent.nix { inherit pkgs; };
caBundle = "/etc/ssl/certs/ca-certificates.crt";
caDir = "/etc/ssl/certs";
in
{
# Per-user git identity, generated by `rootcell` from the host's `git config`.
Expand Down Expand Up @@ -84,6 +86,19 @@ in
recursive = true;
};

home.sessionVariables = {
NODE_EXTRA_CA_CERTS = caBundle;
NIX_SSL_CERT_FILE = caBundle;
SSL_CERT_FILE = caBundle;
SSL_CERT_DIR = caDir;
REQUESTS_CA_BUNDLE = caBundle;
CURL_CA_BUNDLE = caBundle;
GIT_SSL_CAINFO = caBundle;
PIP_CERT = caBundle;
AWS_CA_BUNDLE = caBundle;
UV_NATIVE_TLS = "true";
};

programs.bash = {
enable = true;
enableCompletion = true;
Expand All @@ -99,6 +114,38 @@ in
programs.direnv = {
enable = true;
nix-direnv.enable = true;
stdlib = ''
rootcell_ca_bundle=${caBundle}
rootcell_ca_dir=${caDir}

rootcell_export_ca_bundle() {
if [ -r "$rootcell_ca_bundle" ]; then
export NODE_EXTRA_CA_CERTS="$rootcell_ca_bundle"
export NIX_SSL_CERT_FILE="$rootcell_ca_bundle"
export SSL_CERT_FILE="$rootcell_ca_bundle"
export SSL_CERT_DIR="$rootcell_ca_dir"
export REQUESTS_CA_BUNDLE="$rootcell_ca_bundle"
export CURL_CA_BUNDLE="$rootcell_ca_bundle"
export GIT_SSL_CAINFO="$rootcell_ca_bundle"
export PIP_CERT="$rootcell_ca_bundle"
export AWS_CA_BUNDLE="$rootcell_ca_bundle"
export UV_NATIVE_TLS=true
fi
}

rootcell_wrap_direnv_loader() {
local fn="$1"
local original="rootcell_original_$fn"
if declare -F "$fn" >/dev/null && ! declare -F "$original" >/dev/null; then
eval "$(declare -f "$fn" | sed "1s/^$fn/$original/")"
eval "$fn() { $original \"\$@\"; local status=\$?; rootcell_export_ca_bundle; return \$status; }"
fi
}

rootcell_export_ca_bundle
rootcell_wrap_direnv_loader use_flake
rootcell_wrap_direnv_loader use_nix
'';
};

# git-delta as the git pager. The `enableGitIntegration` flag writes the
Expand Down
46 changes: 31 additions & 15 deletions src/rootcell/rootcell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,33 @@ function shellQuote(value: string): string {
return `'${value.replaceAll("'", "'\\''")}'`;
}

const AGENT_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
const AGENT_CA_DIR = "/etc/ssl/certs";
const AGENT_CA_ENV = [
["NODE_EXTRA_CA_CERTS", AGENT_CA_BUNDLE],
["NIX_SSL_CERT_FILE", AGENT_CA_BUNDLE],
["SSL_CERT_FILE", AGENT_CA_BUNDLE],
["SSL_CERT_DIR", AGENT_CA_DIR],
["REQUESTS_CA_BUNDLE", AGENT_CA_BUNDLE],
["CURL_CA_BUNDLE", AGENT_CA_BUNDLE],
["GIT_SSL_CAINFO", AGENT_CA_BUNDLE],
["PIP_CERT", AGENT_CA_BUNDLE],
["AWS_CA_BUNDLE", AGENT_CA_BUNDLE],
["UV_NATIVE_TLS", "true"],
] as const;

function agentCaEnv(bundlePath = AGENT_CA_BUNDLE): readonly string[] {
return AGENT_CA_ENV.map(([key, value]) => `${key}=${value === AGENT_CA_BUNDLE ? bundlePath : value}`);
}

function exportAgentCaEnvScript(bundlePath: string): string {
return agentCaEnv(bundlePath).map((entry) => `export ${entry}`).join("\n");
}

function sudoAgentCaEnvScript(): string {
return AGENT_CA_ENV.map(([key]) => ` ${key}="$${key}" \\`).join("\n");
}

function nixStringList(values: readonly string[]): string {
return `[ ${values.map(nixString).join(" ")} ]`;
}
Expand Down Expand Up @@ -292,9 +319,7 @@ export class RootcellApp<TAttachment extends VmNetworkAttachment> {
env: [
...injectedSecretEnv,
`AWS_REGION=${this.config.awsEc2?.region ?? process.env.AWS_REGION ?? "us-east-1"}`,
"NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt",
"SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt",
"REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt",
...agentCaEnv(),
],
});
}
Expand Down Expand Up @@ -971,15 +996,9 @@ sudo nixos-rebuild switch --flake ${shellQuote(this.guestFlakeRef(this.nixosConf
await this.bootstrapAgentFirewallTrust();
await this.runNixosSwitch("agent", `
set -e
export NIX_SSL_CERT_FILE=/tmp/agent-vm-bootstrap-ca-bundle.crt
export SSL_CERT_FILE=/tmp/agent-vm-bootstrap-ca-bundle.crt
export GIT_SSL_CAINFO=/tmp/agent-vm-bootstrap-ca-bundle.crt
export REQUESTS_CA_BUNDLE=/tmp/agent-vm-bootstrap-ca-bundle.crt
${exportAgentCaEnvScript("/tmp/agent-vm-bootstrap-ca-bundle.crt")}
sudo env \\
NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE" \\
SSL_CERT_FILE="$SSL_CERT_FILE" \\
GIT_SSL_CAINFO="$GIT_SSL_CAINFO" \\
REQUESTS_CA_BUNDLE="$REQUESTS_CA_BUNDLE" \\
${sudoAgentCaEnvScript()}
nixos-rebuild switch --flake ${shellQuote(this.guestFlakeRef(this.nixosConfiguration("agent")))}
`);
await this.runAgentHomeManager();
Expand Down Expand Up @@ -1038,10 +1057,7 @@ fi
private async runAgentHomeManager(): Promise<void> {
const script = `
set -e
export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
${exportAgentCaEnvScript(AGENT_CA_BUNDLE)}
nix run ${shellQuote(this.guestFlakeRef("home-manager"))} -- switch --flake ${shellQuote(this.guestFlakeRef(this.config.guestUser))}
`;
const attempts = 4;
Expand Down