mirror of
https://github.com/openai/codex.git
synced 2026-09-13 11:47:17 +00:00
## Why Allow remote exec servers to connect directly to AWS-hosted registries that authenticate registry requests and WebSocket handshakes with AWS SigV4. ## What changed - Add `--remote-transport direct` with SigV4 profile, region, and service options while keeping Noise as the default transport. - Register the `direct_jsonrpc_v1` transport and carry plain exec-server JSON-RPC messages over the authenticated WebSocket. - Reuse direct registrations across transient disconnects, refresh them after a `409 Conflict`, and require TLS for non-loopback endpoints. ## Testing - Cover CLI validation and SigV4 request signing. - Exercise direct registration, handshake retry behavior, JSON-RPC interoperability, and process recovery after reconnecting. GitOrigin-RevId: 0755df330ba3abe5db0a516fdaa49338d9bbe2d2
97 lines
3.5 KiB
Rust
97 lines
3.5 KiB
Rust
use std::time::Duration;
|
|
|
|
use codex_aws_auth::AwsAccessKeys;
|
|
use http::HeaderValue;
|
|
use http::Method;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
|
|
async fn test_provider() -> AwsSigV4AuthProvider {
|
|
let context = AwsAuthContext::load_with_access_keys(
|
|
AwsAuthConfig {
|
|
profile: None,
|
|
region: Some("us-east-1".to_string()),
|
|
service: "execute-api".to_string(),
|
|
},
|
|
AwsAccessKeys {
|
|
access_key_id: "test-access-key".to_string(),
|
|
secret_access_key: "test-secret-key".to_string(),
|
|
session_token: Some("test-session-token".to_string()),
|
|
},
|
|
)
|
|
.await
|
|
.expect("load fixture signing context");
|
|
AwsSigV4AuthProvider { context }
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn signs_requests_without_changing_payload_or_metadata() {
|
|
let provider = test_provider().await;
|
|
let url = "https://executor.example.com/connect?environment_id=environment-1";
|
|
for mut request in [
|
|
Request::new(Method::GET, url.to_string()),
|
|
Request::new(Method::POST, url.to_string())
|
|
.with_json(&serde_json::json!({"transport": "direct_jsonrpc_v1"})),
|
|
Request::new(Method::POST, url.to_string())
|
|
.with_json(&serde_json::json!({"transport": "direct_jsonrpc_v1"}))
|
|
.with_compression(RequestCompression::Zstd),
|
|
] {
|
|
request
|
|
.headers
|
|
.insert("session_id", HeaderValue::from_static("session-1"));
|
|
request
|
|
.headers
|
|
.insert("x-custom-header", HeaderValue::from_static("preserved"));
|
|
request.timeout = Some(Duration::from_secs(3));
|
|
let method = request.method.clone();
|
|
let expected = request
|
|
.prepare_body_for_send()
|
|
.expect("prepare fixture body");
|
|
let signed = provider
|
|
.apply_auth(request)
|
|
.await
|
|
.expect("sign fixture request");
|
|
|
|
assert_eq!(signed.method, method);
|
|
assert_eq!(signed.url, url);
|
|
assert_eq!(signed.timeout, Some(Duration::from_secs(3)));
|
|
assert_eq!(signed.body, expected.body.clone().map(RequestBody::Raw));
|
|
assert_eq!(signed.compression, RequestCompression::None);
|
|
for (name, value) in &expected.headers {
|
|
assert_eq!(signed.headers.get(name), Some(value));
|
|
}
|
|
assert_eq!(signed.headers["x-amz-security-token"], "test-session-token");
|
|
assert!(signed.headers.contains_key("x-amz-date"));
|
|
let authorization = signed.headers[http::header::AUTHORIZATION]
|
|
.to_str()
|
|
.unwrap();
|
|
assert!(authorization.starts_with("AWS4-HMAC-SHA256 "));
|
|
assert!(authorization.contains("/us-east-1/execute-api/aws4_request"));
|
|
assert_eq!(signed.prepare_body_for_send().unwrap().body, expected.body);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signing_request_is_a_permanent_auth_error() {
|
|
let provider = test_provider().await;
|
|
let error = provider
|
|
.apply_auth(Request::new(Method::GET, "not a URL".to_string()))
|
|
.await
|
|
.expect_err("invalid URL should fail signing");
|
|
assert!(matches!(error, AuthError::Build(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signing_configuration_is_rejected() {
|
|
let error = aws_sigv4_auth_provider(AwsAuthConfig {
|
|
profile: None,
|
|
region: Some("us-east-1".to_string()),
|
|
service: " ".to_string(),
|
|
})
|
|
.await
|
|
.err()
|
|
.expect("empty service should fail configuration");
|
|
assert!(matches!(error, AwsAuthError::EmptyService));
|
|
}
|