feat(providers): AWS STS AssumeRole refresh strategy and aws-s3 profile#1782
feat(providers): AWS STS AssumeRole refresh strategy and aws-s3 profile#1782russellb wants to merge 16 commits into
Conversation
|
This pull request has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. |
|
This is still waiting on #1638 to go in first. |
1b1cbd3 to
cb10f83
Compare
|
/ok to test cb10f83 |
pimlock
left a comment
There was a problem hiding this comment.
Hi @russellb, I did a first pass, but I run out of time to get through this fully. I needed to check this out locally and run this to understand how it worked (wasn't as familiar with the cred refresh).
I added a few comments.
The biggest item I had was around how the implicit extra env vars that are managed by the refresh, but not defined as such in the provider profile.
I did investigation around this and options. I would need to spend more time figuring out what makes sense, but I will need to come back to it on Thursday. In the meantime, I can share raw notes from agent, but I haven't checked these very deeply yet: https://gist.github.com/pimlock/6a99b3d9c4e19c6efc3b0d2da59b6b63
| ```shell | ||
| openshell settings set --global --key providers_v2_enabled --value true --yes | ||
|
|
||
| openshell provider create --name my-aws --type aws-s3 |
There was a problem hiding this comment.
This call will fail without setting a dummy value for --credential AWS_ACCESS_KEY_ID=dummy (interestingly, it doesn't even need to be a declared credential, something like --credential FOO=bar will do).
I think ideally we could use the --runtime-credentials flag, but using that fails, because of the STS refresh not being in here:
OpenShell/crates/openshell-providers/src/profiles.rs
Lines 478 to 486 in cb10f83
However that won't fully work either, since the refresh is only configured on the access_key_id and not on the secret key/session token.
That last thing is one thing that I wonder how we can solve -> the only cred that is marked as being refreshed is the access_key_id, but in fact the secret key and session token are as well.
And right now we are missing a way to represent this.
There was a problem hiding this comment.
Both parts addressed in cbe9b43.
Runtime credentials: root cause was is_gateway_mintable in openshell-providers omitting AwsStsAssumeRole — it had diverged from the server-side check. Consolidated into one shared function that includes STS, so the profile's credentials are runtime-resolvable and openshell provider create --type aws-s3 --runtime-credentials works with no placeholder --credential. Docs and examples/aws-s3-sts.md updated.
Representing multi-output refresh: went declarative. A refresh now declares the extra credentials it co-mints via additional_outputs, mapping each strategy-produced output to a sibling credential:
refresh:
strategy: aws_sts_assume_role
additional_outputs:
- output: secret_access_key
credential: secret_access_key
- output: session_token
credential: session_tokenThe resolved output→env-key mapping is pinned into the refresh state at configure time (so later profile edits can't silently redirect where minted values are written), and minting, collision reservation, and active_provider_environment_keys all read it instead of the previously hardcoded AWS list.
Concretely this closes the gap you described: configure now reserves all three keys up front, so "configure STS, don't rotate, another provider claims AWS_SECRET_ACCESS_KEY, attach both" is rejected at attach time instead of failing at mint. The SigV4 signer stays on the standard AWS names, now enforced by profile validation (outputs must resolve to AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN and the primary to AWS_ACCESS_KEY_ID).
| | ProviderCredentialRefreshStrategy::GoogleServiceAccountJwt | ||
| | ProviderCredentialRefreshStrategy::AwsStsAssumeRole | ||
| ) | ||
| } |
There was a problem hiding this comment.
I just noticed that this check is duplicated in
OpenShell/crates/openshell-providers/src/profiles.rs
Lines 478 to 486 in cb10f83
Can we replace this one with the one in openshell-providers?
There was a problem hiding this comment.
Consolidated into a single is_gateway_mintable_strategy in openshell-providers; both server call sites (provider_refresh.rs and grpc/provider.rs) now delegate to it. Good catch that these had diverged — the providers-side copy was missing AwsStsAssumeRole, which is exactly what broke --runtime-credentials (your docs comment). Fixing the duplication fixes both. cbe9b43
| - name: access_key_id | ||
| description: AWS access key ID (gateway-minted via STS) | ||
| env_vars: [AWS_ACCESS_KEY_ID] | ||
| required: true | ||
| refresh: | ||
| strategy: aws_sts_assume_role | ||
| refresh_before_seconds: 300 | ||
| max_lifetime_seconds: 3600 | ||
| material: | ||
| - name: role_arn | ||
| description: ARN of the IAM role to assume | ||
| required: true | ||
| secret: false | ||
| - name: session_name | ||
| description: Session name for CloudTrail attribution | ||
| required: false | ||
| secret: false | ||
| - name: external_id | ||
| description: External ID for cross-account role assumption | ||
| required: false | ||
| secret: false | ||
| - name: aws_region | ||
| description: AWS region for STS endpoint | ||
| required: false | ||
| secret: false | ||
| - name: aws_access_key_id | ||
| description: Long-lived IAM access key (only needed if gateway lacks ambient AWS credentials) | ||
| required: false | ||
| secret: false | ||
| - name: aws_secret_access_key | ||
| description: Long-lived IAM secret key (only needed if gateway lacks ambient AWS credentials) | ||
| required: false | ||
| secret: true | ||
| - name: secret_access_key | ||
| description: AWS secret access key (co-managed with access_key_id) | ||
| env_vars: [AWS_SECRET_ACCESS_KEY] | ||
| required: true | ||
| - name: session_token | ||
| description: AWS session token (co-managed with access_key_id) | ||
| env_vars: [AWS_SESSION_TOKEN] | ||
| required: true |
There was a problem hiding this comment.
I was wondering if there is a way to reuse credentials and only define endpoints in the service-specific profile.
I think the closest we could do today is something like:
aws.yamlhas credentials (what it already has)aws-service.yamlhas endpoints- the user needs to attach both providers to the sandbox for this to work
The benefit here would be they configure the aws profile instance once and can reuse it with multiple services.
The problem is that the relationship is implicit and running a sandbox with aws-service, but not aws will fail in runtime during signing. Perhaps there is a way to make that relationship explicit and be able to validate it.
There was a problem hiding this comment.
cc @johntmyers I wonder if something like this came up before:
- there is one credential type for given suite of services
- endpoints for each service are defined separately
In this example:
- provider_1: I want to bring in AWS creds
- provider_2: I want to access S3
- provider_3: I want to access Lambda
There was a problem hiding this comment.
This is a nice direction and I think it's the natural next step. The blocker is exactly what you named — the credentials↔endpoints relationship would be implicit and only fail at signing time. Making it explicit and validated is worth its own design pass (it overlaps the additional_outputs model added in this PR), so rather than expand this PR I'd like to track it as a follow-up. Flagging it here as a follow-up for now — happy to open a tracking issue for you and @johntmyers to weigh in on.
|
@pimlock thank you very much for the review! I know it can be very time-consuming. I will dig into the comments tomorrow. |
|
/ok to test cbe9b43 |
|
@russellb Thanks for addressing the feedback! I'm running a bit behind, so I'll do a final review tomorrow and follow up. |
No problem! |
…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>
AWS STS AssumeRole mints three credentials from one refresh, but the model could only represent one; the extra AWS keys were hardcoded across the server. Add `additional_outputs` to the refresh profile so a refresh declares the sibling credentials it co-mints, resolve that mapping to concrete env keys, pin it in the refresh state, and drive minting, collision reservation, and env-key surfacing from it. - Consolidate the gateway-mintable strategy check into one shared function that includes AwsStsAssumeRole, fixing runtime resolvability so aws/aws-s3 providers can be created with --runtime-credentials. - Reserve all co-minted keys at configure time (resolved from the profile) instead of a hardcoded AWS list, so a configured-but-not-yet-minted refresh cannot collide at mint time. - Validate additional_outputs: known/required outputs per strategy, unique ids, existing single-env-var siblings, no self-refresh, standard AWS env keys, and pinned primary key. - Update aws/aws-s3 profiles and provider docs. Refs NVIDIA#1576 Signed-off-by: Russell Bryant <rbryant@redhat.com>
cbe9b43 to
2c6dfb8
Compare
|
/ok to test 2c6dfb8 |
|
Label |
PR Review StatusValidation: This PR is project-valid because it implements the linked provider-v2 feature request #1576 and completes the AWS credential path enabled by merged PR #1638. Head SHA: Thanks @pimlock — I checked the runtime-credential resolution, duplicated mintability logic, declarative multi-output mapping, and collision-reservation concerns from your July 7 review. Thanks @russellb — the July 8 update does address those original concerns with the shared mintability check and pinned
Please add regression coverage for the endpoint override rejection, partial source credentials, profile/strategy/canonical-key validation, v2 disable-after-config rotation, and additional-output expiry cleanup. No local tests or builds were run as part of this code-only review. Docs: The feature is otherwise covered in existing Fern-managed pages and needs no navigation change, but the docs gate fails until the secret-handling example is corrected. CI: Next state: |
The aws_sts_assume_role refresh read an sts_endpoint_url from refresh material and, when it pointed at a loopback host, redirected the AWS-signed AssumeRole request there. That let a caller aim a signed STS request at an arbitrary loopback service (CWE-918 SSRF) via undocumented production material. Compile the endpoint override out of production builds: it is now a test-only helper that unit tests use to target a local mock STS. Reject the sts_endpoint_url material key at the configure boundary so it can never reach a stored refresh state outside tests. Production always resolves the STS endpoint from the region. Signed-off-by: Russell Bryant <rbryant@redhat.com>
Configuring an aws_sts_assume_role refresh accepted any provider and credential key, even a generic provider with no profile or a co-managed output key. The mint path then fell back to hardcoded AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN names, so a refresh with no resolved output mapping would still write credentials the SigV4 signer trusts by name (CWE-20). Require a single canonical profile binding at the configure boundary: the credential's profile must declare a matching refresh strategy, the credential key must be the canonical primary env key (AWS_ACCESS_KEY_ID), and every required strategy output must resolve to its canonical env key from the profile's additional_outputs. The canonical primary key is stored as the refresh credential key. Remove the mint-path fallback so a missing resolved output mapping fails closed instead of guessing. Signed-off-by: Russell Bryant <rbryant@redhat.com>
A refresh that supplied only aws_access_key_id or only aws_secret_access_key silently ignored the lone key and fell back to the gateway's ambient AWS identity, minting against an unintended principal (CWE-20). Require both keys present or both absent, rejecting a partial pair at the configure boundary and again in the mint path. Signed-off-by: Russell Bryant <rbryant@redhat.com>
The providers_v2_enabled gate for aws_sts_assume_role was checked only in handle_configure_provider_refresh. Once a refresh was configured, the worker sweep and manual rotation kept minting STS credentials even after the setting was disabled. Enforce the gate in refresh_provider_credential, the shared execution path for both the worker and manual rotation, so disabling providers_v2_enabled halts further mints. The rejection is recorded on the refresh state for observability. Signed-off-by: Russell Bryant <rbryant@redhat.com>
Deleting an AWS STS refresh cleared only the primary credential's expiry, leaving stale refresh-owned expiries on the co-managed AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN credentials. Clear the expiry for the primary and every pinned additional output whose stored expiry still matches the refresh's, in a single provider update. Expiries that were independently changed (no longer matching the refresh) are left untouched. Signed-off-by: Russell Bryant <rbryant@redhat.com>
The off-AWS AWS STS refresh example expanded $AWS_SECRET_ACCESS_KEY into a --material argument, exposing the long-lived secret in the host process table (CWE-200). Use --secret-material-env aws_secret_access_key=AWS_SECRET_ACCESS_KEY so the CLI reads the value from its own environment and marks it secret. Signed-off-by: Russell Bryant <rbryant@redhat.com>
|
Addressed each finding in its own commit on top of 2c6dfb8. Summary below; commit SHAs referenced per item.
Fixed in e7a978c. The endpoint override is now a
Fixed in 916e9d2. Added
Fixed in 29970bb. The mint path now treats the pair as all-or-nothing and errors on a partial pair instead of falling through to the ambient identity; configure rejects the partial pair early with the same message.
Fixed in a726dfc. The gate now runs in
Fixed in 6799dcc.
Fixed in e8495fd. The off-AWS example now uses
Regression tests land with each corresponding fix rather than as a separate commit: endpoint-override rejection and partial-source-credential rejection (configure + mint), profile/strategy/canonical-key validation (no-profile and non-canonical-key cases), v2 disable-after-config rotation, and additional-output expiry cleanup (with an independently-changed expiry preserved). Verification: One note for the CI gate: the |
Re-check After Author UpdateThanks @russellb. I reviewed current head What I checked: the full current diff, the six fix commits, the STS configure/mint/delete paths, regression coverage, middle-label wildcard consumers, Fern docs, and the current CI state. The endpoint-override, canonical profile binding, partial source-credential, providers-v2 gate, and process-argument secret findings are resolved. Multi-key expiry cleanup is present, but its preservation guarantee is not concurrency-safe. Disposition: partially resolved; author changes are still required. Remaining items:
No local tests or builds were run as part of this code-only review. Docs: the Fern-managed pages are updated and need no navigation change; the remaining example corrections are listed above. CI: Next state: |
…osure Deleting a refresh decided which expiries to clear from a provider snapshot read at the top of the handler, then applied an unconditional zero to each. A rotation or provider update landing between that read and the write was silently clobbered (CWE-362). Move the equality check and removal into a pure helper run inside the provider CAS closure, so the decision reflects the current stored provider rather than a stale snapshot. Only expiries that still match the deleted refresh are cleared; concurrently changed values are preserved. Signed-off-by: Russell Bryant <rbryant@redhat.com>
The boto3 walkthrough ran pip install inside a sandbox attached only to the aws-s3 profile, which permits S3 egress but not PyPI, so the install would be blocked. Attach the built-in pypi profile so pip can reach PyPI, and note the custom-image alternative. Also delete the s3-curl sandbox (and the pypi provider) in the cleanup section. Signed-off-by: Russell Bryant <rbryant@redhat.com>
The prover's host-pattern overlap only wildcard-matched the first DNS label and compared the remaining labels literally, so it missed overlap for the whole middle-label wildcards this PR added to the aws-s3 profile (e.g. *.s3.*.amazonaws.com). It therefore under-reported credential-associated policy risk (CWE-693). Match label-for-label instead: a * (or partial * glob) matches exactly one label in any position, literal labels compare literally, and a leading ** still matches one or more labels. Cover matching and non-matching label counts in tests. Signed-off-by: Russell Bryant <rbryant@redhat.com>
|
Addressed the three remaining items from the re-check, each in its own commit on top of e8495fd.
Fixed in 557221f. The equality check and removal now live in a pure helper (
Fixed in 623512e.
Fixed in 6c58d2a. The boto3 walkthrough now creates a Verification: |
Re-check After Author UpdateThanks @russellb. I reviewed current head What I checked: the full current diff, the focused commits since Disposition: partially resolved; one blocking correctness/security issue remains. Remaining items:
No local tests, builds, clippy, pre-commit, or E2E were run as part of this code-only review. Docs: the user-facing behavior is covered by existing Fern pages under CI: Next state: |
refresh_provider_credential read the refresh state, awaited the STS (or OAuth) request, then wrote credentials and persisted state with an unconditional upsert. A delete-refresh completing while the request was in flight was silently undone: the rotation re-minted credentials and recreated the deleted refresh state, including stored source-credential material (CWE-362). Capture the refresh generation at the start and make every terminal persist a version-matched UPDATE (never an insert), so a deleted or superseded refresh is detected and never recreated. Re-check that the refresh still exists after minting, before writing credentials into the provider, so a removed refresh is not re-minted. Add a deterministic regression test that pauses mock STS, deletes the refresh, releases STS, and verifies the refresh state and provider credentials are unchanged. Signed-off-by: Russell Bryant <rbryant@redhat.com>
…cally Refresh configuration validated key availability and persisted the reservation as separate steps. Two concurrent configures of providers attached to the same sandbox could each pass validation before either persisted, then both reserve the same primary or additional output key, leaving conflicting unusable refreshes (CWE-362). Hold the process-wide sandbox sync guard across the reserve-and-persist sequence — the same guard sandbox create/attach and profile changes take — so configurations and sandbox mutations that share the credential-key namespace are serialized. Add a concurrent-configure regression test asserting exactly one configuration reserves the key. Signed-off-by: Russell Bryant <rbryant@redhat.com>
|
Addressed both concurrency items from the latest re-check (head 623512e), each in its own commit.
Fixed in f462775. The generation acts as the store-backed check you described (a delete bumps the row out of existence, so the version-matched write fails closed). I did not introduce a separate lease/tombstone object — the existing per-row Deterministic regression test Residual window I want to be transparent about: a delete that lands between the post-mint existence re-check and the provider credential write can still leave the freshly minted credential values in the provider. That is consistent with delete semantics —
Fixed in bf68e0b. Scope note: this guard serializes within a gateway process, matching the existing concurrency model for sandbox-affecting mutations. It is not cross-replica; a store-backed reservation would be needed for multi-writer HA, which would be a broader change than this profile-scoped PR. Verification: |
Re-check After Author UpdateThanks @russellb. I reviewed current head What I checked: the full current diff, the two commits since Disposition: partially resolved; author changes are still required. Remaining items:
No local tests, builds, clippy, pre-commit, or E2E were run as part of this code-only review. Docs: the user-facing behavior is covered in existing Fern pages; no CI: DCO is green. Required Branch Checks, Helm Lint, and E2E are waiting for the copy-PR mirror on this head. Next state: |
…ites A version-matched put_if that matched no row reported an absent row as a stringy Database error while a version mismatch was a typed Conflict. Callers that need to distinguish 'condition not met' from a real error had to match backend-dependent error text. Report both cases as Conflict: an absent row carries current_resource_version: None, a version mismatch carries the current version. Applied to both the SQLite and Postgres backends. Signed-off-by: Russell Bryant <rbryant@redhat.com>
…ownership After minting, the rotation checked only that a refresh with the same name still existed, wrote credentials to the provider, and only then did the version-matched refresh-state write. A refresh deleted-and-recreated during mint, or a losing concurrent rotation, could write credentials minted from a stale generation before returning Aborted (CWE-362). Claim the refresh generation first: perform the version-matched refresh-state write before touching the provider. It succeeds only if the refresh still holds the generation the rotation started from; on a lost claim (deleted, recreated, or superseded) it returns None and the rotation aborts leaving both the refresh state and provider unchanged. No transaction spans the two objects — the store exposes only per-object CAS — so this optimistic claim-then-write is the ownership guarantee: only the generation owner writes credentials. Uses the typed conditional-write Conflict result instead of matching error strings. Adds a deterministic superseded-mid-flight regression test (a concurrent write bumps the generation while STS is paused) alongside the existing deleted-mid-flight test. Signed-off-by: Russell Bryant <rbryant@redhat.com>
…ntials The explicit AWS source-credential path always passed no session token, so temporary credentials from AWS SSO or a prior AssumeRole (which are only valid with a session token) failed to sign the STS request. Accept an optional secret aws_session_token material and pass it to the AWS SDK. It requires the aws_access_key_id and aws_secret_access_key pair, enforced at both the configure boundary and the mint path. Declare it as optional secret material on the aws and aws-s3 profiles and document it. Signed-off-by: Russell Bryant <rbryant@redhat.com>
PR Review StatusValidation: This PR is project-valid because it implements the linked provider-v2 request #1576 and completes the AWS credential path enabled by merged PR #1638. Head SHA: Thanks @russellb. I checked the three commits since the prior
Non-blocking follow-ups:
No local tests, builds, clippy, pre-commit, or E2E were run as part of this code-only review. Docs: the direct UX is covered in existing Fern pages and needs no navigation change, subject to the reference correction above. CI: Next state: |
|
Addressed the two warnings and the first blocking item (head b0365a1). On the second blocking item I'm pushing back — reasoning below.
Fixed in a5a89f0. The rotation now claims the generation before touching the provider: it performs the version-matched refresh-state write ( On literal single-operation atomicity across the two objects: the store exposes only per-object CAS — there is no multi-object transaction API — so a single atomic datastore op spanning the refresh row and the provider row isn't available. The claim-then-write ordering is the optimistic-concurrency equivalent: generation ownership is the gate, and the loser mutates nothing. (I also switched the persist to consume a typed conditional-write result rather than matching error strings — see the CWE-693 item below.) Deterministic tests:
You're right that the gateway is active-active (I checked:
The guard added in this PR is still a strict improvement — it closes the same-process race (the common case, including single-replica rollouts). I'd propose the cross-replica reservation be tracked as its own follow-up rather than gating this feature. Happy to file that issue / open an RFC if you agree with the scoping.
Fixed in b0365a1. The explicit source-credential path now accepts an optional secret
Fixed in 31c339e. Verification: |
Summary
Add gateway-owned AWS STS AssumeRole as a credential refresh strategy, ship
awsandaws-s3provider profiles, and extend policy validation to support single-label*wildcards in middle DNS labels.Combined with the SigV4 proxy re-signing from #1638 (now merged), sandboxes can access S3 and other AWS services without ever seeing real credentials — the gateway mints short-lived STS credentials and the proxy re-signs requests on the fly.
Related Issue
Refs #1576
Changes
STS credential refresh (
openshell-server)AwsStsAssumeRolevariant inProviderCredentialRefreshStrategy(proto fieldAWS_STS_ASSUME_ROLE = 6).provider_refresh.rsimplements the refresh loop: callssts:AssumeRoleusing the gateway's ambient AWS credentials (or explicit long-lived keys from refresh material), stores the resultingAccessKeyId,SecretAccessKey, andSessionTokenon the provider, and schedules re-rotation before expiry.ConfigureProviderRefreshandRotateProviderCredentialRPCs gated behind theproviders_v2_enabledsetting.Provider profiles (
openshell-providers)AWS provider profiles follow the same pattern as the existing
aws-bedrockprofile: 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. Same rolegoogle-cloudplays relative togoogle-vertex-ai.providers/aws-s3.yaml— S3-specific profile that adds pre-configured endpoints covering regional (*.s3.*.amazonaws.com), global (*.s3.amazonaws.comwithsigning_region: us-east-1), and dualstack variants. All endpoints usecredential_signing: sigv4andsigning_service: s3. Includes a binaries allowlist for Python, curl, and the AWS CLI.Policy: single-label
*wildcard in middle DNS labels (openshell-policy)validate_host_wildcardnow permits*as a complete middle label (e.g.*.s3.*.amazonaws.com) in addition to leading-label and intra-label positions. Matches exactly one DNS label via Rego'sglob.matchwith.separator, consistent with TLS wildcard semantics.host_matches_wildcard_middlerule andendpoint_config_for_middle_wildcardin Rego.**in any position and partial wildcards in middle labels.TUI (
openshell-tui)AwsStsAssumeRolematch arm to the refresh strategy label in the provider settings view.Documentation
docs/sandboxes/manage-providers.mdx: STS refresh setup instructionsexamples/aws-s3-sts.md: end-to-end manual test guide (create IAM role, configure provider, test S3 PUT/GET/LIST from sandbox)Testing
cargo test -p openshell-providers— 65 passed (STS serde roundtrip, aws/aws-s3 profile parsing, endpoint validation)cargo test -p openshell-policy— 91 passed (wildcard DNS validation, middle-label wildcards, credential_signing validation)cargo test -p openshell-server— all passed (STS configure v2 gate, success path, credential key collision)cargo test -p openshell-supervisor-network— all passed (wildcard host matching, OPA endpoint config)cargo test -p openshell-tui— 24 passedChecklist