Integrate config-server policies with startup and authentication (#57175)

## What changed

- Use the managed configuration loader in CLI, exec, TUI, and app-server configuration flows so `requirements.toml` can select a config server.
- Sign in to the config server when needed during interactive TUI startup and CLI login, reload configuration after CLI sign-in, and clear config-server credentials on CLI logout.
- Preserve the app-server's config-server settings and refresh worker across OpenAI login and logout.
- Require config-server settings to be applied when loading CLI and doctor configuration.

## Testing

Add app-server integration tests using a local HTTPS config server to verify managed provider and instruction settings at startup, preservation across API-key login and logout, and cache reuse after restart and thread resume.

GitOrigin-RevId: 27ee81f7943d2290a5c3fcc0ac556579ec63f8b4
This commit is contained in:
smurching-oai
2026-10-26 18:14:57 +00:00
committed by copyberry
parent 619531225b
commit ec513795a6
13 changed files with 401 additions and 39 deletions
+4 -2
View File
@@ -1,7 +1,7 @@
use anyhow::Context;
use anyhow::Result;
use codex_app_server::in_process::EmbeddedNetworkPolicy;
use codex_cloud_config::chatgpt_config_bundle_loader_for_storage;
use codex_cloud_config::managed_config_bundle_loader_for_storage;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLoadOptions;
use codex_core::config::Config;
@@ -27,6 +27,7 @@ pub async fn load_config(
.build()
.await
.context("failed to load configuration")?;
config.config_layer_stack.ensure_config_server_applied()?;
EmbeddedNetworkPolicy::default().activate(&mut config);
Ok(config)
}
@@ -58,11 +59,12 @@ pub async fn config_builder(
)
.await
.context("failed to load bootstrap configuration")?;
let cloud_config_bundle = chatgpt_config_bundle_loader_for_storage(
let cloud_config_bundle = managed_config_bundle_loader_for_storage(
network_policy.bind_bootstrap_auth(
bootstrap_auth_config(codex_home.as_path(), &bootstrap_config)
.context("failed to resolve cloud configuration authentication")?,
),
bootstrap_config.config_layer_stack.requirements_toml(),
/*enable_codex_api_key_env*/ false,
)
.await
+4 -2
View File
@@ -628,7 +628,7 @@ async fn load_config(
..config_overrides_from_interactive(interactive, arg0_paths)
};
crate::cloud_config::config_builder(
let config = crate::cloud_config::config_builder(
&root_config_overrides,
LoaderOverrides::default(),
overrides,
@@ -636,7 +636,9 @@ async fn load_config(
.await?
.build()
.await
.context("failed to load Codex config")
.context("failed to load Codex config")?;
config.config_layer_stack.ensure_config_server_applied()?;
Ok(config)
}
fn config_overrides_from_interactive(
+1 -1
View File
@@ -18,7 +18,7 @@ pub use login::read_api_key_from_stdin;
pub use login::run_login_status;
pub use login::run_login_with_access_token;
pub use login::run_login_with_api_key;
pub use login::run_login_with_chatgpt;
pub use login::run_login_with_browser;
pub use login::run_login_with_device_code;
pub use login::run_login_with_device_code_fallback_to_browser;
pub use login::run_logout;
+79 -7
View File
@@ -172,6 +172,27 @@ pub async fn login_with_chatgpt(
server.block_until_done().await
}
/// Signs into the config server if needed, then reloads configuration.
async fn sign_in_to_config_server_and_reload_config(
config: &mut Config,
cli_config_overrides: &CliConfigOverrides,
) -> std::io::Result<()> {
let requirements = config.config_layer_stack.requirements_toml();
let network = codex_app_server::in_process::EmbeddedNetworkPolicy::load(
&codex_config::LoaderOverrides::default(),
)
.await;
let bootstrap_auth = network.bind_bootstrap_auth(config.auth_config());
let auth_route_config = bootstrap_auth.auth_route_config.clone();
codex_cloud_config::ensure_config_server_login(bootstrap_auth, requirements).await?;
*config = crate::cloud_config::load_config(cli_config_overrides, Default::default())
.await
.map_err(|error| std::io::Error::other(format!("{error:#}")))?;
network.activate(config);
config.application_auth_route_config = Some(auth_route_config);
Ok(())
}
fn finish_login(result: std::io::Result<()>) -> ! {
match result {
Ok(_) => {
@@ -187,17 +208,28 @@ fn finish_login(result: std::io::Result<()>) -> ! {
async fn run_interactive_login<F>(
cli_config_overrides: CliConfigOverrides,
login: impl FnOnce(Config) -> F,
chatgpt_login: impl FnOnce(Config) -> F,
) -> !
where
F: std::future::Future<Output = std::io::Result<()>>,
{
let config = load_config_or_exit(cli_config_overrides).await;
let mut config = load_config_or_exit(cli_config_overrides.clone()).await;
if !config
.config_layer_stack
.requirements_toml()
.requires_remote_config()
{
let _login_log_guard = init_login_file_logging(&config);
finish_login(chatgpt_login(config).await);
}
let result =
sign_in_to_config_server_and_reload_config(&mut config, &cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
finish_login(login(config).await)
finish_login(result)
}
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
pub async fn run_login_with_browser(cli_config_overrides: CliConfigOverrides) -> ! {
run_interactive_login(cli_config_overrides, |config| async move {
tracing::info!("starting browser login flow");
@@ -226,7 +258,7 @@ pub async fn run_login_with_api_key(
cli_config_overrides: CliConfigOverrides,
api_key: String,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let config = load_managed_login_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting api key login flow");
@@ -259,7 +291,7 @@ pub async fn run_login_with_access_token(
cli_config_overrides: CliConfigOverrides,
access_token: String,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let config = load_managed_login_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting access token login flow");
@@ -521,9 +553,28 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
}
}
async fn logout_config_server(config: &Config) -> std::io::Result<bool> {
match codex_cloud_config::managed_config_auth_manager(
config.auth_config(),
config.config_layer_stack.requirements_toml(),
)? {
Some(manager) => manager.logout().await,
None => Ok(false),
}
}
pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let auth_route_config = config.auth_route_config();
let config_server_logout = if config
.config_layer_stack
.requirements_toml()
.requires_remote_config()
{
logout_config_server(&config).await
} else {
Ok(false)
};
// Cleanup is independent of the current feature gate. Retain the grant lock
// through account removal so another process cannot commit a late login.
@@ -590,7 +641,14 @@ pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
false
};
if logged_out || cleared_bedrock_config {
let config_server_logged_out = match config_server_logout {
Ok(removed) => removed,
Err(error) => {
eprintln!("Error logging out: {error}");
std::process::exit(1);
}
};
if logged_out || config_server_logged_out || cleared_bedrock_config {
eprintln!("Successfully logged out");
} else {
eprintln!("Not logged in");
@@ -598,6 +656,20 @@ pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
std::process::exit(0);
}
async fn load_managed_login_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
let mut config = load_config_or_exit(cli_config_overrides.clone()).await;
if config
.config_layer_stack
.requirements_toml()
.requires_remote_config()
&& let Err(error) =
sign_in_to_config_server_and_reload_config(&mut config, &cli_config_overrides).await
{
finish_login(Err(error));
}
config
}
async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
let cli_overrides = match cli_config_overrides.parse_overrides() {
Ok(v) => v,
+2 -2
View File
@@ -16,7 +16,7 @@ use codex_cli::read_api_key_from_stdin;
use codex_cli::run_login_status;
use codex_cli::run_login_with_access_token;
use codex_cli::run_login_with_api_key;
use codex_cli::run_login_with_chatgpt;
use codex_cli::run_login_with_browser;
use codex_cli::run_login_with_device_code;
use codex_cli::run_logout;
use codex_cloud_tasks::Cli as CloudTasksCli;
@@ -1699,7 +1699,7 @@ async fn cli_main(
let access_token = read_access_token_from_stdin();
run_login_with_access_token(login_cli.config_overrides, access_token).await;
} else {
run_login_with_chatgpt(login_cli.config_overrides).await;
run_login_with_browser(login_cli.config_overrides).await;
}
}
}