mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Add rotating stdin credentials for remote exec-server (#54434)
## What changed - Add `--executor-credential-stdin` to read newline-delimited JSON credentials bound to the configured environment. Require an initial credential within 30 seconds and accept subsequent rotations. - Reject expired credentials on each request, and stop the executor on invalid input or stdin closure. Limit credential frames to 16 KiB. - Use scoped bearer authentication for registry requests without account credential fallback or WebSocket authentication headers. Skip workload identity loading and configured telemetry exporters in this mode. ## Testing Add unit and integration coverage for rotation, expiry, malformed frames, environment mismatches, stdin closure, registry-only authentication, credential redaction, and telemetry exporter isolation. GitOrigin-RevId: 30a94478165395144a765450f5ec6b3697acdd55
This commit is contained in:
@@ -26,6 +26,7 @@ use codex_utils_cli::CliConfigOverrides;
|
||||
use codex_websocket_auth::WebsocketAuthArgs;
|
||||
|
||||
use crate::exec_server_auth;
|
||||
use crate::exec_server_credential::ExecutorCredentialPipe;
|
||||
use crate::exec_server_telemetry;
|
||||
|
||||
/// [EXPERIMENTAL] Run the standalone exec-server service.
|
||||
@@ -115,6 +116,15 @@ pub(super) struct ExecServerCommand {
|
||||
)]
|
||||
pub(super) use_agent_identity_auth: bool,
|
||||
|
||||
/// Read rotating executor credentials from stdin; exit when the pipe closes.
|
||||
#[arg(
|
||||
long = "executor-credential-stdin",
|
||||
requires = "exec_server_remote",
|
||||
conflicts_with_all = ["use_agent_identity_auth", "aws_sigv4"],
|
||||
global = true
|
||||
)]
|
||||
pub(super) executor_credential_stdin: bool,
|
||||
|
||||
/// Sign Direct registration and WebSocket handshake requests with AWS SigV4.
|
||||
#[arg(long = "aws-sigv4", requires = "exec_server_remote", global = true)]
|
||||
pub(super) aws_sigv4: bool,
|
||||
@@ -196,12 +206,31 @@ impl ExecServerCommand {
|
||||
let config = load_exec_server_config(
|
||||
root_config_overrides,
|
||||
strict_config,
|
||||
/*enable_workload_identity*/ true,
|
||||
/*enable_workload_identity*/ !self.executor_credential_stdin,
|
||||
)
|
||||
.await?;
|
||||
let direct_transport = self.remote_transport == ExecServerRemoteTransport::Direct;
|
||||
let (_otel, telemetry) = exec_server_telemetry::init(Some(&config));
|
||||
let auth_provider = if self.aws_sigv4 {
|
||||
// Configured exporters may contain account-wide credentials or client keys.
|
||||
// The scoped executor must not initialize those exporters.
|
||||
let telemetry_config = (!self.executor_credential_stdin).then_some(&config);
|
||||
let (_otel, telemetry) = exec_server_telemetry::init(telemetry_config);
|
||||
let mut credential_provider = None;
|
||||
let mut parent_lifetime = if self.exit_on_stdin_close {
|
||||
exec_server_telemetry::ParentLifetime::StdinPipe
|
||||
} else {
|
||||
exec_server_telemetry::ParentLifetime::Independent
|
||||
};
|
||||
let auth_provider: codex_api::SharedAuthProvider = if self.executor_credential_stdin {
|
||||
let ExecutorCredentialPipe {
|
||||
provider,
|
||||
parent_closed,
|
||||
} = ExecutorCredentialPipe::from_stdin(environment_id.trim().to_string());
|
||||
provider.wait_for_initial().await?;
|
||||
parent_lifetime =
|
||||
exec_server_telemetry::ParentLifetime::MonitoredStdin(parent_closed);
|
||||
credential_provider = Some(Arc::clone(&provider));
|
||||
provider
|
||||
} else if self.aws_sigv4 {
|
||||
exec_server_auth::aws_sigv4_auth_provider(
|
||||
codex_aws_auth::AwsAuthConfig {
|
||||
profile: self.aws_profile,
|
||||
@@ -230,16 +259,14 @@ impl ExecServerCommand {
|
||||
auth_provider,
|
||||
config.http_client_factory(),
|
||||
)?;
|
||||
if self.executor_credential_stdin {
|
||||
remote_config = remote_config.with_executor_credential_auth()?;
|
||||
}
|
||||
if let Some(name) = self.name {
|
||||
remote_config.name = name;
|
||||
}
|
||||
remote_config.request_dispatch_mode = self.request_dispatch_mode;
|
||||
let remote_config = remote_config.with_telemetry(telemetry);
|
||||
let parent_lifetime = if self.exit_on_stdin_close {
|
||||
exec_server_telemetry::ParentLifetime::StdinPipe
|
||||
} else {
|
||||
exec_server_telemetry::ParentLifetime::Independent
|
||||
};
|
||||
let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel();
|
||||
#[cfg(target_os = "macos")]
|
||||
let runtime_paths = runtime_paths.with_allowed_symlinked_codex_home(
|
||||
@@ -248,7 +275,7 @@ impl ExecServerCommand {
|
||||
&config.codex_home,
|
||||
),
|
||||
);
|
||||
exec_server_telemetry::run_until_shutdown(
|
||||
let result = exec_server_telemetry::run_until_shutdown(
|
||||
async move {
|
||||
let shutdown = async move {
|
||||
let _ = shutdown_receiver.await;
|
||||
@@ -276,7 +303,11 @@ impl ExecServerCommand {
|
||||
parent_lifetime,
|
||||
exec_server_telemetry::ShutdownBehavior::Graceful(shutdown_sender),
|
||||
)
|
||||
.await
|
||||
.await;
|
||||
if let Some(provider) = credential_provider {
|
||||
provider.check_pipe_failure()?;
|
||||
}
|
||||
result
|
||||
} else {
|
||||
let config_result = load_exec_server_config(
|
||||
root_config_overrides,
|
||||
|
||||
Reference in New Issue
Block a user