Skip to content

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

Merged
pimlock merged 13 commits into
NVIDIA:mainfrom
jhjaggars:sigv4-credential-signing
Jun 26, 2026
Merged

feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels#1638
pimlock merged 13 commits into
NVIDIA:mainfrom
jhjaggars:sigv4-credential-signing

Conversation

@jhjaggars

@jhjaggarsjhjaggars commented May 29, 2026

Copy link
Copy Markdown
Contributor

Add proxy-side AWS SigV4 credential re-signing for CONNECT tunnel requests. The proxy auto-detects the correct signing mode from the client SDK's x-amz-content-sha256 header, supporting Bedrock (signed body), S3 uploads (streaming unsigned), and S3 reads/deletes (unsigned payload) — all with a single credential_signing: sigv4 policy setting.

Client x-amz-content-sha256Proxy behaviorServices
Hex SHA-256 hashBuffer body, hash, signBedrock, most AWS
STREAMING-UNSIGNED-PAYLOAD-TRAILERSign headers, stream bodyS3 PutObject, upload_fileobj
STREAMING-AWS4-HMAC-SHA256-PAYLOADReject in auto-detect; use sigv4:no_bodyChunk-signed S3 uploads
UNSIGNED-PAYLOADSign headers, no bufferingS3 over HTTPS
Any other STREAMING-*Reject in auto-detect; use sigv4:no_bodyUnsupported/future SDK variants
AbsentContent-Length heuristicNon-AWS SDKs
STREAMING-UNSIGNED-PAYLOAD-TRAILER is re-signed as unsigned streaming. Other STREAMING-* variants are rejected in auto-detect mode because the proxy cannot reproduce per-chunk signatures; use sigv4:no_body instead.
Two explicit overrides: sigv4:body (always hash body) and sigv4:no_body (always unsigned).
Closes#1631
Related: #1576, #1568
  • sigv4.rs: SigV4 signing via aws-sigv4 — signed body and headers-only modes. Region extraction supports standard, dualstack, FIPS, FIPS+dualstack, virtual-hosted, GovCloud, and China partition hostnames. Rejects service names (e.g. s3) as regions via looks_like_region() heuristic.
  • l7/rest.rs: Auto-detects payload mode from client headers, branches into signed-body or streaming path. Treats STREAMING-UNSIGNED-PAYLOAD-TRAILER as unsigned streaming and rejects other STREAMING-* variants in auto-detect mode with guidance to use sigv4:no_body. Handles Expect: 100-continue within SigV4 path only. Fails closed for chunked bodies in signed-body path.
  • l7/mod.rs: CredentialSigning enum (SigV4, SigV4Body, SigV4NoBody). Rejects unknown values and missing signing_service at sandbox startup.
  • Proto/Policy/OPA: credential_signing (field 19) and signing_service (field 20) on NetworkEndpoint, plumbed through serde, proto, Rego, and provider profile DTO.
  • Policy validation: Rejects unknown credential_signing values and missing signing_service at load time.
  • Provider profiles: EndpointProfile preserves both fields through round-trip.
  • OCSF: NetworkActivity event emitted for each SigV4 re-signing decision.
  • Architecture docs: Updated architecture/sandbox.md with all signing modes.
  • mise run pre-commit passes
  • Unit tests added/updated (region extraction, signing output, policy validation, L7 config parsing)
  • Integration test against real Bedrock and S3 (cargo test -p openshell-sandbox --test sigv4_real_aws -- --ignored)
  • End-to-end from inside Podman sandbox: Bedrock InvokeModel, S3 PUT/GET/DELETE, S3 streaming upload — all passing
  • Follows Conventional Commits
  • Commits are signed off (DCO)

@copy-pr-bot

Copy link
Copy Markdown

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

github-actionsBot commented May 29, 2026

Copy link
Copy Markdown

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
ContributorAuthor

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

Comment threadcrates/openshell-supervisor-network/src/l7/rest.rs
Comment threadcrates/openshell-policy/src/lib.rs
@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from bec3afc to e2f78daCompareJune 1, 2026 15:49
@russellb

Copy link
Copy Markdown
Contributor

just for visibility - I started working on AWS STS credential support (#1576), using access to S3 as a test case.

This depends on sigv4 support, so I pulled that in to my branch, or at least a version of it from the end of last week.

main...russellb:OpenShell:feat/1576-aws-sts-with-sigv4

@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from 1ee1ee5 to 84c5c3dCompareJune 1, 2026 21:40
@jhjaggars
jhjaggars marked this pull request as ready for review June 1, 2026 21:41
Comment threadcrates/openshell-sandbox/src/l7/rest.rs Outdated
@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch 2 times, most recently from 54e0f39 to 7141a81CompareJune 1, 2026 23:40
@jhjaggars
jhjaggars requested a review from russellbJune 2, 2026 17:55
@russellb

Copy link
Copy Markdown
Contributor

This is working for me in my AWS STS with S3 testing in my branch. Thanks!

@mrunalp

Copy link
Copy Markdown
Collaborator

/ok to test 95e2ecb

@mrunalp

Copy link
Copy Markdown
Collaborator

I ran an agent analysis:

Findings

  1. High: crates/openshell-sandbox/src/l7/rest.rs treats STREAMING-AWS4-HMAC-SHA256-PAYLOAD and any other unknown STREAMING-* value as StreamingUnsignedTrailer, but the relay path only regenerates headers and then forwards the original chunk stream verbatim. That is not enough to convert a chunk-signed AWS body into the unsigned-trailer wire format. Any
    client that really sends signed chunks will end up with a new auth envelope paired with the old chunk-signature chain.

crates/openshell-sandbox/src/l7/rest.rs lines 1567-1579

fn detect_payload_mode(headers: &str) -> Result {
// ...
return match val {
"streaming-unsigned-payload-trailer" | "streaming-aws4-hmac-sha256-payload" => {
Ok(SigV4PayloadMode::StreamingUnsignedTrailer)
}
"unsigned-payload" => Ok(SigV4PayloadMode::UnsignedPayload),
v if v.starts_with("streaming-") => {
Ok(SigV4PayloadMode::StreamingUnsignedTrailer)
}
_ => Ok(SigV4PayloadMode::SignBody),
};
}

crates/openshell-sandbox/src/l7/rest.rs lines 571-594

let signable_body = match payload_mode {
SigV4PayloadMode::StreamingUnsignedTrailer => {
SignableBody::StreamingUnsignedPayloadTrailer
}
_ => SignableBody::UnsignedPayload,
};
let signed_headers = crate::sigv4::apply_sigv4_headers_only_with_body(
&rewrite_result.rewritten,
options.host,
&region,
service,
access_key,
secret_key,
session_token,
signable_body,
)?;
// ...
relay_chunked(
client,
upstream,
&req.raw_header[header_end..],
options.generation_guard,
)
.await?;

  1. High: crates/openshell-sandbox/src/l7/rest.rs short-circuits request_body_credential_rewrite when credential_signing is enabled, and crates/openshell-policy/src/lib.rs does not reject that combination. That means unresolved body placeholders can bypass the existing fail-closed rewrite path and get signed and forwarded as literal placeholder text.

crates/openshell-sandbox/src/l7/rest.rs lines 464-477

// Apply SigV4 signing if configured.
if options.credential_signing.is_sigv4() {
// ...
} else if options.request_body_credential_rewrite {
let body = collect_and_rewrite_request_body(
req,
client,
&rewrite_result.rewritten,
header_str,
&req.raw_header[header_end..],
options.resolver,
options.generation_guard,
)
.await?;

  1. Medium: crates/openshell-sandbox/src/l7/rest.rs buffered sign-body mode loses policy-reload enforcement while it drains the request body. The new path uses a single client.read_exact() to buffer the remaining body, with no generation_guard.ensure_current() during or after that read, while the existing relay_fixed() and collect_and_rewrite_request_body()
    helpers in the same file do check the guard on each read. A slow upload can therefore continue under a stale allow decision after policy reload.

  2. Medium: crates/openshell-sandbox/src/sigv4.rs, crates/openshell-sandbox/src/l7/rest.rs, and proto/sandbox.proto hard-code region discovery from hostnames and silently fall back to us-east-1. There is no signing_region policy field, so any valid AWS endpoint that does not match extract_aws_region()'s baked-in patterns gets mis-signed with no operator
    override. That is a brittle control-plane contract for an egress feature meant to cover arbitrary AWS endpoints.

  3. Medium: crates/openshell-sandbox/src/l7/rest.rs caps signed-body mode at 256 KiB via MAX_REWRITE_BODY_BYTES. In auto mode, a normal Bedrock request with a real hex payload hash takes this path, so larger but valid requests fail even though the feature is advertised as general SigV4 support. If the intent is to support Bedrock broadly, this needs either
    a larger/spooled buffer strategy or an explicit product limit.

  4. Low: crates/openshell-sandbox/src/l7/rest.rs emits the OCSF event as Allowed/Success before size validation, signing, and upstream write can fail, and records dst_endpoint with port 0. The audit trail can therefore report successful re-signing for requests that never actually made it upstream.

@mrunalp

Copy link
Copy Markdown
Collaborator

We should also update the user facing docs for this.

@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from 95e2ecb to cb54591CompareJune 5, 2026 19:27
@mrunalp

Copy link
Copy Markdown
Collaborator

/ok to test aa12351

@TaylorMutch

Copy link
Copy Markdown
Collaborator

/ok to test aa12351

@mrunalp

Copy link
Copy Markdown
Collaborator

/ok to test aa12351

@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from 54e7875 to 22ea06fCompareJune 11, 2026 15:24
jhjaggarsand others added 11 commits June 22, 2026 09:22
…nnels
Add proxy-side AWS SigV4 re-signing so sandbox clients can reach AWS
services (Bedrock) through the CONNECT tunnel using placeholder
credentials. The proxy strips the invalid signature, resolves real
credentials from the SecretResolver, re-signs with the aws-sigv4 crate,
and forwards. Configuration is policy-driven via two new fields
(credential_signing, signing_service).
Policy YAML example:
credential_signing: sigv4
signing_service: bedrock
Implementation:
- sigv4.rs: strip_aws_headers removes old auth headers before the
fail-closed placeholder scan; apply_sigv4_to_request re-signs using
the aws-sigv4 SDK with PayloadChecksumKind::XAmzSha256 enabled.
Returns Result instead of panicking. Non-signed headers (Accept,
User-Agent, etc.) are preserved in the output.
- rest.rs: SigV4 path buffers body (capped at MAX_REWRITE_BODY_BYTES)
for signing, then forwards the re-signed request upstream.
- Proto: credential_signing (field 19), signing_service (field 20)
on NetworkEndpoint.
- Policy/OPA: plumbed through serde, proto conversion, and Rego data.
- Supports AWS session tokens (STS temporary credentials).
- Integration test against real Bedrock (ignored, requires AWS creds).
Reject policies where credential_signing is set but signing_service is
empty during validate_sandbox_policy() instead of failing at connection
time. The runtime check in rest.rs is kept as defense-in-depth.
Extend the SigV4 proxy re-signing to auto-detect the correct payload
signing mode from the client SDK's x-amz-content-sha256 header:
- Hex hash → buffer body and include hash in signature (Bedrock)
- STREAMING-UNSIGNED-PAYLOAD-TRAILER → sign headers only, stream body
through for aws-chunked uploads (S3 PutObject, upload_fileobj)
- UNSIGNED-PAYLOAD → sign headers only, no body buffering (S3 over HTTPS)
- Absent → fall back to Content-Length heuristic
This eliminates the need for body buffering on S3 uploads and adds
support for chunked transfer encoding that the previous implementation
could not handle.
New credential_signing policy values:
- sigv4 — auto-detect from client headers (recommended)
- sigv4:body — always buffer and hash the body
- sigv4:no_body — always use UNSIGNED-PAYLOAD
Also adds Expect: 100-continue handling in the REST L7 relay so clients
like boto3's S3 PutObject receive the interim 100 response before
sending the body.
Validated end-to-end from inside a Podman sandbox against real AWS:
Bedrock InvokeModel, S3 PUT/GET/DELETE, and streaming upload_fileobj.
Critical:
- Scope Expect: 100-continue handling to SigV4 paths only. Previously
it fired for all L7-proxied requests, violating RFC 7231 §5.1.1 and
risking double 100 responses on non-SigV4 traffic.
Warnings:
- Reject unknown credential_signing values at policy validation time.
A typo like "sigv4_typo" now produces a clear PolicyViolation instead
of silently falling back to no signing.
- Support dualstack, FIPS, virtual-hosted, and China partition hostnames
in extract_aws_region (e.g. s3.dualstack.us-west-2.amazonaws.com,
s3.cn-north-1.amazonaws.com.cn).
- Emit OCSF NetworkActivity event for SigV4 re-signing decisions
instead of debug! tracing, per AGENTS.md structured logging guidelines.
- Update architecture/sandbox.md to document all three signing modes
(signed body, streaming unsigned trailer, unsigned payload) and the
auto-detection mechanism.
…OCSF nit
- Fix extract_aws_region for FIPS+dualstack combo hostnames like
s3-fips.dualstack.us-west-2.amazonaws.com (scans past all "dualstack"
labels instead of just one).
- Add tests for FIPS+dualstack and GovCloud region extraction.
- Add unit test for UnknownCredentialSigning policy validation
(e.g. "sigv4_typo" produces the expected violation).
- Use ActivityId::Traffic instead of ActivityId::Other for the SigV4
OCSF event — more descriptive for a signing operation on existing
traffic flow.
…TO, startup validation
Critical:
- Reject STREAMING-AWS4-HMAC-SHA256-PAYLOAD in detect_payload_mode()
instead of silently treating it as SignBody (per-chunk signing is not
supported). Returns a clear error directing the user to sigv4:no_body.
- Add defense-in-depth guard in the SignBody path: fail closed if the
request uses chunked transfer encoding, preventing body-less forwards.
Warnings:
- Wire credential_signing and signing_service through EndpointProfile
DTO in openshell-providers. Both endpoint_to_proto() and
endpoint_from_proto() now preserve the fields during round-trip.
- Reject unknown credential_signing values at sandbox L7 config parse
time (returns None, disabling L7 for the endpoint) instead of
silently downgrading to CredentialSigning::None. Also reject SigV4
modes with empty signing_service at startup rather than deferring
the error to request time.
Boto3 connects to global S3 endpoints like
bucket.s3.amazonaws.com (no region in the hostname). The previous
extract_aws_region returned "s3" for this pattern because it took
the label at parts[len-3] without checking if it was actually a
region.
Add looks_like_region() which requires a hyphen followed by a digit
(e.g., us-east-1). Service names like "s3" or "bedrock-runtime" are
rejected, causing the fallback to us-east-1.
Refs NVIDIA#1576
Boto3 put_object sends x-amz-content-sha256 with the value
STREAMING-AWS4-HMAC-SHA256-PAYLOAD, which was rejected by
detect_payload_mode() because per-chunk signing is not supported.
Treat all streaming- variants as StreamingUnsignedTrailer: re-sign
headers only and stream the body through. The proxy cannot reproduce
per-chunk signatures, but AWS accepts unsigned streaming payloads
over HTTPS.
Refs NVIDIA#1576
…ntials
The sigv4_real_aws test reads ~/.aws/credentials and makes live calls
to Bedrock and S3. Even though the tests are #[ignore]d, committing
code that consumes real credentials is a security risk.
…docs
Security (High):
- Reject STREAMING-AWS4-HMAC-SHA256-PAYLOAD and unknown STREAMING-*
modes in detect_payload_mode() instead of silently mapping to
StreamingUnsignedTrailer. The proxy cannot reproduce per-chunk
signatures; error message directs users to sigv4:no_body.
- Add policy validation rejecting credential_signing +
request_body_credential_rewrite on the same endpoint (mutually
exclusive). Runtime defense-in-depth guard added in the SigV4 path.
Correctness (Medium):
- Add generation_guard.ensure_current() after body buffering in the
SignBody path so stale policy decisions are caught after slow uploads.
- Add signing_region policy field (proto, policy YAML, providers DTO,
L7 config). When set, takes precedence over hostname extraction.
When neither yields a region, the request is rejected with a clear
error instead of silently falling back to us-east-1.
- Raise SigV4 body buffer limit to 10 MiB (separate from the 256 KiB
credential rewrite limit) for Bedrock payloads.
Audit (Low):
- Move OCSF event emission to after successful signing and upstream
write (was emitted before validation/signing could fail).
- Populate dst_endpoint port from the actual upstream port instead of
hardcoded 0.
Clippy:
- Fix items_after_statements in sigv4.rs (move consts before bindings).
- Suppress pre-existing large_futures lint at the spawn call site.
- Fix map_or → is_some_and, if_not_else, and doc backtick lints.
Docs:
- Add docs/providers/aws-sigv4.mdx covering policy config, signing
modes, region detection, and restrictions.
- Add credential_signing, signing_service, signing_region to
docs/reference/policy-schema.mdx.
- Update architecture/sandbox.md with streaming rejection, region
override, and mutual exclusivity notes.
Tests:
- Add 7 detect_payload_mode unit tests covering all modes and rejection.
- Add validate_rejects_credential_signing_with_body_rewrite test.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from 22ea06f to c0147adCompareJune 22, 2026 13:56
@pimlock

Copy link
Copy Markdown
Collaborator

/ok to test c0147ad

- Replace from_utf8_lossy with strict from_utf8 in strip_aws_headers,
apply_sigv4_to_request, and apply_sigv4_headers_only_with_body to
fail closed on non-UTF-8 headers (security hardening)
- Deduplicate parse_body_length call in SignBody path
- Add SPDX license header to sigv4_localstack.rs test file
- Fix rustfmt import ordering in rest.rs
- Fix clippy ignore-without-reason and match-same-arms in test file
- Fix stale crate name in test run command comment
- Add doc comments for header lowercasing and defense-in-depth stripping
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
@jhjaggars
jhjaggarsforce-pushed the sigv4-credential-signing branch from dcdcecf to 6db6ff1CompareJune 23, 2026 18:49
@maxamillion

Copy link
Copy Markdown
Collaborator

/ok to test 6db6ff1

@maxamillionmaxamillion added the test:e2e Requires end-to-end coverage label Jun 23, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied for 6db6ff1. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

Comment threadcrates/openshell-supervisor-network/src/opa.rs
Comment threaddocs/providers/aws-sigv4.mdx Outdated
Comment threaddocs/providers/aws-sigv4.mdx Outdated
Comment threaddocs/providers/aws-sigv4.mdx Outdated
Comment threaddocs/providers/aws-sigv4.mdx Outdated
@pimlock

Copy link
Copy Markdown
Collaborator

@jhjaggars I added a few minor comments and with these resolved, we can get this merged.

For the middleware discussion: I see a path of migrating this implementation to the middleware once we have that in place. The initial middleware contract will need to be extended to work with different ways sigv4 is applied, especially around streaming. I think it's still worth doing though to have sigv4 handling encapsulated as a middleware with a common way of configuring it, etc. Once we have that, we could deprecate this config and use middleware-specific config instead.

- Add signing_region to OPA data JSON alongside signing_service
- Add round-trip test for signing_region preservation
- Remove access field from Bedrock example (uses rules instead)
- Change access: allow to access: full in S3, STS, and region
override doc examples
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
@pimlock

Copy link
Copy Markdown
Collaborator

/ok to test 10b88d2

@pimlockpimlock left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, took one last pass and added a comment.

Otherwise LGTM and we can get it merged.

Comment threadcrates/openshell-policy/src/lib.rs
@pimlock
pimlock merged commit f569a0a into NVIDIA:mainJun 26, 2026
45 checks passed
russellb added a commit to russellb/OpenShell that referenced this pull request Jun 30, 2026
…rofile
Add server-side AWS STS AssumeRole credential refresh so gateways can
mint short-lived AWS credentials for sandboxes. Combined with the SigV4
proxy re-signing from NVIDIA#1638, sandboxes can access S3 (and other AWS
services) without ever seeing real credentials.
## STS credential refresh (server)
- New `AwsStsAssumeRole` variant in `ProviderCredentialRefreshStrategy`.
- `provider_refresh.rs` implements the refresh loop: calls
`sts:AssumeRole` using the gateway's ambient AWS credentials (or
explicit long-lived keys from material), stores the resulting
`AccessKeyId`, `SecretAccessKey`, and `SessionToken` on the provider,
and schedules re-rotation before expiry.
- `ConfigureProviderRefresh` and `RotateProviderCredential` RPCs gated
behind the `providers_v2_enabled` setting. Validates that configuring
STS refresh for one provider won't collide with credential keys already
held by another provider attached to the same sandbox.
- Proto: `AWS_STS_ASSUME_ROLE = 3` on `ProviderCredentialRefreshStrategy`.
## Provider profiles: `aws` and `aws-s3`
AWS provider profiles follow the same pattern as the existing
`aws-bedrock` profile: a generic base plus service-specific variants.
- `providers/aws.yaml`: base AWS profile with STS refresh material
(role_arn, session_name, external_id, aws_region, optional long-lived
keys) but no endpoints or binaries allowlist. Intended for AWS
services that don't yet have a dedicated profile — the user attaches
their own policy to supply endpoints. This is the same role
`google-cloud` plays relative to `google-vertex-ai`.
- `providers/aws-s3.yaml`: S3-specific profile that adds pre-configured
endpoints covering regional (`*.s3.*.amazonaws.com`), global
(`*.s3.amazonaws.com` with `signing_region: us-east-1`), and dualstack
variants. All endpoints use `credential_signing: sigv4` and
`signing_service: s3` so the proxy re-signs requests automatically.
Includes a binaries allowlist for Python, curl, and the AWS CLI.
- End-to-end walkthrough added in `examples/aws-s3-sts.md`.
- Provider docs updated in `docs/sandboxes/manage-providers.mdx`.
## Policy: single-label `*` wildcard in middle DNS labels
- `validate_host_wildcard` in `openshell-policy` now permits `*` as a
complete middle label (e.g. `*.s3.*.amazonaws.com`) in addition to the
existing leading-label and intra-label positions. A bare `*` label
matches exactly one DNS label via Rego's `glob.match` with `.`
separator, consistent with TLS wildcard semantics.
- OPA endpoint matching and L7 config lookup updated to handle
middle-label wildcards: `host_matches_wildcard_middle` rule and
`endpoint_config_for_middle_wildcard` in Rego.
- Validation rejects `**` in any position and partial wildcards in
middle labels (e.g. `s*.example.com` in a non-leading label).
## TUI
- Added `AwsStsAssumeRole` match arm to the refresh strategy label in
the provider settings view.
Validated end-to-end: gateway mints STS credentials, sandbox runs boto3
PutObject/ListObjects/GetObject against real S3 through the SigV4
re-signing proxy. The sandbox sees only placeholder credentials.
russellb added a commit to russellb/OpenShell that referenced this pull request Jun 30, 2026
…rofile
Add server-side AWS STS AssumeRole credential refresh so gateways can
mint short-lived AWS credentials for sandboxes. Combined with the SigV4
proxy re-signing from NVIDIA#1638, sandboxes can access S3 (and other AWS
services) without ever seeing real credentials.
## STS credential refresh (server)
- New `AwsStsAssumeRole` variant in `ProviderCredentialRefreshStrategy`.
- `provider_refresh.rs` implements the refresh loop: calls
`sts:AssumeRole` using the gateway's ambient AWS credentials (or
explicit long-lived keys from material), stores the resulting
`AccessKeyId`, `SecretAccessKey`, and `SessionToken` on the provider,
and schedules re-rotation before expiry.
- `ConfigureProviderRefresh` and `RotateProviderCredential` RPCs gated
behind the `providers_v2_enabled` setting. Validates that configuring
STS refresh for one provider won't collide with credential keys already
held by another provider attached to the same sandbox.
- Proto: `AWS_STS_ASSUME_ROLE = 3` on `ProviderCredentialRefreshStrategy`.
## Provider profiles: `aws` and `aws-s3`
AWS provider profiles follow the same pattern as the existing
`aws-bedrock` profile: a generic base plus service-specific variants.
- `providers/aws.yaml`: base AWS profile with STS refresh material
(role_arn, session_name, external_id, aws_region, optional long-lived
keys) but no endpoints or binaries allowlist. Intended for AWS
services that don't yet have a dedicated profile — the user attaches
their own policy to supply endpoints. This is the same role
`google-cloud` plays relative to `google-vertex-ai`.
- `providers/aws-s3.yaml`: S3-specific profile that adds pre-configured
endpoints covering regional (`*.s3.*.amazonaws.com`), global
(`*.s3.amazonaws.com` with `signing_region: us-east-1`), and dualstack
variants. All endpoints use `credential_signing: sigv4` and
`signing_service: s3` so the proxy re-signs requests automatically.
Includes a binaries allowlist for Python, curl, and the AWS CLI.
- End-to-end walkthrough added in `examples/aws-s3-sts.md`.
- Provider docs updated in `docs/sandboxes/manage-providers.mdx`.
## Policy: single-label `*` wildcard in middle DNS labels
- `validate_host_wildcard` in `openshell-policy` now permits `*` as a
complete middle label (e.g. `*.s3.*.amazonaws.com`) in addition to the
existing leading-label and intra-label positions. A bare `*` label
matches exactly one DNS label via Rego's `glob.match` with `.`
separator, consistent with TLS wildcard semantics.
- OPA endpoint matching and L7 config lookup updated to handle
middle-label wildcards: `host_matches_wildcard_middle` rule and
`endpoint_config_for_middle_wildcard` in Rego.
- Validation rejects `**` in any position and partial wildcards in
middle labels (e.g. `s*.example.com` in a non-leading label).
## SigV4 body validation fix
- Allow bodyless requests (GET/HEAD/DELETE) through the SignBody path.
boto3 sets `x-amz-content-sha256` to the empty-body hash on all
requests, routing them through SignBody via `detect_payload_mode`.
Previously `BodyLength::None` was rejected; now it signs with the
empty-body hash, matching AWS SDK behavior.
## TUI
- Added `AwsStsAssumeRole` match arm to the refresh strategy label in
the provider settings view.
Validated end-to-end: gateway mints STS credentials, sandbox runs boto3
PutObject/ListObjects/GetObject against real S3 through the SigV4
re-signing proxy. The sandbox sees only placeholder credentials.
Signed-off-by: Russell Bryant <rbryant@redhat.com>
russellb added a commit to russellb/OpenShell that referenced this pull request Jul 14, 2026
…rofile
Add server-side AWS STS AssumeRole credential refresh so gateways can
mint short-lived AWS credentials for sandboxes. Combined with the SigV4
proxy re-signing from NVIDIA#1638, sandboxes can access S3 (and other AWS
services) without ever seeing real credentials.
- New `AwsStsAssumeRole` variant in `ProviderCredentialRefreshStrategy`.
- `provider_refresh.rs` implements the refresh loop: calls
`sts:AssumeRole` using the gateway's ambient AWS credentials (or
explicit long-lived keys from material), stores the resulting
`AccessKeyId`, `SecretAccessKey`, and `SessionToken` on the provider,
and schedules re-rotation before expiry.
- `ConfigureProviderRefresh` and `RotateProviderCredential` RPCs gated
behind the `providers_v2_enabled` setting. Validates that configuring
STS refresh for one provider won't collide with credential keys already
held by another provider attached to the same sandbox.
- Proto: `AWS_STS_ASSUME_ROLE = 3` on `ProviderCredentialRefreshStrategy`.
AWS provider profiles follow the same pattern as the existing
`aws-bedrock` profile: a generic base plus service-specific variants.
- `providers/aws.yaml`: base AWS profile with STS refresh material
(role_arn, session_name, external_id, aws_region, optional long-lived
keys) but no endpoints or binaries allowlist. Intended for AWS
services that don't yet have a dedicated profile — the user attaches
their own policy to supply endpoints. This is the same role
`google-cloud` plays relative to `google-vertex-ai`.
- `providers/aws-s3.yaml`: S3-specific profile that adds pre-configured
endpoints covering regional (`*.s3.*.amazonaws.com`), global
(`*.s3.amazonaws.com` with `signing_region: us-east-1`), and dualstack
variants. All endpoints use `credential_signing: sigv4` and
`signing_service: s3` so the proxy re-signs requests automatically.
Includes a binaries allowlist for Python, curl, and the AWS CLI.
- End-to-end walkthrough added in `examples/aws-s3-sts.md`.
- Provider docs updated in `docs/sandboxes/manage-providers.mdx`.
- `validate_host_wildcard` in `openshell-policy` now permits `*` as a
complete middle label (e.g. `*.s3.*.amazonaws.com`) in addition to the
existing leading-label and intra-label positions. A bare `*` label
matches exactly one DNS label via Rego's `glob.match` with `.`
separator, consistent with TLS wildcard semantics.
- OPA endpoint matching and L7 config lookup updated to handle
middle-label wildcards: `host_matches_wildcard_middle` rule and
`endpoint_config_for_middle_wildcard` in Rego.
- Validation rejects `**` in any position and partial wildcards in
middle labels (e.g. `s*.example.com` in a non-leading label).
- Allow bodyless requests (GET/HEAD/DELETE) through the SignBody path.
boto3 sets `x-amz-content-sha256` to the empty-body hash on all
requests, routing them through SignBody via `detect_payload_mode`.
Previously `BodyLength::None` was rejected; now it signs with the
empty-body hash, matching AWS SDK behavior.
- Added `AwsStsAssumeRole` match arm to the refresh strategy label in
the provider settings view.
Validated end-to-end: gateway mints STS credentials, sandbox runs boto3
PutObject/ListObjects/GetObject against real S3 through the SigV4
re-signing proxy. The sandbox sees only placeholder credentials.
Signed-off-by: Russell Bryant <rbryant@redhat.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2eRequires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

7 participants

@jhjaggars@russellb@mrunalp@TaylorMutch@pimlock@maxamillion@johntmyers