PRD 0029: Codex host credentials through egress #110
@@ -8,7 +8,7 @@ command, default image, and prompt/auth behavior.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
@@ -71,6 +71,7 @@ class AgentProvisionPlan:
|
||||
image: str
|
||||
dockerfile: str
|
||||
guest_env: dict[str, str]
|
||||
env_vars: dict[str, str] = field(default_factory=dict)
|
||||
dirs: tuple[AgentProvisionDir, ...] = ()
|
||||
files: tuple[AgentProvisionFile, ...] = ()
|
||||
pre_copy: tuple[AgentProvisionCommand, ...] = ()
|
||||
@@ -124,17 +125,17 @@ def agent_provision_plan(
|
||||
) -> AgentProvisionPlan:
|
||||
runtime = runtime_for(template)
|
||||
resolved_guest_env = dict(guest_env or {})
|
||||
env_vars: dict[str, str] = {}
|
||||
dirs: list[AgentProvisionDir] = []
|
||||
files: list[AgentProvisionFile] = []
|
||||
pre_copy: list[AgentProvisionCommand] = []
|
||||
verify: list[AgentProvisionCommand] = []
|
||||
|
||||
if template == PROVIDER_CODEX:
|
||||
resolved_guest_env.setdefault(
|
||||
"CODEX_CA_CERTIFICATE",
|
||||
"/etc/ssl/certs/ca-certificates.crt",
|
||||
)
|
||||
env_vars["CODEX_CA_CERTIFICATE"] = "/etc/ssl/certs/ca-certificates.crt"
|
||||
auth_dir = resolved_guest_env.get("CODEX_HOME", f"{guest_home}/.codex")
|
||||
if forward_host_credentials:
|
||||
env_vars["CODEX_HOME"] = auth_dir
|
||||
dirs.append(AgentProvisionDir(auth_dir))
|
||||
config_path = f"{auth_dir}/config.toml"
|
||||
config_file = state_dir / "codex-config.toml"
|
||||
@@ -177,6 +178,7 @@ def agent_provision_plan(
|
||||
prompt_mode=runtime.prompt_mode,
|
||||
image=runtime.image,
|
||||
dockerfile=dockerfile,
|
||||
env_vars=env_vars,
|
||||
guest_env=resolved_guest_env,
|
||||
dirs=tuple(dirs),
|
||||
files=tuple(files),
|
||||
|
||||
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from ...agent_provider import agent_provision_plan, runtime_for
|
||||
@@ -231,6 +232,10 @@ def resolve_plan(
|
||||
forward_host_credentials=provider.forward_host_credentials,
|
||||
host_env=dict(os.environ),
|
||||
)
|
||||
guest_env = dict(agent_provision.guest_env)
|
||||
for key, val in agent_provision.env_vars.items():
|
||||
guest_env.setdefault(key, val)
|
||||
agent_provision = replace(agent_provision, guest_env=guest_env)
|
||||
|
||||
return DockerBottlePlan(
|
||||
spec=spec,
|
||||
|
||||
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from ...agent_provider import agent_provision_plan, runtime_for
|
||||
@@ -128,24 +129,6 @@ def resolve_plan(
|
||||
if provider.template == "claude" and has_provider_auth:
|
||||
|
didericis marked this conversation as resolved
Outdated
|
||||
guest_env.setdefault("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
|
||||
guest_env.setdefault("DISABLE_ERROR_REPORTING", "1")
|
||||
if provider.template == "codex":
|
||||
# Codex is a Rust/rustls client: unlike the Node agents it does
|
||||
# NOT consult the system trust store or honor NODE_EXTRA_CA_CERTS.
|
||||
# It reads CODEX_CA_CERTIFICATE (falling back to SSL_CERT_FILE)
|
||||
# for custom roots, across HTTPS *and* the wss responses channel.
|
||||
# Point it at the bundle update-ca-certificates rebuilt with the
|
||||
# egress MITM CA so Codex trusts the proxy and egress can inject
|
||||
# the host bearer — without this, codex bottles need
|
||||
# pipelock tls_passthrough, which disables auth injection.
|
||||
guest_env["CODEX_CA_CERTIFICATE"] = (
|
||||
"/etc/ssl/certs/ca-certificates.crt"
|
||||
)
|
||||
if provider.template == "codex" and provider.forward_host_credentials:
|
||||
# Smolvm exec process trees do not reliably inherit the image
|
||||
# user's login environment. Pin CODEX_HOME to the same path
|
||||
# provision_provider_auth writes so Codex never falls back to a
|
||||
# root or unset home and shows the sign-in picker.
|
||||
guest_env["CODEX_HOME"] = "/home/node/.codex"
|
||||
|
||||
supervise_plan = None
|
||||
if bottle.supervise:
|
||||
@@ -189,6 +172,10 @@ def resolve_plan(
|
||||
forward_host_credentials=provider.forward_host_credentials,
|
||||
host_env=dict(os.environ),
|
||||
)
|
||||
merged_guest_env = dict(agent_provision.guest_env)
|
||||
for key, val in agent_provision.env_vars.items():
|
||||
merged_guest_env.setdefault(key, val)
|
||||
agent_provision = replace(agent_provision, guest_env=merged_guest_env)
|
||||
|
||||
return SmolmachinesBottlePlan(
|
||||
spec=spec,
|
||||
|
||||
@@ -42,8 +42,9 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
self.assertEqual("/tmp/Dockerfile.codex", plan.dockerfile)
|
||||
self.assertEqual(
|
||||
"/etc/ssl/certs/ca-certificates.crt",
|
||||
plan.guest_env["CODEX_CA_CERTIFICATE"],
|
||||
plan.env_vars["CODEX_CA_CERTIFICATE"],
|
||||
)
|
||||
self.assertEqual({}, plan.guest_env)
|
||||
self.assertEqual(("/home/node/.codex",), tuple(d.guest_path for d in plan.dirs))
|
||||
self.assertEqual(
|
||||
("/home/node/.codex/config.toml",),
|
||||
@@ -70,6 +71,7 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
"/run/codex-home/auth.json",
|
||||
{f.guest_path for f in plan.files},
|
||||
)
|
||||
self.assertEqual("/run/codex-home", plan.env_vars["CODEX_HOME"])
|
||||
self.assertEqual(1, len(plan.pre_copy))
|
||||
self.assertEqual(1, len(plan.verify))
|
||||
self.assertIn("CODEX_HOME=/run/codex-home", plan.verify[0].argv)
|
||||
|
||||
Reference in New Issue
Block a user
this should also be in the agent provisioner now, assuming we can evaluate
has_provider_authat that stage. If not we'll need a more generic hook to call into here/there should not be any logic specific to an specific type of agent in here anymore.