mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
Track startup MITM CA env
This commit is contained in:
@@ -101,12 +101,14 @@ const MANAGED_MITM_CA_DIR: &str = "proxy";
|
||||
const MANAGED_MITM_CA_CERT: &str = "ca.pem";
|
||||
const MANAGED_MITM_CA_KEY: &str = "ca.key";
|
||||
const MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX: &str = "ca-bundle";
|
||||
const SSL_CERT_FILE_ENV_KEY: &str = "SSL_CERT_FILE";
|
||||
pub const SSL_CERT_DIR_ENV_KEY: &str = "SSL_CERT_DIR";
|
||||
|
||||
// Best-effort compatibility set for common child toolchains that accept a CA bundle path.
|
||||
// This is intentionally curated rather than pretending to cover every TLS client.
|
||||
pub const CUSTOM_CA_ENV_KEYS: [&str; 10] = [
|
||||
"CODEX_CA_CERTIFICATE",
|
||||
"SSL_CERT_FILE",
|
||||
SSL_CERT_FILE_ENV_KEY,
|
||||
"REQUESTS_CA_BUNDLE",
|
||||
"CURL_CA_BUNDLE",
|
||||
"NODE_EXTRA_CA_CERTS",
|
||||
@@ -122,6 +124,7 @@ pub const CUSTOM_CA_ENV_KEYS: [&str; 10] = [
|
||||
pub(crate) struct ManagedMitmCaTrustBundle {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) startup_env_values: HashMap<&'static str, String>,
|
||||
pub(crate) startup_cwd: PathBuf,
|
||||
}
|
||||
|
||||
fn managed_ca_paths() -> Result<(PathBuf, PathBuf)> {
|
||||
@@ -146,8 +149,11 @@ fn managed_ca_trust_bundle_for_cert_path(
|
||||
cert_path: &Path,
|
||||
env: &HashMap<&'static str, String>,
|
||||
) -> Result<ManagedMitmCaTrustBundle> {
|
||||
let startup_cwd =
|
||||
std::env::current_dir().context("failed to resolve startup cwd for managed MITM CA")?;
|
||||
let startup_env_values = CUSTOM_CA_ENV_KEYS
|
||||
.into_iter()
|
||||
.chain(std::iter::once(SSL_CERT_DIR_ENV_KEY))
|
||||
.filter_map(|key| {
|
||||
env.get(key)
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -160,6 +166,7 @@ fn managed_ca_trust_bundle_for_cert_path(
|
||||
Ok(ManagedMitmCaTrustBundle {
|
||||
path,
|
||||
startup_env_values,
|
||||
startup_cwd,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -512,12 +519,18 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
let managed_ca_cert_path = dir.path().join("ca.pem");
|
||||
fs::write(&managed_ca_cert_path, "managed ca\n").unwrap();
|
||||
let env = HashMap::from([("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string())]);
|
||||
let env = HashMap::from([
|
||||
("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string()),
|
||||
(SSL_CERT_DIR_ENV_KEY, "/tmp/startup-certs".to_string()),
|
||||
]);
|
||||
let trust_bundle =
|
||||
managed_ca_trust_bundle_for_cert_path(&managed_ca_cert_path, &env).unwrap();
|
||||
assert_eq!(
|
||||
trust_bundle.startup_env_values,
|
||||
HashMap::from([("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string())])
|
||||
HashMap::from([
|
||||
("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string()),
|
||||
(SSL_CERT_DIR_ENV_KEY, "/tmp/startup-certs".to_string()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ mod state;
|
||||
mod upstream;
|
||||
|
||||
pub use certs::CUSTOM_CA_ENV_KEYS;
|
||||
pub use certs::SSL_CERT_DIR_ENV_KEY;
|
||||
pub use certs::is_managed_mitm_ca_trust_bundle_path;
|
||||
pub use config::NetworkDomainPermission;
|
||||
pub use config::NetworkDomainPermissionEntry;
|
||||
@@ -46,6 +47,7 @@ pub use proxy::Args;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
|
||||
pub use proxy::DEFAULT_NO_PROXY_VALUE;
|
||||
pub use proxy::MITM_CA_ENV_ACTIVE_ENV_KEY;
|
||||
pub use proxy::NO_PROXY_ENV_KEYS;
|
||||
pub use proxy::NetworkProxy;
|
||||
pub use proxy::NetworkProxyBuilder;
|
||||
|
||||
@@ -308,6 +308,7 @@ impl NetworkProxyRuntimeSettings {
|
||||
let mitm_ca_trust_bundle = if config.network.mitm {
|
||||
let env = crate::certs::CUSTOM_CA_ENV_KEYS
|
||||
.into_iter()
|
||||
.chain(std::iter::once(crate::certs::SSL_CERT_DIR_ENV_KEY))
|
||||
.filter_map(|key| std::env::var(key).ok().map(|value| (key, value)))
|
||||
.collect();
|
||||
Some(crate::certs::managed_ca_trust_bundle(&env)?)
|
||||
@@ -376,6 +377,8 @@ pub const PROXY_URL_ENV_KEYS: &[&str] = &[
|
||||
|
||||
pub const ALL_PROXY_ENV_KEYS: &[&str] = &["ALL_PROXY", "all_proxy"];
|
||||
pub const PROXY_ACTIVE_ENV_KEY: &str = "CODEX_NETWORK_PROXY_ACTIVE";
|
||||
pub const MITM_CA_ENV_ACTIVE_ENV_KEY: &str = "CODEX_NETWORK_PROXY_MITM_CA_ENV_ACTIVE";
|
||||
const STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY: &str = "CODEX_NETWORK_PROXY_STARTUP_CA_ENV_KEYS_PRESENT";
|
||||
pub const ALLOW_LOCAL_BINDING_ENV_KEY: &str = "CODEX_NETWORK_ALLOW_LOCAL_BINDING";
|
||||
const ELECTRON_GET_USE_PROXY_ENV_KEY: &str = "ELECTRON_GET_USE_PROXY";
|
||||
const NODE_USE_ENV_PROXY_ENV_KEY: &str = "NODE_USE_ENV_PROXY";
|
||||
@@ -383,6 +386,8 @@ const NODE_USE_ENV_PROXY_ENV_KEY: &str = "NODE_USE_ENV_PROXY";
|
||||
const GIT_SSH_COMMAND_ENV_KEY: &str = "GIT_SSH_COMMAND";
|
||||
pub const PROXY_ENV_KEYS: &[&str] = &[
|
||||
PROXY_ACTIVE_ENV_KEY,
|
||||
MITM_CA_ENV_ACTIVE_ENV_KEY,
|
||||
STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY,
|
||||
ALLOW_LOCAL_BINDING_ENV_KEY,
|
||||
ELECTRON_GET_USE_PROXY_ENV_KEY,
|
||||
NODE_USE_ENV_PROXY_ENV_KEY,
|
||||
@@ -571,27 +576,55 @@ fn apply_proxy_env_overrides(
|
||||
}
|
||||
|
||||
if let Some(mitm_ca_trust_bundle) = mitm_ca_trust_bundle {
|
||||
env.insert(MITM_CA_ENV_ACTIVE_ENV_KEY.to_string(), "1".to_string());
|
||||
let managed_path = mitm_ca_trust_bundle.path.to_string_lossy().into_owned();
|
||||
let startup_ca_env_keys_present_in_child = ca_env_keys()
|
||||
.filter(|&key| {
|
||||
env.get(key)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some_and(|value| {
|
||||
mitm_ca_trust_bundle.startup_env_values.get(key) == Some(value)
|
||||
|| (value == &managed_path && is_tracked_startup_ca_env_key(env, key))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if startup_ca_env_keys_present_in_child.is_empty() {
|
||||
env.remove(STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY);
|
||||
} else {
|
||||
env.insert(
|
||||
STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY.to_string(),
|
||||
startup_ca_env_keys_present_in_child.join(","),
|
||||
);
|
||||
}
|
||||
for key in crate::certs::CUSTOM_CA_ENV_KEYS {
|
||||
if env
|
||||
.get(key)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some_and(|value| {
|
||||
value != &managed_path
|
||||
&& mitm_ca_trust_bundle.startup_env_values.get(key) != Some(value)
|
||||
value != &managed_path && !startup_ca_env_keys_present_in_child.contains(&key)
|
||||
})
|
||||
{
|
||||
// TODO(winston): Materialize policy-checked per-child bundles for readable
|
||||
// startup and command-scoped CA overrides. For now startup overrides are
|
||||
// replaced with the default bundle and later command-scoped overrides are
|
||||
// preserved, either of which can make intercepted TLS fail.
|
||||
continue;
|
||||
}
|
||||
env.insert(key.to_string(), managed_path.clone());
|
||||
}
|
||||
} else {
|
||||
env.remove(MITM_CA_ENV_ACTIVE_ENV_KEY);
|
||||
env.remove(STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
fn ca_env_keys() -> impl Iterator<Item = &'static str> {
|
||||
crate::certs::CUSTOM_CA_ENV_KEYS
|
||||
.into_iter()
|
||||
.chain(std::iter::once(crate::certs::SSL_CERT_DIR_ENV_KEY))
|
||||
}
|
||||
|
||||
fn is_tracked_startup_ca_env_key(env: &HashMap<String, String>, key: &str) -> bool {
|
||||
env.get(STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY)
|
||||
.is_some_and(|keys| keys.split(',').any(|tracked_key| tracked_key == key))
|
||||
}
|
||||
|
||||
impl NetworkProxy {
|
||||
pub fn builder() -> NetworkProxyBuilder {
|
||||
NetworkProxyBuilder::default()
|
||||
@@ -643,7 +676,7 @@ impl NetworkProxy {
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
let runtime_settings = self.runtime_settings();
|
||||
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
|
||||
// managed MITM CA vars preserve child-scoped overrides after proxy startup.
|
||||
// managed MITM CA vars preserve command-scoped overrides after proxy startup.
|
||||
apply_proxy_env_overrides(
|
||||
env,
|
||||
self.http_addr,
|
||||
@@ -1110,6 +1143,7 @@ mod tests {
|
||||
let mitm_ca_trust_bundle = crate::certs::ManagedMitmCaTrustBundle {
|
||||
path: mitm_ca_trust_bundle_path.to_path_buf(),
|
||||
startup_env_values: HashMap::new(),
|
||||
startup_cwd: Path::new("/tmp").to_path_buf(),
|
||||
};
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
@@ -1129,18 +1163,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_preserves_command_scoped_mitm_ca_override() {
|
||||
let command_ca_bundle_path = "/tmp/command-ca.pem".to_string();
|
||||
fn apply_proxy_env_overrides_tracks_rewritten_startup_mitm_ca_override() {
|
||||
let startup_ca_bundle_path = "/tmp/startup-ca.pem".to_string();
|
||||
let mut env = HashMap::from([(
|
||||
"REQUESTS_CA_BUNDLE".to_string(),
|
||||
command_ca_bundle_path.clone(),
|
||||
startup_ca_bundle_path.clone(),
|
||||
)]);
|
||||
let mitm_ca_trust_bundle_path = Path::new("/tmp/codex-proxy/ca-bundle.pem");
|
||||
let mitm_ca_trust_bundle = crate::certs::ManagedMitmCaTrustBundle {
|
||||
path: mitm_ca_trust_bundle_path.to_path_buf(),
|
||||
startup_env_values: HashMap::new(),
|
||||
startup_env_values: HashMap::from([("REQUESTS_CA_BUNDLE", startup_ca_bundle_path)]),
|
||||
startup_cwd: Path::new("/tmp").to_path_buf(),
|
||||
};
|
||||
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3128),
|
||||
@@ -1150,11 +1184,37 @@ mod tests {
|
||||
Some(&mitm_ca_trust_bundle),
|
||||
);
|
||||
|
||||
assert_eq!(env.get("REQUESTS_CA_BUNDLE"), Some(&command_ca_bundle_path));
|
||||
assert_eq!(
|
||||
env.get("REQUESTS_CA_BUNDLE"),
|
||||
Some(&mitm_ca_trust_bundle_path.display().to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get(STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY),
|
||||
Some(&"REQUESTS_CA_BUNDLE".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("SSL_CERT_FILE"),
|
||||
Some(&mitm_ca_trust_bundle_path.display().to_string())
|
||||
);
|
||||
|
||||
env.insert(
|
||||
"REQUESTS_CA_BUNDLE".to_string(),
|
||||
"/tmp/command-ca.pem".to_string(),
|
||||
);
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3128),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
Some(&mitm_ca_trust_bundle),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
env.get("REQUESTS_CA_BUNDLE"),
|
||||
Some(&"/tmp/command-ca.pem".to_string())
|
||||
);
|
||||
assert_eq!(env.get(STARTUP_CA_ENV_KEYS_PRESENT_ENV_KEY), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user