diff --git a/crates/openshell-supervisor-network/src/sigv4.rs b/crates/openshell-supervisor-network/src/sigv4.rs index 79e4c32e18..fb44920bb0 100644 --- a/crates/openshell-supervisor-network/src/sigv4.rs +++ b/crates/openshell-supervisor-network/src/sigv4.rs @@ -2,14 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 use aws_credential_types::Credentials; +use aws_sigv4::SigningOutput; use aws_sigv4::http_request::{ - PayloadChecksumKind, SignableBody, SignableRequest, SigningSettings, sign, + PayloadChecksumKind, SignableBody, SignableRequest, SigningInstructions, SigningSettings, sign, }; use aws_sigv4::sign::v4; use aws_smithy_runtime_api::client::identity::Identity; use miette::{Result, miette}; use std::time::SystemTime; +/// Keep AWS signer diagnostics out of supervisor log sinks: the canonical +/// request can contain the raw session token added during signing. +fn sign_without_tracing<'a>( + request: SignableRequest<'a>, + params: &'a aws_sigv4::http_request::SigningParams<'a>, +) -> std::result::Result, aws_sigv4::http_request::SigningError> +{ + tracing::dispatcher::with_default(&tracing::Dispatch::none(), || sign(request, params)) +} + /// AWS regions contain a hyphen followed by a digit (e.g., `us-east-1`). /// Service names like `s3` or `bedrock-runtime` do not. fn looks_like_region(s: &str) -> bool { @@ -233,7 +244,7 @@ fn build_identity(access_key: &str, secret_key: &str, session_token: Option<&str fn rebuild_request( parts: &RequestParts<'_>, - instructions: &aws_sigv4::http_request::SigningInstructions, + instructions: &SigningInstructions, body: &[u8], ) -> Vec { let mut output = Vec::with_capacity(256 + body.len()); @@ -297,7 +308,7 @@ pub fn apply_sigv4_to_request( ) .map_err(|e| miette!("SigV4 signable request: {e}"))?; - let (instructions, _signature) = sign(signable_request, &signing_params) + let (instructions, _signature) = sign_without_tracing(signable_request, &signing_params) .map_err(|e| miette!("SigV4 signing failed: {e}"))? .into_parts(); @@ -364,7 +375,7 @@ pub fn apply_sigv4_headers_only_with_body( ) .map_err(|e| miette!("SigV4 signable request: {e}"))?; - let (instructions, _signature) = sign(signable_request, &signing_params) + let (instructions, _signature) = sign_without_tracing(signable_request, &signing_params) .map_err(|e| miette!("SigV4 signing failed: {e}"))? .into_parts(); @@ -375,6 +386,55 @@ pub fn apply_sigv4_headers_only_with_body( mod tests { use super::*; + #[test] + fn signing_does_not_emit_the_canonical_request_to_tracing() { + let log = tempfile::NamedTempFile::new().unwrap(); + let subscriber = tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .with_ansi(false) + .with_writer(log.reopen().unwrap()) + .finish(); + let session_token = "SYNTHETIC_SIGV4_SESSION_TOKEN"; + let raw = b"GET /bucket/key HTTP/1.1\r\nHost: s3.us-east-1.amazonaws.com\r\n\r\n"; + + tracing::subscriber::with_default(subscriber, || { + tracing::trace!(target: "test_capture", "before signing"); + apply_sigv4_to_request( + raw, + "s3.us-east-1.amazonaws.com", + "us-east-1", + "s3", + "ASIATEST", + "synthetic-secret", + Some(session_token), + ) + .unwrap(); + for body in [ + SignableBody::UnsignedPayload, + SignableBody::StreamingUnsignedPayloadTrailer, + ] { + apply_sigv4_headers_only_with_body( + raw, + "s3.us-east-1.amazonaws.com", + "us-east-1", + "s3", + "ASIATEST", + "synthetic-secret", + Some(session_token), + body, + ) + .unwrap(); + } + tracing::trace!(target: "test_capture", "after signing"); + }); + + let events = std::fs::read_to_string(log.path()).unwrap(); + assert!(events.contains("before signing")); + assert!(events.contains("after signing")); + assert!(!events.contains("aws_sigv4")); + assert!(!events.contains(session_token)); + } + #[test] fn extract_region_from_hostname() { let region = extract_aws_region("bedrock-runtime.us-east-2.amazonaws.com").unwrap(); diff --git a/docs/how-it-works/providers/aws.mdx b/docs/how-it-works/providers/aws.mdx index 953652e3c4..86cf7dde7a 100644 --- a/docs/how-it-works/providers/aws.mdx +++ b/docs/how-it-works/providers/aws.mdx @@ -166,6 +166,7 @@ endpoints: - OpenShell rejects a signed sandbox policy before activation unless the endpoint has a resolvable AWS credential source. An endpoint-bearing profile must declare `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` and cover the signed host, port, and path. An endpointless profile must declare those keys and be selected with `credential_binding.provider` on the signed endpoint. - The `sigv4:body` mode buffers at most 10 MiB. Requests with larger bodies are rejected. Use `sigv4:no_body` or `sigv4` (auto-detect) for large payloads. - The active provider must contain current `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` values. If either becomes unavailable after policy activation, the request fails closed. +- AWS signer diagnostics are omitted from supervisor logs, including at `TRACE` level, because they can contain session tokens. ## Use from a Sandbox