For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Workload identity tokens (WIMSE)
Use WIMSE Workload Identity Tokens with agentgateway to authenticate service-to-service and egress traffic in your ambient mesh.
Warning
WIMSE/WIT support is in the alpha state. Alpha features are likely to change, are not fully tested, and are not supported for production. For more information, see Solo feature maturity.
WIMSE (Workload Identity in Multi-System Environments) is an IETF working group that defines standards for carrying workload identity across system boundaries. The core problem it addresses is that mTLS-based identity (SPIFFE/X.509) is scoped to a single connection. When a workload makes an outbound HTTP call to an external service or crosses a trust boundary, the originating workload’s identity is not automatically carried with the request. WIMSE defines the Workload Identity Token (WIT), a structured token format that encodes workload identity claims so they can travel as an HTTP header (Workload-Identity-Token) alongside the request, independently of the underlying transport security.
When Solo Enterprise for agentgateway is deployed as a waypoint, it can extract workload identity claims from the mTLS peer certificate SAN, expose those claims in CEL policy expressions, and forward or exchange the WIT for outbound requests to external services.
This feature requires Solo Enterprise for agentgateway as a waypoint proxy. For setup, see Install agentgateway as a waypoint.
How it works
The trust chain involves three components working together:
- Istiod embeds workload identity claims in the certificate SAN when it mints workload certificates.
- Ztunnel reads those SAN claims for L4 policy and forwards the WIT header on outbound HBONE connections.
- Agentgateway extracts the claims at L7 from the peer TLS certificate SAN and exposes them in CEL policy expressions.
Inbound policy with workload claims
When agentgateway receives a request, it extracts workload identity claims from the peer certificate and makes them available to EnterpriseAgentgatewayPolicy expressions as CEL attributes:
| CEL attribute | Description |
|---|---|
source.identity.trustDomain | Istio trust domain of the source workload, extracted from the SPIFFE URI in the peer mTLS certificate SAN. This is the value configured in meshConfig.trustDomain (for example, cluster.local by default, or cluster1 in a multicluster setup). |
source.identity.namespace | Namespace of the source workload. |
source.identity.serviceAccount | Service account of the source workload. |
source.claims['istio.io.trust_domain'] | Workload trust domain, sourced from the extended workload claims that istiod embeds in the mTLS certificate. Carries the same value as source.identity.trustDomain, but uses the source.claims map key format that is portable with ztunnel L4 AuthorizationPolicy expressions. Requires workloadClaims.enabled: true in EnterpriseAgentgatewayParameters. |
source.claims['istio.io.workload.name'] | Workload name. |
source.claims['istio.io.workload.namespace'] | Workload namespace. |
source.claims['istio.io.workload.pod'] | Workload pod name (absent if empty). |
source.claims['solo.io.security-claims.<key>'] | Custom security claim sourced from a pod annotation prefixed solo.io.security-claims/. For example, the annotation solo.io.security-claims/jurisdiction: eu is accessible as source.claims['solo.io.security-claims.jurisdiction']. |
These attributes are available in EnterpriseAgentgatewayPolicy expressions targeted at a Gateway, HTTPRoute, or Service.
Enable workload claims on the waypoint proxy
Note
The source.claims attributes are populated from identity claims that istiod embeds in each workload’s mTLS certificate. To populate these attributes, you must enable workload claims on both ztunnel and the agentgateway waypoint proxy.
- Ztunnel: Set
ENABLE_WORKLOAD_CLAIMS=truein your ztunnel Helm values. For setup instructions and ztunnel-level enforcement of the same claims, see CEL and workload claims authorization for L4 traffic. - Agentgateway waypoint proxy: Enable workload claims on the waypoint proxy by using
EnterpriseAgentgatewayParameters. Without this reference,source.claimsis never populated, and all claims-based authorization expressions evaluate to false.
To enable workload claims on the agentgateway waypoint proxy, create an EnterpriseAgentgatewayParameters resource and reference it from your waypoint Gateway.
Create the
EnterpriseAgentgatewayParametersresource.apiVersion: enterpriseagentgateway.solo.io/v1alpha1 kind: EnterpriseAgentgatewayParameters metadata: name: waypoint-params namespace: ${NAMESPACE} spec: workloadClaims: enabled: trueReference the parameters from your waypoint
Gateway.spec: infrastructure: parametersRef: group: enterpriseagentgateway.solo.io kind: EnterpriseAgentgatewayParameters name: waypoint-params
The following EnterpriseAgentgatewayPolicy example combines workload claims with JWT validation to allow requests only when the source workload carries the eu jurisdiction claim and the end-user JWT subject ends with @example.com.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: upstream-authz
namespace: ${NAMESPACE}
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: <gateway_name>
traffic:
authorization:
action: Allow
policy:
matchExpressions:
- 'jwt.sub.endsWith("@example.com") && source.claims["solo.io.security-claims.jurisdiction"] == "eu"'Workload identity propagation
To carry workload identity claims on outbound requests, configure backend.workloadIdentity in an EnterpriseAgentgatewayPolicy. Target the policy at a Service when you don’t need routing rules, or at an HTTPRoute when you do. These modes control the Workload-Identity-Token HTTP header sent to the upstream service. The mTLS identity of the outbound HBONE connection always uses agentgateway’s own workload certificate and is not affected by this setting.
Note
workloadIdentity is mutually exclusive with tokenExchange and auth on the same backend block. This means that you cannot combine workload identity forwarding with token exchange flows or credential injection that generate a new token on behalf of the workload. To preserve the source workload’s identity in the outbound request, agentgateway forwards the original WIT rather than creating a new one.
SourceDelegation
Forwards the inbound Workload-Identity-Token from the source workload to the upstream service. Use this when the upstream service needs to verify the source workload’s identity independently, for example when calling an in-mesh service or an external API that accepts WIMSE tokens.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: forward-wit
namespace: ${NAMESPACE}
spec:
targetRefs:
- kind: Service
name: <svc_name>
group: ""
backend:
workloadIdentity:
mode: SourceDelegationSelfIdentification
Uses agentgateway’s own WIT as the outbound identity rather than delegating the source workload’s identity. Use this when the upstream should authorize the gateway as the caller, not the original source workload. If agentgateway has no minted WIT available, it logs a warning and sends no Workload-Identity-Token header on that request.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: self-identification
namespace: ${NAMESPACE}
spec:
targetRefs:
- kind: Service
name: <svc_name>
group: ""
backend:
workloadIdentity:
mode: SelfIdentificationWorkload Proof Tokens (WPT)
A WIT states who a workload is, but it does not prove that the sender holds the key that the token was issued to. A workload that obtains a copy of a valid WIT can replay it. The WIMSE Workload Proof Token (WPT) closes that gap. A WPT is a short-lived JSON Web Signature that proves possession of the key bound to a WIT for one request, and it carries no identity claims of its own. Senders mint a WPT in the Workload-Proof-Token header, and receivers validate it.
A WPT binds to the tokens that travel with it by hash, so a stolen token cannot be paired with a different proof:
| Claim | Binds |
|---|---|
wth | SHA-256 of the sender’s own WIT, from the Workload-Identity-Token header. |
aud | https:// plus the canonical authority of the destination for this hop, with no path, query, or fragment. |
oth | A map of header name to SHA-256 hash for the other tokens that the request carries, such as the originating workload’s WIT in X-Original-Workload-Identity-Token and the identity chain in X-Forwarded-Workload-Identity. |
exp and iat | A short validity window. |
jti | A value that is unique per WPT, checked against a replay cache on each proxy instance. |
A gateway does not hold the source workload’s private key, so it signs the proof with its own key under its own WIT. A WPT from a gateway therefore asserts that the gateway received the identity over authenticated mTLS and is forwarding it within a bounded time window. Proofs are single-hop, and each gateway on the path mints a new one.
Note
Configure enforcement before enabling emission. Start with Permissive mode to identify senders that do not yet emit proofs without failing their traffic, then enable emission on senders, and tighten enforcement to RequireProof or PeerBound once all senders emit proofs.
Enforce proofs on received requests
To validate proofs on the receive side, set traffic.entWptEnforcement in an EnterpriseAgentgatewayPolicy. Attach the policy to a Gateway or ListenerSet to enforce at that listener, or to a Service or ServiceEntry to enforce at the Service’s waypoint. Enforcement runs with the other authentication policies and before authorization, so an authorization expression sees the verified identities.
Important
The traffic.entWptEnforcement field accepts only a Gateway, ListenerSet, Service, or ServiceEntry target. A policy that targets an HTTPRoute or a GRPCRoute is rejected at admission, because enforcement has to run before routing can rewrite the authority that the proof audience is checked against.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: wpt-peer-bound
namespace: ${NAMESPACE}
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: <gateway_name>
traffic:
entWptEnforcement:
mode: PeerBoundThree modes trade strictness against how much of the mesh already emits proofs.
| Mode | Behavior | Use for |
|---|---|---|
Permissive | Validates a WPT when the request carries one, and rejects an invalid proof. A request that carries no proof is allowed and counted in metrics. | Staged rollout. Turn this on first to find the callers that do not emit proofs yet, without failing their traffic. |
RequireProof | Requires a valid WPT. A missing or invalid proof rejects the request. | A hop that sits downstream of a gateway that already emits proofs. |
PeerBound | Requires a valid WPT when the request carries one. A request with no proof is allowed only when its WIT is bound to the authenticated mTLS peer, which starts the proof chain at this hop. | The edge that workloads connect to directly, where the first hop has no upstream proof to forward. |
Full validation requires the proof to arrive over authenticated mesh transport (HBONE) alongside the WIT that it binds. The proxy checks the signature, the typ header, wth, aud, exp, jti, and oth; confirms that the bound WIT belongs to the mTLS peer; and verifies the x5c chain of any forwarded original WIT.
Rejections use two status codes, which distinguish a caller that presented nothing from a caller that presented something invalid:
| Response | Cause |
|---|---|
401 Unauthorized | The mode requires a proof and the request carried none. |
403 Forbidden | The request carried a proof that failed validation. |
To disable enforcement, omit the entWptEnforcement block or remove the policy.
Override the accepted audience
By default the enforcement point derives the audience that it accepts from the policy attachment, which is a Service hostname or a Gateway listener hostname. On the supported mesh paths that derived audience already matches what senders mint, so most policies need no audiences list.
Set audiences only when your topology decouples the two, such as a non-empty HBONE listener hostname, a BackendTLS SNI override, or an external peer-bound WPT client that targets a concrete virtual host. The list holds up to 64 host patterns that are matched against the authority in the received aud claim, and it replaces the derived audience rather than adding to it.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: wpt-audiences
namespace: ${NAMESPACE}
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: <gateway_name>
traffic:
entWptEnforcement:
mode: PeerBound
audiences:
- ingress-gw.gateway-system.svc.cluster.local
- "*.svc.cluster.local"Authorize on token-verified identity
WPT enforcement stashes the identities that it verified, and CEL policy expressions read them under workloadIdentity. These attributes differ from source.identity and source.claims, which come from the mTLS peer certificate and the transport alone. A workloadIdentity attribute is present only where WPT enforcement validated a token for that request.
| CEL attribute | Description |
|---|---|
workloadIdentity.sender.identity | SPIFFE identity of the previous hop, taken from the WIT sub. This is the workload whose WIT the proof was signed under, verified to match the mTLS peer. |
workloadIdentity.sender.trustDomain | Trust domain of the previous hop, attested by the certificate chain of its WIT. |
workloadIdentity.sender.claims | Claim set of the verified WIT, such as workloadIdentity.sender.claims['istio.io']['workload']. |
workloadIdentity.original.identity | SPIFFE identity of the workload that the request started from, whose WIT was forwarded hop to hop. Absent when the sender conveyed none, such as under SelfIdentification. |
workloadIdentity.original.trustDomain | Trust domain of the originating workload. |
workloadIdentity.original.claims | Claim set of the originating workload’s verified WIT. |
workloadIdentity.chain.origin | The sub of the originating workload. |
workloadIdentity.chain.hops | The gateways that the request passed through, in the wire order of the X-Forwarded-Workload-Identity header: oldest first, with the immediate sender last. Only the endpoints of the chain are proven, because the sender’s proof attests the intermediate hops. |
The following policy allows a request only when the previous hop is in the local trust domain and the request originated from a named workload.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: authorize-on-verified-identity
namespace: ${NAMESPACE}
spec:
targetRefs:
- kind: Service
name: <svc_name>
group: ""
traffic:
authorization:
action: Allow
policy:
matchExpressions:
- 'workloadIdentity.sender.trustDomain == "cluster.local"'
- 'workloadIdentity.chain.origin.endsWith("/ns/frontend/sa/web")'Monitor WPT enforcement
Two Prometheus counters report what enforcement did. Both are unstable metrics, so their names and labels can change between releases.
| Metric | Reports |
|---|---|
agentgateway_wpt_permissive_missing_total | Requests allowed without a proof under Permissive mode. Watch this counter during a migration, and move a hop to RequireProof when it reaches zero. |
agentgateway_wpt_rejection_total | Requests rejected by enforcement, with a reason label that names the failure. |
The reason label separates a configuration problem from an attack:
| Reason group | Label values |
|---|---|
| No proof, or the wrong transport | Required, UntrustedTransport, MissingWit |
| Identity verification failed | WitRejected, PeerBindingFailed, OriginalWitRejected, ChainRejected |
| Proof verification failed | Malformed, SignatureInvalid, WthMismatch, AudienceMismatch, Expired, LifetimeTooLong, OthViolation, AthViolation, TthViolation |
| Replay protection | Replayed, ReplayCacheFull |
| Trust roots unavailable | TrustRootsUnavailable |
A rejection also logs a warning that names the proxy’s reason, so the proxy logs are the place to confirm which token in the request failed.
Emit a proof
To mint a WPT alongside the WIT, set emitProof on the backend.workloadIdentity block that already selects the propagation mode. Emission works with both SourceDelegation and SelfIdentification.
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: forward-wit-with-proof
namespace: ${NAMESPACE}
spec:
targetRefs:
- kind: Service
name: <svc_name>
group: ""
backend:
workloadIdentity:
mode: SourceDelegation
emitProof: true
proofLifetime: 60s
bindAccessToken: true
bindTransactionToken: true| Setting | Description |
|---|---|
emitProof | Mints a WPT for each request and proves possession of the gateway’s key. Defaults to false. When the upstream is off mesh, the proxy suppresses proof emission and sends no WIT at all. To present a plain WIT to an off-mesh upstream, set mode and leave emitProof unset. |
proofLifetime | Validity window of each minted WPT. Defaults to 60s. Receivers cap the accepted lifetime at 2 minutes, and the proxy clamps a larger value down to 120s rather than failing, so a long lifetime is silently shortened instead of rejected. |
bindAccessToken | Binds the request’s OAuth access token, from the Authorization: Bearer header, into the WPT ath claim as described in RFC 9449. A receiver can then verify that the proof and the access token were presented together. The proxy hashes the token as sent, which is after any token exchange that this hop performs. Defaults to false. |
bindTransactionToken | Binds the request’s transaction token, from the Txn-Token header, into the WPT tth claim. Defaults to false. |
Under SourceDelegation with emitProof: true, the gateway presents its own WIT in Workload-Identity-Token and moves the inbound peer-bound WIT to X-Original-Workload-Identity-Token. A receiver that reads the source workload’s identity from the header must therefore read the CEL attributes that WPT enforcement populates rather than parse the header itself.
JWT propagation for end-user identity
WITs carry workload identity and identify the calling service, not an end user or external application. When external callers present an OIDC or OAuth2 bearer JWT (for example, from Keycloak, Entra ID, or a cloud identity provider), that is a separate identity layer that requires different handling. For this identity layer, you can add JWT validation in addition to WIT.
When agentgateway is deployed as a waypoint, it can validate inbound JWTs and make claims from the validated token available in EnterpriseAgentgatewayPolicy CEL expressions, alongside the workload identity claims in the claims map. This lets you combine both layers in a single policy: enforce that the source workload is a trusted service account (WIT) and that the end-user token carries the expected role or scope (JWT).
JWT passthrough: By default, agentgateway forwards the inbound Authorization header to the upstream service. No additional policy is needed if you only want to pass the token through for the upstream to validate independently.
JWT validation and claim-based policy: To validate JWTs at the waypoint and use their claims in authorization decisions, configure JWT validation in agentgateway with a JWKS endpoint. For configuration steps, see the agentgateway JWT authentication documentation.
JWT token exchange: To exchange an inbound JWT for a different credential before forwarding (for example, to call a downstream service that requires a different issuer or token format), configure backend.tokenExchange in an EnterpriseAgentgatewayPolicy. The inbound JWT can serve as the subject_token passed to the STS.
Trust domain verification for WIT x5c chains
When VALIDATE_SPIFFE_TRUST_DOMAIN_NAMES=STRICT is set on agentgateway and a request carries a WIT, agentgateway verifies the x5c certificate chain that is embedded in the WIT in addition to the mTLS certificate chain. During the verification, agentgateway walks the x5c chain from the signing certificate up to the configured root and rejects the request if no ancestor certificate carries a SAN that matches the SPIFFE trust domain that is declared in the WIT. The proxy uses the same SAN matching rules that are used during the mTLS certificate chain verification. For more information, see SPIFFE trust domain chain verification.
Requests that do not carry a WIT are not affected by this check.