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 1 commit 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
| - 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.
There was a problem hiding this comment.
Let's create an issue with context and we can follow up there.
|
@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! |
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: |
|
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: |
|
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: |
|
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: |
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: |
|
/ok to test b0365a1 |
|
Hi @russellb, thanks for addressing all the comments. I think this is ready For the context, the I agree on postponing this item:
IMO this is now ready, if you could please resolve conflicts, I will approve and we can get this merged. |
Add gateway-managed AWS STS credential refresh (provider-v2, NVIDIA#1576). The gateway calls sts:AssumeRole and writes three short-lived credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) to the provider record; the proxy re-signs requests with SigV4. Adds the aws and aws-s3 provider profiles and a declarative multi-output refresh model (additional_outputs) so one AssumeRole co-mints all three credentials. Hardening (from review): - Reject caller-supplied STS endpoint overrides; the endpoint is always region-resolved in production, test-only otherwise (CWE-918). - Validate a single canonical STS profile binding at configure (matching strategy, canonical AWS_ACCESS_KEY_ID primary, both declared outputs); remove the mint-path fallback to hardcoded sibling keys. - Require aws_access_key_id/aws_secret_access_key as a complete pair, with optional aws_session_token for temporary source credentials. - Enforce the providers_v2 gate on every mint (worker and manual rotate), not just at configure time. - Clear refresh-owned expiries for the primary and every pinned additional output inside the provider CAS closure, preserving independent changes. - Gate the provider credential write on refresh-generation ownership so a rotation cannot resurrect a deleted refresh or write stale-generation credentials (CWE-362). - Serialize refresh configuration against sandbox mutations to reserve credential keys atomically within a gateway process. - Return a typed Conflict for absent-row conditional writes instead of a backend-dependent error string. - Mirror runtime whole-middle-label host wildcard semantics in the prover. Signed-off-by: Russell Bryant <rbryant@redhat.com>
b0365a1 to
e3ad817
Compare
|
Conflicts are resolved, but I'm running through the full example scenario to make sure it's good, as the resolved conflicts were not trivial edits. |
Testing complete. Still functional. |
|
/ok to test e3ad817 |
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