Skip to content

feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels#1638

Draft
jhjaggars wants to merge 7 commits into
NVIDIA:mainfrom
jhjaggars:sigv4-credential-signing
Draft

feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels#1638
jhjaggars wants to merge 7 commits into
NVIDIA:mainfrom
jhjaggars:sigv4-credential-signing

Conversation

@jhjaggars
Copy link
Copy Markdown

@jhjaggars jhjaggars commented May 29, 2026

Add proxy-side AWS SigV4 credential signing for CONNECT tunnel requests. When credential_signing: sigv4 is set on a REST endpoint in the policy YAML, the sandbox proxy strips the client's invalid SigV4 signature (computed with placeholder credentials), re-signs the request with real AWS credentials from the SecretResolver using the official aws-sigv4 crate, and forwards the properly signed request upstream. This enables AWS services like Bedrock to work through OpenShell's credential proxy without exposing real secrets to the sandbox.
Closes #1631
Related: #1576 (STS refresh strategy — sandbox-side session token support is included here), #1568 (Google Vertex AI — complementary, no conflicts)

  • Proto: add credential_signing (field 18) and signing_service (field 19) to NetworkEndpoint
  • Policy lib: add both fields to NetworkEndpointDef serde struct and proto conversions
  • New sigv4.rs module: SigV4 signing via aws-sigv4 crate, AWS header stripping, region extraction from hostname
  • L7 config: add CredentialSigning enum and signing_service field, parse from OPA policy data
  • CONNECT tunnel relay (l7/rest.rs): hook SigV4 into relay_http_request_with_options_guarded — strip AWS headers before fail-closed placeholder scan, buffer body, re-sign after credential rewrite
  • OPA/relay plumbing: pass credential_signing and signing_service through OPA data and RelayRequestOptions
  • Session token support: resolve optional AWS_SESSION_TOKEN for STS temporary credentials
  • Dependencies: add aws-sigv4, aws-credential-types, aws-smithy-runtime-api; remove hmac
  • Integration test: standalone test against real Bedrock exercising the full strip → re-sign → send flow (#[ignore]'d, requires AWS credentials)
endpoints:
  - host: bedrock-runtime.us-east-2.amazonaws.com
    port: 443
    protocol: rest
    credential_signing: sigv4
    signing_service: bedrock
    access: read-write
    enforcement: enforce
  • Minimal signed headers: only host, content-type, and content-length are included in the signature. Signing all headers causes 403 InvalidSignatureException because the proxy modifies headers like Connection and Accept-Encoding between signing and delivery.
  • Explicit signing_service field: AWS signing service names don't always match hostname prefixes (e.g. bedrock-runtimebedrock). The aws-sigv4 crate is just signing math — hostname-to-signing-name resolution lives in per-service SDK crates with no lightweight alternative. Making the policy author specify it is correct and extensible.
  • Payload checksum: PayloadChecksumKind::XAmzSha256 is enabled to include x-amz-content-sha256, which Bedrock requires.
  • Unit tests for region extraction, signing output format, header stripping, session token inclusion, and request rewriting (6 tests)
  • OPA integration test verifying credential_signing and signing_service round-trip through proto → OPA data → L7 config
  • Integration test against real Bedrock (cargo test -p openshell-sandbox --test sigv4_signing -- --ignored)
  • End-to-end: Claude Code → OpenShell sandbox (OpenShift) → Bedrock us-east-2 with proxy-side SigV4 re-signing
  • mise run pre-commit passes
  • Unit tests added/updated
  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

jhjaggars added 6 commits May 29, 2026 11:15
Add `credential_signing: sigv4` policy field for REST endpoints. When set,
the sandbox proxy strips the client's invalid SigV4 Authorization header
(computed with placeholder credentials) and re-signs the request using
real AWS credentials from the SecretResolver before forwarding upstream.
This enables AWS services like Bedrock that use SigV4 auth to work through
OpenShell's credential proxy without exposing real secrets to the sandbox.
Changes:
- proto: add credential_signing field (18) to NetworkEndpoint
- l7/mod.rs: add CredentialSigning enum, parse from policy YAML
- sigv4.rs: new module — SigV4 signing with AWS service name normalization
- l7/rest.rs: hook SigV4 into CONNECT tunnel relay (strip AWS headers
  before fail-closed scan, re-sign after rewrite)
- opa.rs: plumb credential_signing through OPA data
- policy lib: add credential_signing to serde and proto conversion
Tested end-to-end: Claude Code → OpenShell sandbox → Bedrock (us-east-2)
with proxy-side SigV4 re-signing. Sandbox never sees real AWS credentials.
TODO: replace hand-rolled SigV4 with aws-sigv4 crate for correct service
name resolution across all AWS services.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Replace the hand-rolled HMAC key derivation, canonical request
formatting, and ISO 8601 date math with the official aws-sigv4 crate.
This gives us a tested, correct SigV4 implementation while keeping the
same proxy-side re-signing flow.
- Add aws-sigv4, aws-credential-types, aws-smithy-runtime-api deps
- Remove hmac dependency (no longer used directly)
- Rewrite apply_sigv4_to_request to delegate to aws_sigv4::http_request::sign()
- Delete AwsCredentials struct, sign_request, format_iso8601, and
  internal crypto helpers (~90 lines of hand-rolled code)
- Keep normalize_aws_signing_name table (no SDK alternative exists)
- Keep strip_aws_headers (raw byte manipulation, no SDK equivalent)
- Update l7/rest.rs call site to pass credentials as &str directly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
…guessing

Move the AWS signing service name into the policy document instead of
inferring it from the hostname. The policy author specifies the correct
signing service name (e.g. "bedrock") directly, eliminating the fragile
normalize_aws_signing_name mapping table.
Policy YAML example:
  credential_signing: sigv4
  signing_service: bedrock
- Proto: add signing_service field (19) to NetworkEndpoint
- Policy lib: add signing_service to serde struct and proto conversions
- L7/OPA/relay: plumb signing_service through to RelayRequestOptions
- rest.rs: use signing_service from policy; error if empty with SigV4
- sigv4.rs: replace extract_aws_region_and_service with extract_aws_region,
  delete normalize_aws_signing_name entirely
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
The aws-sigv4 SDK signs every header passed to SignableRequest. When the
proxy or transport modifies headers like Connection or Accept-Encoding
between signing and delivery, the signature breaks (403 InvalidSignature
from AWS).
Fix by only including host, content-type, and content-length in the
signed headers — matching the working hand-rolled implementation. Also
enable PayloadChecksumKind::XAmzSha256 so the x-amz-content-sha256
header is included in the signature, which AWS Bedrock requires.
Tested end-to-end: Claude Code → OpenShell sandbox → Bedrock succeeds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Add session_token parameter to apply_sigv4_to_request so SigV4 signing
works with temporary STS credentials (AccessKeyId + SecretAccessKey +
SessionToken). The aws-sigv4 SDK handles injecting the
x-amz-security-token header automatically when a session token is
present.
The call site resolves AWS_SESSION_TOKEN from the SecretResolver
alongside the access/secret keys. The token is optional — static IAM
keys continue to work without it.
Prepares for issue NVIDIA#1576 (aws_sts_assume_role refresh strategy) which
will provide short-lived STS credentials via gateway-side rotation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Add a standalone integration test that exercises the full proxy-side
re-signing flow: take a raw HTTP request with fake AWS auth headers,
strip them, re-sign with real credentials via apply_sigv4_to_request,
send over TLS to Bedrock, and verify a 200 response.
Marked #[ignore] — requires real AWS credentials:
  AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=xxx cargo test \
    -p openshell-sandbox --test sigv4_signing -- --ignored --nocapture
Also makes the sigv4 module pub for integration test access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 29, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 29, 2026

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@jhjaggars jhjaggars marked this pull request as draft May 29, 2026 19:45
@jhjaggars
Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

- Reorder imports per rustfmt
- Use map_or instead of if-let/else for request line parsing
- Use ToString::to_string instead of redundant closure
- Use usize::try_from instead of truncating as-cast
- Remove redundant as_deref on Option<&str>
- Backtick-wrap identifiers in doc comments
- Reformat long assert! lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels

1 participant