mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Fix custom CA login behind TLS-inspecting proxies (#20676)
Refs: https://linear.app/openai/issue/SE-6311/login-fails-for-experian-users-behind-tls-inspecting-proxy ## Summary - When a custom CA bundle is configured, force the shared `codex-client` reqwest builder onto rustls before registering custom roots. - Add the `rustls-tls-native-roots` reqwest feature so the rustls client preserves native roots plus the enterprise CA bundle. - Add subprocess TLS coverage for both a direct local TLS 1.3 server and a hermetic local CONNECT TLS-intercepting proxy that forwards a token-exchange-shaped POST to a local origin. ## Plain-language explanation Experian users are behind a TLS-inspecting proxy, so the login token exchange needs to trust the enterprise CA bundle from `CODEX_CA_CERTIFICATE` or `SSL_CERT_FILE`. Before this change, that custom-CA branch still used reqwest default TLS selection, which could fail in the proxy environment. Now, only when a custom CA is configured, Codex selects rustls first and then adds the custom CA roots, matching the validated behavior from the Experian test build while leaving normal system-root clients unchanged. The new regression test recreates the enterprise-proxy shape locally: the probe client sends an HTTPS `POST /oauth/token` through an explicit HTTP CONNECT proxy, the proxy presents a leaf certificate signed by a runtime-generated test CA, decrypts the request, forwards it to a local origin, and relays the `ok` response back. ## Scope note - The actual production fix is the first commit: `8368119282 Fix custom CA reqwest clients to use rustls`. - The second commit is integration-test coverage only. It generates all test CA and localhost certificate material at runtime. ## Validation - `cd codex-rs && cargo test -p codex-client --test ca_env posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle -- --nocapture` - `cd codex-rs && cargo test -p codex-client` - `cd codex-rs && cargo test -p codex-login` - `cd codex-rs && just fmt` - `cd codex-rs && just bazel-lock-update` - `cd codex-rs && just bazel-lock-check` - `cd codex-rs && just fix -p codex-client`
This commit is contained in:
@@ -14,10 +14,9 @@
|
||||
//! `TRUSTED CERTIFICATE` labels and bundles that also contain CRLs
|
||||
//! - return user-facing errors that explain how to fix misconfigured CA files
|
||||
//!
|
||||
//! It does not validate certificate chains or perform a handshake in tests. Its contract is
|
||||
//! narrower: produce a transport configuration whose root store contains every parseable
|
||||
//! certificate block from the configured PEM bundle, or fail early with a precise error before
|
||||
//! the caller starts network traffic.
|
||||
//! Its production contract is narrow: produce a transport configuration whose root store contains
|
||||
//! every parseable certificate block from the configured PEM bundle, or fail early with a precise
|
||||
//! error before the caller starts network traffic.
|
||||
//!
|
||||
//! In this module's test setup, a hermetic test is one whose result depends only on the CA file
|
||||
//! and environment variables that the test chose for itself. That matters here because the normal
|
||||
@@ -36,7 +35,8 @@
|
||||
//! - unit tests in this module cover env-selection logic without constructing a real client
|
||||
//! - subprocess integration tests under `tests/` cover real client construction through
|
||||
//! [`build_reqwest_client_for_subprocess_tests`], which disables reqwest proxy autodetection so
|
||||
//! the tests can observe custom-CA success and failure directly
|
||||
//! the tests can observe custom-CA success and failure directly, including one TLS handshake
|
||||
//! through a local HTTPS server
|
||||
//! - those subprocess tests also scrub inherited CA environment variables before launch so their
|
||||
//! result depends only on the test fixtures and env vars set by the test itself
|
||||
|
||||
@@ -266,12 +266,21 @@ fn maybe_build_rustls_client_config_with_env(
|
||||
/// This exists so tests can exercise precedence behavior deterministically without mutating the
|
||||
/// real process environment. It selects the CA bundle, delegates file parsing to
|
||||
/// [`ConfiguredCaBundle::load_certificates`], preserves the caller's chosen `reqwest` builder
|
||||
/// configuration, and finally registers each parsed certificate with that builder.
|
||||
/// configuration, forces rustls when a custom CA is configured, and finally registers each parsed
|
||||
/// certificate with that builder.
|
||||
fn build_reqwest_client_with_env(
|
||||
env_source: &dyn EnvSource,
|
||||
mut builder: reqwest::ClientBuilder,
|
||||
) -> Result<reqwest::Client, BuildCustomCaTransportError> {
|
||||
if let Some(bundle) = env_source.configured_ca_bundle() {
|
||||
ensure_rustls_crypto_provider();
|
||||
info!(
|
||||
source_env = bundle.source_env,
|
||||
ca_path = %bundle.path.display(),
|
||||
"building HTTP client with rustls backend for custom CA bundle"
|
||||
);
|
||||
builder = builder.use_rustls_tls();
|
||||
|
||||
let certificates = bundle.load_certificates()?;
|
||||
|
||||
for (idx, cert) in certificates.iter().enumerate() {
|
||||
|
||||
Reference in New Issue
Block a user