-
Notifications
You must be signed in to change notification settings - Fork 6.6k
feat: add mTLS support for model providers #7650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| Err(e) => { | ||
| tracing::error!("Failed to apply TLS configuration: {}", e); | ||
| // Fall back to base builder without TLS | ||
| create_base_client_builder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail mTLS client build instead of falling back silently
When apply_tls_config returns an error (e.g., a missing/ unreadable client certificate or key), build_configured_reqwest_client logs and reconstructs a default client instead of surfacing the failure. That means requests continue without the configured client identity or CA; on servers where client certificates are optional, mTLS is silently skipped rather than failing fast, undermining the security guarantee users expect when they supply certs. The code should propagate the error instead of returning a fallback client.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted in the commit description, the existing code already had a fallback on builder failures.
That said, agreed. Making create_default_client() fallible is a bigger change across the codebase however. So hopeful these fixes can be shipped separately?
Perhaps failing harder is okay for the time being?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree feeling harder seems like the right choice. Is there any commentary on the PR that introduced the original behavior to explain why it didn't do this before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bolinfest I traced it back to #3110 through a couple of refactors and other changes. Not much there.
It'd require updating the callsites to handle the error. Doesn't look too tricky to move that error one way up the stack. Not sure how the UI etc would deal with it.
|
Thanks for the contribution. We've updated our contribution guidelines to clarify that we're currently accepting contributions for bugs and security fixes, but we're not generally accepting new features at this time. We need to make sure that all new features compose well with both existing and upcoming features and fit into our roadmap. If you would like to propose a new feature, please file or upvote an enhancement request in the issue tracker. We will generally prioritize new features based on community feedback. |
The mTLS implementation was failing with "tlsv13 alert certificate required" errors despite certificates loading correctly. The root cause was Cargo's feature unification - when multiple crates depend on reqwest, Cargo enables the union of all requested features. Since some crates were using default features (which includes native-tls) and others specified rustls, both TLS backends were being compiled and linked, causing unpredictable behavior during TLS handshakes. The fix ensures rustls-only is used across the entire workspace by setting `default-features = false` and explicitly specifying `features = ["rustls-tls"]` for all reqwest dependencies. This is important because the mTLS code uses `Identity::from_pem()` which only exists with rustls. Added a verification script (`just check-tls`) that fails if openssl-sys appears in the dependency tree.
d81b29e to
6370c79
Compare
bolinfest
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please also update https://github.com/openai/codex/blob/main/docs/config.md as part of this PR?
codex-rs/core/src/default_client.rs
Outdated
| use std::sync::Mutex; | ||
| use std::sync::OnceLock; | ||
|
|
||
| /// TLS configuration for HTTP clients |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also go in model_provider_tls_config.rs? Declaring it before this other stuff, (like the constant with the detailed docstring) doesn't feel like the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bolinfest I put this here because it's not strictly tied to model providers. It's possible to build and use a configured reqwest client that uses mTLS.
I'm guessing it'd be nice to make OTEL's http support use this client eventually too. It currently does its own thing.
| Err(e) => { | ||
| tracing::error!("Failed to apply TLS configuration: {}", e); | ||
| // Fall back to base builder without TLS | ||
| create_base_client_builder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree feeling harder seems like the right choice. Is there any commentary on the PR that introduced the original behavior to explain why it didn't do this before?
codex-rs/core/src/default_client.rs
Outdated
| path.clone() | ||
| } else { | ||
| // Resolve relative to $CODEX_HOME (defaults to ~/.codex) | ||
| let codex_home = std::env::var("CODEX_HOME") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should never read env var inline: should read the codex_home field off of Config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked how other parts in the codebase do this. Matched it.
Adds mutual TLS (mTLS) authentication support for model provider connections, allowing Codex to connect to endpoints requiring client certificates. The implementation uses rustls exclusively - native-tls is not supported due to Cargo's feature unification causing both TLS backends to be linked when any dependency enables native-tls. Configuration is done via TOML for each model provider: ```toml [model-providers.my-provider] base_url = "https://example.com" tls.ca-certificate = "/path/to/ca.pem" tls.client-certificate = "/path/to/client.pem" tls.client-private-key = "/path/to/key.pem" ``` Paths can be absolute or relative to `~/.codex/`. All three TLS fields are optional - omit CA certificate to use system roots, omit client cert/key for server-only TLS. The implementation gracefully falls back to a non-TLS client if certificate loading fails (with error logging), ensuring connectivity isn't completely broken by misconfigured certificates. This matches the previous behavior on reqwest builder failures.
Adds support for specifying TLS certificate paths via environment variables in model provider configuration. Each certificate/key path field now has a corresponding `-env` variant that names an environment variable containing the path. New config fields: - `ca-certificate-env`: env var for CA certificate path - `client-certificate-env`: env var for client certificate path - `client-private-key-env`: env var for client private key path If both a direct path and env var are specified, the env var takes precedence (if set and non-empty). This follows the same pattern used by `env_http_headers` for HTTP headers in the codebase. Example config: ```toml [model_providers.my-provider.tls] ca-certificate-env = "MY_CA_CERT_PATH" client-certificate-env = "MY_CLIENT_CERT_PATH" client-private-key-env = "MY_CLIENT_KEY_PATH" ```
6370c79 to
e0fb260
Compare
|
@bolinfest Two thoughts/questions:
|
Adds mutual TLS (mTLS) authentication support for model provider connections, enabling Codex to connect to endpoints requiring client certificates.
tlsconfig block for CA and client certificatesjust check-tlsto verify no native-tls in dependency tree