feat(gateway): sign S3-compatible endpoints via an aws secret type - #418
Open
tenequm wants to merge 3 commits into
Open
feat(gateway): sign S3-compatible endpoints via an aws secret type#418tenequm wants to merge 3 commits into
tenequm wants to merge 3 commits into
Conversation
Adds an "aws" secret type whose value carries S3 credentials and whose
injectionConfig carries { region, service? }. The gateway emits the
existing internal x-onecli-aws-* headers plus a service header, and the
SigV4 finalizer signs with the explicit service when set - so
S3-compatible stores (Hetzner, R2, B2, MinIO) work, not just
*.amazonaws.com hosts. Real AWS hosts are unchanged (no service header
-> hostname parsing as before).
createSecret enforced JSON-with-both-keys and a present region for aws secrets, but updateSecret did not, so an edit could set injectionConfig to null or the value to non-JSON and the secret would silently inject nothing at request time. Share one isValidAwsValueJson validator between the create schema and updateSecret, and reject an aws update that drops the region or supplies a malformed value.
tenequm
marked this pull request as ready for review
July 7, 2026 19:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I have read the CONTRIBUTING.md file.
YES
What kind of change does this PR introduce?
Feature - gateway request signing. Adds an
awssecret type so the gateway can SigV4-sign requests to S3-compatible endpoints (Hetzner Object Storage, Cloudflare R2, Backblaze B2, MinIO), not just*.amazonaws.com. Credentials stay in the vault; the agent's machine never holds them.Closes #419. Related but separate: #417 (non-HTTP credential flows). Discussed on the 2026-07-07 call with @johnnyfish and @guyb1, who invited this PR. Opening as a draft for design feedback before the dashboard UI (see Additional context).
What is the current behavior?
The gateway has a working SigV4 finalizer (
apps/gateway/src/gateway/finalizers/aws_sigv4.rs), but it only fires for the compiled-inawsprovider whose host rules match.amazonaws.com/.api.aws. For a non-AWS S3 host two things block it:parse_service_region("nbg1.your-objectstorage.com")returns("unknown", ...), so the signature scope is wrong and S3 rejects it.A
genericsecret can't fill the gap: it injects a static header, while S3 auth is a computed per-request signature.What is the new behavior?
An
awssecret type, resolved through the secret layer (user-configured hosts) rather than the compiled-in provider registry - a Hetzner bucket isn't "AWS", so modeling it as a user secret with a free-formhostPatternis the honest fit and gives multi-bucket / wildcard support for free.secret_inject.rs- new"aws"arm. Value is JSON{ accessKeyId, secretAccessKey, sessionToken? };injectionConfigis{ region, service? }(service defaults tos3). Emits the internalx-onecli-aws-*headers plus a newx-onecli-aws-service.aws_sigv4.rs-AwsCredentialsgains an optionalservice; when set it overrides hostname parsing so any S3-compatible host signs correctly. AWS hosts are unchanged (no service header -> parsed from hostname as today).forward.rs- if no connection/host finalizer matched but the request carries the internal AWS access-key header (anawssecret injected it), run the SigV4 finalizer.finalize_requestis a no-op without those headers, so non-AWS traffic is untouched.awsadded to the secret type enum and theinjectionConfigunion ({ region, service? }), with validation that the value is JSON carrying both keys and that a region is present, on both the create and update paths.Additional context
Design note (for reviewers). I considered adding an S3-compatible host rule to the
awsapp connection withcredential_host_field(the JFrog*.jfrog.ioidiom) instead. I went with the secret layer because a non-AWS endpoint isn't conceptually an "AWS" connection, and the secret path already models per-host user credentials with wildcard support. Happy to move it to the connection side if you'd rather keep all AWS-signing under theawsprovider - it's your vocabulary. Likewise,forward.rsdispatch uses header-presence detection (smallest change, no resolve-cache format change); I can thread an explicit finalizer through the secret resolve result instead if you prefer. No new trust surface either way: an agent can already setx-onecli-aws-*toward*.amazonaws.comand only ever signs with its own supplied creds - no vault material is exposed.Deliberately out of scope (follow-up). No dashboard dialog yet - the web
SecretTypeunion is local to the dialog, so this PR keeps the UI compiling untouched and the feature is reachable via the API/SDK/CLI. If the design lands, the UI add is a small follow-up: an "AWS / S3-compatible" option with Access Key ID / Secret Access Key / Region / optional Service fields that packs the two keys into the JSONvalueand region/service intoinjectionConfig.Verification.
pnpm check(lint + types + format) clean across the monorepo.cargo fmt --checkandcargo clippy -- -D warningsclean;cargo testgreen (unit + integration), including the service override, a Hetzner-style host signing ass3, and theawsinjection shape.vitestgreen (284), includingawscreate/value validation and the shared value validator. Create and update both reject a non-JSON value or a missing region, so a secret can't be edited into a silently-broken state.Try it. Create an
awssecret (via API) withhostPattern: nbg1.your-objectstorage.com, value{"accessKeyId":"...","secretAccessKey":"..."},injectionConfig: {"region":"eu-central-1"}, assign it to an agent, and point an S3 client at the bucket through the gateway - it signs on the wire, the key never lands on the sandbox.