mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Fail closed when workload identity initialization fails (#38424)
## What changed - Treat workload identity environment markers as an explicit authentication selection, even when another process credential is present. - Return initialization errors from `AuthManager` and propagate them through commands and services instead of continuing with an unusable authentication state. - Make `codex login status` validate workload identity, keep the TUI on an embedded app server for local workload identity, and reject workload identity in `codex mcp-server`, where it is unsupported. ## Testing - Cover workload identity precedence and partial configuration errors. - Verify login status reports an unreadable identity assertion and app-server routing enforces the supported workload identity topology. GitOrigin-RevId: efc6b6b4cd4d61652617de82aaa3d7ffc75d6618
This commit is contained in:
+94
-27
@@ -51,6 +51,7 @@ use codex_login::AuthConfig;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_login::default_client::set_default_client_residency_requirement;
|
||||
use codex_login::enforce_login_restrictions;
|
||||
use codex_login::is_workload_identity_selected;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::auth::AuthMode;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
@@ -834,8 +835,18 @@ fn app_server_target_for_launch(
|
||||
explicit_remote_endpoint: Option<RemoteAppServerEndpoint>,
|
||||
default_daemon_socket: Option<AbsolutePathBuf>,
|
||||
can_reuse_implicit_local_daemon: bool,
|
||||
) -> AppServerTarget {
|
||||
match explicit_remote_endpoint {
|
||||
workload_identity_selected: bool,
|
||||
) -> std::io::Result<AppServerTarget> {
|
||||
if workload_identity_selected {
|
||||
if explicit_remote_endpoint.is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"workload identity must be configured on the remote app-server host",
|
||||
));
|
||||
}
|
||||
return Ok(AppServerTarget::Embedded);
|
||||
}
|
||||
Ok(match explicit_remote_endpoint {
|
||||
Some(endpoint) => AppServerTarget::Remote { endpoint },
|
||||
None if can_reuse_implicit_local_daemon => {
|
||||
default_daemon_socket.map_or(AppServerTarget::Embedded, |socket_path| {
|
||||
@@ -845,7 +856,20 @@ fn app_server_target_for_launch(
|
||||
})
|
||||
}
|
||||
None => AppServerTarget::Embedded,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn cloud_config_bundle_for_app_server_target(
|
||||
app_server_target: &AppServerTarget,
|
||||
bootstrap_config: &ConfigTomlLoadResult,
|
||||
codex_home: &Path,
|
||||
) -> std::io::Result<CloudConfigBundleLoader> {
|
||||
cloud_config_bundle_loader_for_storage(
|
||||
app_server_target
|
||||
.auth_config_for_cloud_loader(bootstrap_auth_config(codex_home, bootstrap_config)?),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn loader_overrides_are_default(loader_overrides: &LoaderOverrides) -> bool {
|
||||
@@ -939,12 +963,14 @@ pub async fn run_main(
|
||||
launch_loader_overrides.user_config_path = Some(user_config_path);
|
||||
launch_loader_overrides.user_config_profile = Some(profile_v2.clone());
|
||||
}
|
||||
let reuse_implicit_local_daemon = can_reuse_implicit_local_daemon(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
);
|
||||
let workload_identity_selected = is_workload_identity_selected();
|
||||
let reuse_implicit_local_daemon = !workload_identity_selected
|
||||
&& can_reuse_implicit_local_daemon(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
);
|
||||
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
|
||||
maybe_probe_default_daemon_socket(&codex_home).await
|
||||
} else {
|
||||
@@ -954,7 +980,8 @@ pub async fn run_main(
|
||||
explicit_remote_endpoint,
|
||||
default_daemon,
|
||||
reuse_implicit_local_daemon,
|
||||
);
|
||||
workload_identity_selected,
|
||||
)?;
|
||||
let remote_cwd_override = cli
|
||||
.cwd
|
||||
.clone()
|
||||
@@ -995,12 +1022,12 @@ pub async fn run_main(
|
||||
)
|
||||
.await;
|
||||
let bootstrap_config_toml = &bootstrap_config.config_toml;
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
app_server_target
|
||||
.auth_config_for_cloud_loader(bootstrap_auth_config(&codex_home, &bootstrap_config)?),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
let cloud_config_bundle = cloud_config_bundle_for_app_server_target(
|
||||
&app_server_target,
|
||||
&bootstrap_config,
|
||||
&codex_home,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
let cwd_override = if app_server_target.uses_remote_workspace() {
|
||||
None
|
||||
@@ -1090,11 +1117,15 @@ pub async fn run_main(
|
||||
)
|
||||
.await;
|
||||
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
app_server_target.auth_config_for_cloud_loader(config.auth_config()),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await;
|
||||
let cloud_config_bundle = if workload_identity_selected {
|
||||
cloud_config_bundle
|
||||
} else {
|
||||
cloud_config_bundle_loader_for_storage(
|
||||
app_server_target.auth_config_for_cloud_loader(config.auth_config()),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
let environment_manager = Arc::new(
|
||||
prepared_environment_manager
|
||||
.build(Some(local_runtime_paths), config.http_client_factory())
|
||||
@@ -1160,7 +1191,7 @@ pub async fn run_main(
|
||||
}
|
||||
}
|
||||
|
||||
if !app_server_target.uses_remote_workspace() {
|
||||
if !app_server_target.uses_remote_workspace() && !workload_identity_selected {
|
||||
#[allow(clippy::print_stderr)]
|
||||
if let Err(err) = enforce_login_restrictions(&config.auth_config()).await {
|
||||
eprintln!("{err}");
|
||||
@@ -1289,6 +1320,7 @@ async fn run_ratatui_app(
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
) -> color_eyre::Result<AppExitInfo> {
|
||||
let uses_remote_workspace = app_server_target.uses_remote_workspace();
|
||||
let workload_identity_selected = is_workload_identity_selected();
|
||||
color_eyre::install()?;
|
||||
|
||||
tooltips::announcement::prewarm(initial_config.http_client_factory());
|
||||
@@ -1379,7 +1411,9 @@ async fn run_ratatui_app(
|
||||
!uses_remote_workspace && should_show_trust_screen(&initial_config);
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut trust_decision_was_made = false;
|
||||
let login_status = if initial_config.model_provider.requires_openai_auth {
|
||||
let login_status = if workload_identity_selected {
|
||||
LoginStatus::AuthMode(AuthMode::Chatgpt)
|
||||
} else if initial_config.model_provider.requires_openai_auth {
|
||||
let Some(app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should exist when auth is required");
|
||||
};
|
||||
@@ -1430,12 +1464,12 @@ async fn run_ratatui_app(
|
||||
// If this onboarding run included the login step, always refresh the cloud config bundle
|
||||
// and rebuild config. This avoids missing newly available cloud-managed policy due to login
|
||||
// status detection edge cases.
|
||||
if show_login_screen && !uses_remote_workspace {
|
||||
if show_login_screen && !uses_remote_workspace && !workload_identity_selected {
|
||||
cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
initial_config.auth_config(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
}
|
||||
|
||||
// If the user made an explicit trust decision, or we showed the login flow, reload config
|
||||
@@ -2520,7 +2554,8 @@ mod tests {
|
||||
/*explicit_remote_endpoint*/ None,
|
||||
Some(socket_path.clone()),
|
||||
/*can_reuse_implicit_local_daemon*/ true,
|
||||
);
|
||||
/*workload_identity_selected*/ false,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
target,
|
||||
@@ -2542,7 +2577,8 @@ mod tests {
|
||||
Some(explicit_endpoint.clone()),
|
||||
Some(AbsolutePathBuf::relative_to_current_dir("default.sock")?),
|
||||
/*can_reuse_implicit_local_daemon*/ false,
|
||||
);
|
||||
/*workload_identity_selected*/ false,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
target,
|
||||
@@ -2563,12 +2599,43 @@ mod tests {
|
||||
/*explicit_remote_endpoint*/ None,
|
||||
Some(socket_path),
|
||||
/*can_reuse_implicit_local_daemon*/ false,
|
||||
);
|
||||
/*workload_identity_selected*/ false,
|
||||
)?;
|
||||
|
||||
assert_eq!(target, AppServerTarget::Embedded);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workload_identity_requires_an_embedded_app_server() -> color_eyre::Result<()> {
|
||||
let default_socket = AbsolutePathBuf::relative_to_current_dir("default.sock")?;
|
||||
assert_eq!(
|
||||
app_server_target_for_launch(
|
||||
/*explicit_remote_endpoint*/ None,
|
||||
Some(default_socket),
|
||||
/*can_reuse_implicit_local_daemon*/ true,
|
||||
/*workload_identity_selected*/ true,
|
||||
)?,
|
||||
AppServerTarget::Embedded
|
||||
);
|
||||
|
||||
let explicit_endpoint = RemoteAppServerEndpoint::UnixSocket {
|
||||
socket_path: AbsolutePathBuf::relative_to_current_dir("explicit.sock")?,
|
||||
};
|
||||
let error = app_server_target_for_launch(
|
||||
Some(explicit_endpoint),
|
||||
/*default_daemon_socket*/ None,
|
||||
/*can_reuse_implicit_local_daemon*/ false,
|
||||
/*workload_identity_selected*/ true,
|
||||
)
|
||||
.expect_err("remote hosts must own workload identity");
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"workload identity must be configured on the remote app-server host"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_reuse_implicit_local_daemon_requires_default_launch_config() -> color_eyre::Result<()> {
|
||||
let mut loader_overrides = LoaderOverrides::default();
|
||||
|
||||
@@ -1070,7 +1070,8 @@ mod tests {
|
||||
auth_config.clone(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await,
|
||||
.await
|
||||
.expect("test cloud config loader"),
|
||||
feedback: codex_feedback::CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::Cli;
|
||||
use crate::app_server_session::AppServerSession;
|
||||
use crate::legacy_core::config::ConfigBuilder;
|
||||
use crate::legacy_core::config::ConfigOverrides;
|
||||
use crate::legacy_core::config::bootstrap_auth_config;
|
||||
use crate::legacy_core::config::load_config_toml_with_layer_stack;
|
||||
use crate::legacy_core::config::resolve_oss_provider;
|
||||
use crate::legacy_core::config::resolve_profile_v2_config_path;
|
||||
@@ -22,7 +21,6 @@ use crate::named_session_lookup::SessionCollection;
|
||||
use crate::named_session_lookup::SessionNameLookupMode;
|
||||
use codex_app_server_protocol::Thread as AppServerThread;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_cloud_config::cloud_config_bundle_loader_for_storage;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::ConfigLoadOptions;
|
||||
use codex_config::LoaderOverrides;
|
||||
@@ -278,12 +276,14 @@ async fn start_app_server_for_archive_command(
|
||||
launch_loader_overrides.user_config_profile = Some(profile_v2.clone());
|
||||
}
|
||||
|
||||
let reuse_implicit_local_daemon = super::can_reuse_implicit_local_daemon(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
);
|
||||
let workload_identity_selected = codex_login::is_workload_identity_selected();
|
||||
let reuse_implicit_local_daemon = !workload_identity_selected
|
||||
&& super::can_reuse_implicit_local_daemon(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
);
|
||||
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
|
||||
super::maybe_probe_default_daemon_socket(codex_home.as_path()).await
|
||||
} else {
|
||||
@@ -293,7 +293,8 @@ async fn start_app_server_for_archive_command(
|
||||
explicit_remote_endpoint,
|
||||
default_daemon,
|
||||
reuse_implicit_local_daemon,
|
||||
);
|
||||
workload_identity_selected,
|
||||
)?;
|
||||
let remote_cwd_override = cli
|
||||
.cwd
|
||||
.clone()
|
||||
@@ -337,14 +338,12 @@ async fn start_app_server_for_archive_command(
|
||||
.await
|
||||
.wrap_err("failed to load config.toml")?;
|
||||
let config_toml = &bootstrap_config.config_toml;
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
app_server_target.auth_config_for_cloud_loader(bootstrap_auth_config(
|
||||
codex_home.as_path(),
|
||||
&bootstrap_config,
|
||||
)?),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
let cloud_config_bundle = super::cloud_config_bundle_for_app_server_target(
|
||||
&app_server_target,
|
||||
&bootstrap_config,
|
||||
codex_home.as_path(),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
let model_provider = if cli.oss {
|
||||
resolve_oss_provider(cli.oss_provider.as_deref(), config_toml)
|
||||
|
||||
Reference in New Issue
Block a user