mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
## Why Private IP destinations always bypassed inherited upstream proxies, preventing their use for private networks reachable through an upstream VPN proxy. ## What changed - Add `codex exec-server --proxy-private-ips-via-upstream`, also configurable with `CODEX_EXEC_SERVER_PROXY_PRIVATE_IPS_VIA_UPSTREAM=true`. The setting defaults to disabled. - Allow permitted RFC 1918, carrier-grade NAT, and IPv6 unique-local destinations to use an applicable upstream proxy. Loopback and link-local destinations retain direct routing, and destination access policy still applies. - Keep connections direct when no valid upstream proxy applies or `allow_upstream_proxy=false`. Errors after selecting an upstream proxy do not trigger a direct retry. - Rename `ExecServerRuntimePaths` to `ExecServerRuntimeOptions` and carry the routing setting from executor startup into the managed network proxy. ## Testing Add routing coverage for private address ranges, special-use addresses, and public targets with the option enabled and disabled. Verify that HTTP and CONNECT requests still enforce destination allowlists and denylists, and update the CLI help snapshot. GitOrigin-RevId: b7c9cc7da0e0f545694a6521b74c9b36b7b92769
269 lines
7.6 KiB
Rust
269 lines
7.6 KiB
Rust
use clap::error::ErrorKind;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
use crate::exec_server_command::ExecServerRemoteTransport;
|
|
|
|
fn exec_server_from_args(args: &[&str]) -> ExecServerCommand {
|
|
let cli = MultitoolCli::try_parse_from(
|
|
["codex", "exec-server"]
|
|
.into_iter()
|
|
.chain(args.iter().copied()),
|
|
)
|
|
.expect("parse executor arguments");
|
|
let Some(Subcommand::ExecServer(command)) = cli.subcommand else {
|
|
panic!("expected executor command");
|
|
};
|
|
command
|
|
}
|
|
|
|
#[test]
|
|
fn exec_server_help_documents_remote_options() {
|
|
let command = MultitoolCli::command()
|
|
.term_width(80)
|
|
.mut_subcommand("exec-server", |command| {
|
|
command
|
|
.mut_arg("exit_on_stdin_close", |arg| arg.hide_env_values(true))
|
|
.mut_arg("proxy_private_ips_via_upstream", |arg| {
|
|
arg.hide_env_values(true)
|
|
})
|
|
});
|
|
let help = command
|
|
.try_get_matches_from(["codex", "exec-server", "--help"])
|
|
.expect_err("help should exit before running the executor");
|
|
assert_eq!(help.kind(), ErrorKind::DisplayHelp);
|
|
let help_text = help
|
|
.to_string()
|
|
.lines()
|
|
.map(str::trim_end)
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
insta::assert_snapshot!(help_text);
|
|
}
|
|
|
|
#[test]
|
|
fn exec_server_defaults_preserve_local_and_noise_modes() {
|
|
for args in [
|
|
vec![],
|
|
vec!["--listen", "stdio"],
|
|
vec![
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
],
|
|
vec![
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
"--remote-transport",
|
|
"noise",
|
|
"--use-agent-identity-auth",
|
|
],
|
|
] {
|
|
let command = exec_server_from_args(&args);
|
|
assert_eq!(command.remote_transport, ExecServerRemoteTransport::Noise);
|
|
assert!(!command.aws_sigv4);
|
|
command
|
|
.validate_remote_transport()
|
|
.expect("valid existing mode");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn exec_server_parses_direct_aws_options() {
|
|
for (options, expected) in [
|
|
(vec![], (None, None, "execute-api")),
|
|
(
|
|
vec![
|
|
"--aws-profile",
|
|
"development",
|
|
"--aws-region",
|
|
"us-west-2",
|
|
"--aws-service",
|
|
"bedrock-mantle",
|
|
],
|
|
(Some("development"), Some("us-west-2"), "bedrock-mantle"),
|
|
),
|
|
] {
|
|
let mut args = vec![
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
"--remote-transport",
|
|
"direct",
|
|
"--aws-sigv4",
|
|
];
|
|
args.extend(options);
|
|
let command = exec_server_from_args(&args);
|
|
|
|
assert_eq!(
|
|
command.remote.as_deref(),
|
|
Some("https://registry.example.com")
|
|
);
|
|
assert_eq!(command.environment_id.as_deref(), Some("env-1"));
|
|
assert_eq!(command.remote_transport, ExecServerRemoteTransport::Direct);
|
|
assert!(command.aws_sigv4);
|
|
assert_eq!(
|
|
(
|
|
command.aws_profile.as_deref(),
|
|
command.aws_region.as_deref(),
|
|
command.aws_service.as_str()
|
|
),
|
|
expected,
|
|
);
|
|
command
|
|
.validate_remote_transport()
|
|
.expect("valid Direct mode");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn exec_server_rejects_missing_or_conflicting_remote_options() {
|
|
for (options, expected) in [
|
|
(
|
|
vec!["--remote-transport", "direct"],
|
|
ErrorKind::MissingRequiredArgument,
|
|
),
|
|
(
|
|
vec!["--aws-profile", "development"],
|
|
ErrorKind::MissingRequiredArgument,
|
|
),
|
|
(
|
|
vec!["--aws-region", "us-west-2"],
|
|
ErrorKind::MissingRequiredArgument,
|
|
),
|
|
(
|
|
vec!["--aws-service", "execute-api"],
|
|
ErrorKind::MissingRequiredArgument,
|
|
),
|
|
(
|
|
vec![
|
|
"--remote-transport",
|
|
"direct",
|
|
"--aws-sigv4",
|
|
"--use-agent-identity-auth",
|
|
],
|
|
ErrorKind::ArgumentConflict,
|
|
),
|
|
(
|
|
vec!["--remote-transport", "unsupported"],
|
|
ErrorKind::InvalidValue,
|
|
),
|
|
] {
|
|
let error = MultitoolCli::try_parse_from(
|
|
[
|
|
"codex",
|
|
"exec-server",
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
]
|
|
.into_iter()
|
|
.chain(options),
|
|
)
|
|
.expect_err("reject invalid remote arguments");
|
|
assert_eq!(error.kind(), expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn exec_server_transport_and_aws_options_require_registration_arguments() {
|
|
for options in [
|
|
vec!["--remote-transport", "noise"],
|
|
vec!["--remote-transport", "direct", "--aws-sigv4"],
|
|
vec!["--aws-sigv4"],
|
|
vec![
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--remote-transport",
|
|
"direct",
|
|
"--aws-sigv4",
|
|
],
|
|
] {
|
|
let error =
|
|
MultitoolCli::try_parse_from(["codex", "exec-server"].into_iter().chain(options))
|
|
.expect_err("require remote URL and environment ID");
|
|
assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn exec_server_sigv4_does_not_enable_aws_auth_for_noise() {
|
|
for options in [vec![], vec!["--remote-transport", "noise"]] {
|
|
let mut args = vec![
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
"--aws-sigv4",
|
|
];
|
|
args.extend(options);
|
|
let command = exec_server_from_args(&args);
|
|
// Unset runtime paths prove validation runs before startup or credential loading.
|
|
let error = command
|
|
.run(
|
|
&Arg0DispatchPaths::default(),
|
|
&CliConfigOverrides::default(),
|
|
)
|
|
.await
|
|
.expect_err("Noise auth is unchanged");
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"--aws-sigv4 requires --remote-transport direct"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn exec_server_direct_forwarding_remains_rejected() {
|
|
let command = exec_server_from_args(&[
|
|
"forward",
|
|
"--connect",
|
|
"ws://127.0.0.1:8765",
|
|
"--remote",
|
|
"https://registry.example.com",
|
|
"--environment-id",
|
|
"env-1",
|
|
"--remote-transport",
|
|
"direct",
|
|
"--aws-sigv4",
|
|
"--aws-profile",
|
|
"development",
|
|
"--aws-region",
|
|
"us-west-2",
|
|
"--aws-service",
|
|
"bedrock-mantle",
|
|
]);
|
|
assert_eq!(
|
|
(
|
|
command.remote_transport,
|
|
command.aws_sigv4,
|
|
command.aws_profile.as_deref(),
|
|
command.aws_region.as_deref(),
|
|
command.aws_service.as_str()
|
|
),
|
|
(
|
|
ExecServerRemoteTransport::Direct,
|
|
true,
|
|
Some("development"),
|
|
Some("us-west-2"),
|
|
"bedrock-mantle"
|
|
),
|
|
);
|
|
let error = command
|
|
.run(
|
|
&Arg0DispatchPaths::default(),
|
|
&CliConfigOverrides::default(),
|
|
)
|
|
.await
|
|
.expect_err("Direct forwarding is unsupported before startup");
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"direct exec-server transport does not support forwarding"
|
|
);
|
|
}
|