feat: Add spec.auth to AgentRuntime for SPIFFE token exchange - #510
feat: Add spec.auth to AgentRuntime for SPIFFE token exchange#510Alan-Cha wants to merge 6 commits into
Conversation
The existing warning 'SPIRE not detected for mTLS-enabled workload' is confusing because: 1. SPIRE may be running (via Helm) but not detected by this check 2. JWT-SVID authentication works fine despite this warning 3. The check only looks for socket volumes in the pod spec This commit clarifies that: - The check is specific to X.509-SVID based mTLS - JWT-SVID authentication is unaffected - The message explains what's actually being checked Fixes rossoctl/rossoctl#2361 Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
…nge configuration
Add authentication configuration to AgentRuntime CRD to support per-agent
SPIFFE-based token exchange with scoped audiences.
New fields:
- spec.auth.mode: Authentication mechanism (federated-jwt, client-secret, disabled)
- spec.auth.outbound: List of routes specifying which destinations require
token exchange and what audiences to request
This enables agents to declare which tools they call and what SPIFFE
audiences are needed, allowing the operator to generate proper AuthBridge
route configuration instead of relying on blanket all-to-all FGAP policies.
Example usage:
```yaml
apiVersion: agent.rossoctl.dev/v1alpha1
kind: AgentRuntime
metadata:
name: weather-service
spec:
type: agent
auth:
mode: federated-jwt
outbound:
- destination:
host: weather-tool-mcp.team1.svc.cluster.local
audiences:
- spiffe://localtest.me/ns/team1/sa/weather-tool
```
Related to #2174 (Remove all provisioned credentials in favor of SPIFFE-based
Keycloak authentication)
Co-Authored-By: Claude Code <noreply@anthropic.com>
Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
….outbound
Implement operator logic to read spec.auth.outbound from AgentRuntime CRs
and inject token-exchange routes into per-agent AuthBridge ConfigMaps. This
enables workloads to declaratively configure which audiences to request when
calling specific destinations via SPIFFE token exchange.
Changes:
- Fetch AgentRuntime CR in InjectAuthBridge webhook handler
- Pass AgentRuntime to ensurePerAgentConfigMap function
- Generate routes config from spec.auth.outbound when mode is federated-jwt
- Add unit tests for route generation (with and without spec.auth)
Routes are injected into pipeline.outbound.plugins[token-exchange].config.routes
with the following structure:
routes:
- destination:
host: "service.namespace.svc.cluster.local"
audiences: ["spiffe://trust-domain/ns/namespace/sa/serviceaccount"]
Relates-To: #2174
Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
- Add tokenExchangePluginName constant for 'token-exchange' string - Remove redundant nil check before len() (len returns 0 for nil slices) Fixes CI linter errors from golangci-lint. Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
Complete guide for using spec.auth field to configure SPIFFE-based authentication and token exchange. Includes: - Overview of authentication modes (federated-jwt, client-secret, disabled) - Route configuration examples (exact host, regex, multiple audiences) - How the operator generates AuthBridge routes - Complete working example with agent and tool - Troubleshooting guide - Best practices Assists-By: Claude Code Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
cwiklik
left a comment
There was a problem hiding this comment.
Adds spec.auth to AgentRuntime and generates AuthBridge token-exchange routes into the Envoy outbound pipeline (#2174). The route-gen is nicely defensive — nil-checked navigation of the untyped pipeline with WARN-and-skip at each level, gated to mode: federated-jwt, and a graceful missing-CR fallback (pre-CRD workloads get no routes, existing behavior preserved). mode is Enum-validated with a client-secret default, audiences is MinItems=1, and both generated CRD copies (charts/ and config/crd/bases/) are byte-identical.
Three hardening notes in the credential-minting path (all non-blocking — exploitability is gated by who can create AgentRuntime CRs, and the worst case is scope-broadening for the author's own audiences, not arbitrary escalation):
RouteMatchallows neither/bothhost+hostRegex; an empty match emitsdestination: {}into the token-exchange config (silent catch-all/no-op). Recommend a CEL XValidation requiring exactly one, plus a defensive skip+WARN.hostRegexis unbounded — bound it (maxLength) given ReDoS + credential-scope-broadening.pluginConfig["routes"]overwrite — confirm spec.auth is the intended source of truth.
15 checks green; E2E pending (maintainer trigger). All commits signed, no .claude/.vscode. LGTM.
Assisted-By: Claude Code
| } | ||
|
|
||
| // RouteMatch defines how to match an outbound destination. | ||
| type RouteMatch struct { |
There was a problem hiding this comment.
suggestion: RouteMatch has both host and hostRegex as +optional with no "exactly one" constraint. An empty RouteMatch is accepted, and route-gen in pod_mutator.go then emits destination: {} into the token-exchange config — a silent catch-all/no-op in a credential-minting path (tokens exchanged for the listed audiences on all outbound calls, or none, depending on AuthBridge's empty-match semantics). Recommend a +kubebuilder:validation:XValidation (CEL) requiring exactly one of host/hostRegex, and defensively skip+WARN on an empty destination in route-gen. A test for the empty/both cases would lock it in.
| // Example: ".*\\.team1\\.svc\\.cluster\\.local" | ||
| // | ||
| // +optional | ||
| HostRegex string `json:"hostRegex,omitempty"` |
There was a problem hiding this comment.
suggestion: hostRegex is an unbounded, arbitrary regex evaluated on the request path. Risks: ReDoS from a pathological pattern, and an over-broad pattern (.*) silently broadening which destinations get token-exchange for the configured audiences. Consider a maxLength bound and documenting that the pattern scopes credential exchange (broad regex = broad credential scope).
| routes = append(routes, route) | ||
| } | ||
|
|
||
| pluginConfig["routes"] = routes |
There was a problem hiding this comment.
nit: pluginConfig["routes"] = routes replaces any pre-existing routes key wholesale — if the namespace template ever ships default outbound routes, they'd be clobbered by the spec.auth-derived set. Assuming spec.auth is intended as the source of truth here, a one-line comment saying so would help future readers.
BREAKING CHANGE: Removed spec.auth.mode field from AgentRuntime CRD The authentication mode (federated-jwt vs client-secret) is configured globally at the namespace level via authBridge.clientAuthType, not per-agent. Having a per-agent mode field was misleading and could create conflicts. Changes: - Removed AuthConfig.Mode field - Updated webhook to inject routes when spec.auth.outbound is present, regardless of mode - Routes are only effective when namespace has federated-jwt enabled - Regenerated CRD manifests - Updated tests to remove mode field - Removed separate documentation (will be added to existing auth guide) The authentication mode is a platform-wide setting that applies to all agents in a namespace. Individual agents configure only their outbound routes via spec.auth.outbound. Assisted-By: Claude Code Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
Summary
Implements issue #2174 by adding spec.auth field to the AgentRuntime CRD and operator logic to generate AuthBridge token-exchange routes from spec.auth.outbound configuration.
Changes
CRD Extension
Operator Logic
Tests
Route Format
Generated routes are injected as:
Backward Compatibility
Testing
Unit tests verify:
Closes #2174
Assisted-By: Claude Code