feat(sts): send RFC 8707 resource and audience on token exchange#2106
feat(sts): send RFC 8707 resource and audience on token exchange#2106QuentinBisson wants to merge 6 commits into
Conversation
|
Python companion: #2107 (same env vars across both runtimes). |
a4d3268 to
6a887c7
Compare
There was a problem hiding this comment.
Pull request overview
This PR wires optional RFC 8707 resource and RFC 8693 audience parameters through the Go ADK STS token-exchange flow so exchanged tokens can be scoped to the intended backend (configured via new KAGENT_TOKEN_RESOURCE / KAGENT_TOKEN_AUDIENCE env vars).
Changes:
- Register
KAGENT_TOKEN_RESOURCEandKAGENT_TOKEN_AUDIENCEin the core env registry for discoverability. - Add an options pattern to the STS token propagation plugin and plumb resource/audience into
ExchangeTokenWithActorToken. - Read env vars at agent runtime startup and add a test asserting the form params are sent.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| go/core/pkg/env/kagent.go | Registers env vars for token-exchange resource/audience. |
| go/adk/pkg/sts/plugin.go | Adds WithExchangeTarget option and passes resource/audience into STS exchange calls. |
| go/adk/pkg/sts/plugin_test.go | Adds a test verifying resource/audience are present/absent on the exchange request. |
| go/adk/pkg/runner/adapter.go | Reads env vars and configures the STS plugin with WithExchangeTarget. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks — addressed in 7dccee6:
|
| resource any // RFC 8707 resource indicator passed to the STS exchange | ||
| audience any // RFC 8693 audience passed to the STS exchange |
There was a problem hiding this comment.
Why are these any, they can be string or []string?
There was a problem hiding this comment.
I updated them to []string to be spec compliant :)
| // RFC 8707 resource / RFC 8693 audience scope the exchanged token to a | ||
| // backend. Empty values are omitted by WithExchangeTarget. | ||
| resource := strings.TrimSpace(os.Getenv("KAGENT_TOKEN_RESOURCE")) | ||
| audience := strings.TrimSpace(os.Getenv("KAGENT_TOKEN_AUDIENCE")) |
There was a problem hiding this comment.
Frankly I don't really like adding more env vars if we don't have to, they're hard to track. Can we add a token exchange section to our adk config file instead? This is for the Python impl as well
There was a problem hiding this comment.
Thanks for the review. I'll see what I can do :)
|
@EItanya thanks for the review. Addressed the two smaller points and pushed:
On moving these into the adk config file instead of env vars: happy to. Before I wire it, one scoping question so I size the PRs right. My plan is a |
The token-propagation plugin hardcoded nil resource/audience on every STS token exchange, so issued tokens could not be scoped to a target backend. Read KAGENT_TOKEN_RESOURCE / KAGENT_TOKEN_AUDIENCE and pass them through to the exchange via a WithExchangeTarget option, omitting unset values. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Address review feedback on the resource/audience plumbing: - WithExchangeTarget takes string instead of any, so callers cannot pass a type the STS client silently drops; empty values are omitted. - The resource/audience test captures the token-exchange form over a channel and waits with a timeout, removing the data race and the unreliable t.Fatalf from the server handler goroutine. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Replace the WithExchangeTarget functional option with plain resource and audience string parameters on NewTokenPropagationPlugin, and type the plugin fields as string instead of any. Empty values are converted to nil at the exchange call site so unset targets are omitted from the request. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Align the Go env var names with the Python runtime: KAGENT_TOKEN_RESOURCE/ AUDIENCE become KAGENT_STS_RESOURCE/AUDIENCE so the names make clear they belong to the STS token exchange. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
971b8a8 to
3d00a01
Compare
RFC 8707 resource and RFC 8693 audience are both repeatable, so model them as []string on the STS request instead of any. buildFormData adds one form value per entry and an empty slice sends nothing, so the omitEmpty helper and the type switch are gone. The runtime reads comma-separated KAGENT_STS_RESOURCE / KAGENT_STS_AUDIENCE into the slices. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
f9774a9 to
63f6c1e
Compare
What
The Go ADK token-propagation plugin (
go/adk/pkg/sts) always passednilresource and
nilaudience to the STS token exchange, so an issued tokencould not be scoped to a specific backend. This wires two optional inputs
through to the exchange:
KAGENT_STS_RESOURCE: RFC 8707 resource indicator (the target backend).KAGENT_STS_AUDIENCE: RFC 8693 audience, for STS servers that key on it.Both are read at agent-runtime startup and passed as
resource/audienceparameters to
NewTokenPropagationPlugin. Empty values are omitted from therequest, so behaviour is unchanged when neither is set.
Why
Without a resource/audience, the STS returns a token whose audience is not the
MCP backend, so audience-validating backends reject it. RFC 8707 is the
standard way to scope an exchanged token to its intended resource. The STS
client already serialized
resource/audience; only the call site and theconfiguration plumbing were missing.
Notes
resource/audiencesent.go/core/pkg/envfor discoverability. Movingthis into the adk config file (with a first-class
AgentCRD field) is underdiscussion; see the thread below.