mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Add ChatGPT token stdin authentication to exec-server (#59569)
## What changed - Add `--chatgpt-token-stdin` for remote HTTPS registration and optional `--chatgpt-account-id`, which must match the token's account claim when present. Read a bounded, newline-terminated token from a pipe without persisting it to `auth.json`. - Load managed configuration using the supplied account, enforce account restrictions, and preserve configured policy-server authentication. Skip ChatGPT policy lookup only for known plans that do not require managed configuration. - Allow `--exit-on-stdin-close` to cancel pending policy loading, and redact the registration bearer from registry diagnostics. - Reject incompatible stdin credential options, including flags placed on opposite sides of `forward`, and disallow ChatGPT token stdin authentication with `forward`. ## Testing Add coverage for account-claim validation, policy selection, policy-server sign-in requirements, shutdown during policy loading, credential flag conflicts, and bearer redaction. GitOrigin-RevId: b6b50565e1f150b96edf3cee796909a991f1b47f
This commit is contained in:
@@ -15,9 +15,11 @@ use codex_websocket_auth::WebsocketAuthArgs;
|
||||
|
||||
use crate::config::load_exec_server_config;
|
||||
use crate::config::load_exec_server_remote_auth_provider;
|
||||
use crate::config::read_chatgpt_stdin_auth;
|
||||
use crate::exec_server_auth;
|
||||
use crate::exec_server_credential::ExecutorCredentialPipe;
|
||||
use crate::exec_server_telemetry;
|
||||
use crate::exec_server_telemetry::ParentLifetime;
|
||||
|
||||
/// [EXPERIMENTAL] Run the standalone exec-server service.
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -124,6 +126,18 @@ pub struct ExecServerCommand {
|
||||
)]
|
||||
executor_credential_stdin: bool,
|
||||
|
||||
/// Read a ChatGPT access token from stdin for HTTPS registration.
|
||||
#[arg(
|
||||
long,
|
||||
requires = "exec_server_remote",
|
||||
conflicts_with_all = ["use_agent_identity_auth", "aws_sigv4", "executor_credential_stdin", "registration_bearer_stdin"]
|
||||
)]
|
||||
chatgpt_token_stdin: bool,
|
||||
|
||||
/// ChatGPT account ID. Must match the token's account claim when present.
|
||||
#[arg(long, value_name = "ID", requires = "chatgpt_token_stdin")]
|
||||
chatgpt_account_id: Option<String>,
|
||||
|
||||
/// Sign Direct registration and WebSocket handshake requests with AWS SigV4.
|
||||
#[arg(long = "aws-sigv4", requires = "exec_server_remote", global = true)]
|
||||
aws_sigv4: bool,
|
||||
@@ -207,30 +221,55 @@ impl ExecServerCommand {
|
||||
.registration_bearer_stdin
|
||||
.then(codex_exec_server::registration_auth_from_stdin)
|
||||
.transpose()?;
|
||||
let config =
|
||||
load_exec_server_config(root_config_overrides, strict_config, enable_cloud_config)
|
||||
.await?;
|
||||
let stdin_auth = if self.chatgpt_token_stdin {
|
||||
codex_http_client::validate_https_endpoint_url(&base_url, "ChatGPT token registry")
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
Some(read_chatgpt_stdin_auth(self.chatgpt_account_id.take())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let config = load_exec_server_config(
|
||||
root_config_overrides,
|
||||
strict_config,
|
||||
enable_cloud_config,
|
||||
stdin_auth.as_ref(),
|
||||
);
|
||||
let mut parent_lifetime = if self.exit_on_stdin_close && stdin_auth.is_some() {
|
||||
ParentLifetime::MonitoredStdin(exec_server_telemetry::monitor_stdin_close())
|
||||
} else if self.exit_on_stdin_close {
|
||||
ParentLifetime::StdinPipe
|
||||
} else {
|
||||
ParentLifetime::Independent
|
||||
};
|
||||
let config = match &mut parent_lifetime {
|
||||
ParentLifetime::MonitoredStdin(closed) => tokio::select! {
|
||||
biased;
|
||||
_ = closed => return Ok(()),
|
||||
config = config => config?,
|
||||
},
|
||||
ParentLifetime::StdinPipe | ParentLifetime::Independent => config.await?,
|
||||
};
|
||||
let direct_transport = self.remote_transport == ExecServerRemoteTransport::Direct;
|
||||
// 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);
|
||||
parent_lifetime = ParentLifetime::MonitoredStdin(parent_closed);
|
||||
credential_provider = Some(Arc::clone(&provider));
|
||||
provider
|
||||
} else if let Some(auth) = stdin_auth {
|
||||
anyhow::ensure!(
|
||||
config.auth_config().allows_auth(&auth),
|
||||
"ChatGPT authentication is not permitted for this account"
|
||||
);
|
||||
codex_model_provider::auth_provider_from_auth(&auth)
|
||||
} else if self.aws_sigv4 {
|
||||
exec_server_auth::aws_sigv4_auth_provider(
|
||||
codex_aws_auth::AwsAuthConfig {
|
||||
@@ -264,6 +303,8 @@ impl ExecServerCommand {
|
||||
)?;
|
||||
if self.executor_credential_stdin || self.registration_bearer_stdin {
|
||||
remote_config = remote_config.with_executor_credential_auth()?;
|
||||
} else if self.chatgpt_token_stdin {
|
||||
remote_config = remote_config.with_registry_bearer_redaction();
|
||||
}
|
||||
if let Some(name) = self.name {
|
||||
remote_config.name = name;
|
||||
@@ -312,9 +353,13 @@ impl ExecServerCommand {
|
||||
}
|
||||
result
|
||||
} else {
|
||||
let config_result =
|
||||
load_exec_server_config(root_config_overrides, strict_config, enable_cloud_config)
|
||||
.await;
|
||||
let config_result = load_exec_server_config(
|
||||
root_config_overrides,
|
||||
strict_config,
|
||||
enable_cloud_config,
|
||||
/*stdin_auth*/ None,
|
||||
)
|
||||
.await;
|
||||
let config = if strict_config {
|
||||
Some(config_result?)
|
||||
} else {
|
||||
@@ -346,7 +391,7 @@ impl ExecServerCommand {
|
||||
self.request_dispatch_mode,
|
||||
websocket_auth,
|
||||
),
|
||||
exec_server_telemetry::ParentLifetime::Independent,
|
||||
ParentLifetime::Independent,
|
||||
exec_server_telemetry::ShutdownBehavior::Immediate,
|
||||
);
|
||||
run.await.map_err(anyhow::Error::from_boxed)
|
||||
@@ -363,6 +408,16 @@ impl ExecServerCommand {
|
||||
}
|
||||
|
||||
fn validate_remote_transport(&self) -> anyhow::Result<()> {
|
||||
// Clap checks global flag conflicts separately on either side of `forward`.
|
||||
if self.registration_bearer_stdin && self.executor_credential_stdin {
|
||||
anyhow::bail!(
|
||||
"--registration-bearer-stdin cannot be used with --executor-credential-stdin"
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
!self.chatgpt_token_stdin || self.command.is_none(),
|
||||
"--chatgpt-token-stdin cannot be used with forward"
|
||||
);
|
||||
match (self.remote_transport, self.aws_sigv4) {
|
||||
(ExecServerRemoteTransport::Noise, true) => {
|
||||
anyhow::bail!("--aws-sigv4 requires --remote-transport direct");
|
||||
|
||||
Reference in New Issue
Block a user