mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Add direct SigV4 transport to exec-server (#42781)
## 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
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user