Add managed configuration loaders for custom config servers (#56314)

## What changed

- Add loaders that select `config_server` when configured and otherwise use the existing ChatGPT bundle source. Fetch custom bundles from `<base_url>/codex/config/bundle` without redirects or request logging.
- Support optional OAuth sessions without interactive login during bundle fetches, recover credentials after a `401`, and scope cached bundles to the endpoint and authorization identity.
- Restrict bootstrap network access to configuration and OAuth endpoints using local administrator rules when available.
- Return generic bundle validation errors so invalid TOML, requirements, and provider settings do not expose configuration contents.

## Testing

Add tests for missing configuration-server sign-in, replacement OAuth credentials, endpoint-specific cache reuse, and validation errors that omit sensitive values while preserving cached bundles.

GitOrigin-RevId: 56590a9e66340540666ca21590bef46c495e0e33
This commit is contained in:
smurching-oai
2026-10-23 00:54:06 +00:00
committed by copyberry
parent e103bbc06f
commit f3a90545ae
13 changed files with 608 additions and 33 deletions
@@ -1,9 +1,11 @@
use crate::backend::BackendBundleClient;
use crate::chatgpt::ChatGptSource;
use crate::config_server::config_server_http_client_factory;
use crate::config_source::BundleSource;
use crate::service::CLOUD_CONFIG_BUNDLE_TIMEOUT;
use crate::service::CloudConfigBundleService;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigRequirementsToml;
use codex_http_client::HttpClientFactory;
use codex_login::AuthConfig;
use codex_login::AuthManager;
@@ -12,6 +14,64 @@ use std::sync::Arc;
use tokio::task::AbortHandle;
use tokio::task::JoinHandle;
pub fn managed_config_auth_manager(
auth: AuthConfig,
requirements: &ConfigRequirementsToml,
) -> std::io::Result<Option<codex_login::OAuthSessionManager>> {
let Some(service_config) = requirements.config_server.as_ref() else {
return Ok(None);
};
let factory = config_server_http_client_factory(service_config, &auth.auth_route_config);
crate::config_server_auth_manager(service_config, auth.codex_home, &factory)
}
pub fn managed_config_bundle_loader(
auth_manager: Arc<AuthManager>,
chatgpt_base_url: String,
codex_home: PathBuf,
factory: HttpClientFactory,
requirements: &ConfigRequirementsToml,
) -> CloudConfigBundleLoader {
if let Some(service_config) = requirements.config_server.as_ref() {
let factory = config_server_http_client_factory(
service_config,
&codex_login::AuthRouteConfig::from_http_client_factory(factory),
);
return crate::config_server_bundle_loader(service_config.clone(), codex_home, factory);
}
let endpoint = codex_backend_client::Client::new(chatgpt_base_url.clone(), factory.clone())
.config_bundle_url();
let policy = factory
.network_policy()
.clone()
.restrict_to_endpoints(endpoint.parse().into_iter().collect());
cloud_config_bundle_loader(
auth_manager,
chatgpt_base_url,
codex_home,
factory.with_network_policy(policy),
)
}
pub async fn managed_config_bundle_loader_for_storage(
auth_config: AuthConfig,
requirements: &ConfigRequirementsToml,
enable_codex_api_key_env: bool,
) -> std::io::Result<CloudConfigBundleLoader> {
if let Some(service_config) = requirements.config_server.as_ref() {
let factory =
config_server_http_client_factory(service_config, &auth_config.auth_route_config);
return Ok(crate::config_server_bundle_loader(
service_config.clone(),
auth_config.codex_home,
factory,
));
}
let service =
cloud_config_bundle_service_for_storage(auth_config, enable_codex_api_key_env).await?;
Ok(cloud_config_bundle_loader_for_service(service).0)
}
struct CloudConfigBundleLoaderLifetime<C: BundleSource> {
service: Arc<CloudConfigBundleService<C>>,
refresh_task: AbortHandle,