IRSA Trust Policy aud Mistakes
The IRSA aud claim is sts.amazonaws.com and does not name the cluster. Wrong condition keys, a second audience on the OIDC provider, and a trust policy that only checks sub.
AssumeRoleWithWebIdentity failed. The token in the pod has "aud": "sts.amazonaws.com" and "sub": "system:serviceaccount:payments:payments-api". The trust policy also says aud. The condition key is the bare string aud, so IAM never compares it to the token. That role is not assumable, and the line that looks like a security control is not one.
IRSA trust policy aud is the :aud condition on sts:AssumeRoleWithWebIdentity. How the projected token is mounted is projected service account tokens. Pod Identity uses a different principal and does not use this claim — EKS Pod Identity vs IRSA. This page is the aud mistakes on the IRSA document.
Issuer setup and the trust shape: IAM roles for service accounts.
The condition that matches
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com",
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:payments:payments-api"
}
}
}
The key prefix is the issuer host with no https://. It is the same string as cluster.identity.oidc.issuer after that prefix is stripped. :aud and :sub are claims on that provider. A key named aud is a different key, and it is not in this request, so StringEquals on it does not match. The API error is AccessDenied / not authorized to perform sts:AssumeRoleWithWebIdentity, which is a trust-policy miss.
InvalidIdentityToken / Incorrect token audience is earlier. STS compares the token’s aud to the OIDC provider’s ClientIDList before the role document runs.
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn "$PROVIDER_ARN" \
--query 'ClientIDList'
That list has to include sts.amazonaws.com for a default IRSA token. Putting :aud in the trust policy does not add it here.
aud does not name the cluster
Every default IRSA token carries aud of sts.amazonaws.com. Cluster A and cluster B both do. The cluster is the id in the issuer:
https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE
A copied trust policy that still points Principal.Federated and the condition prefix at cluster A’s issuer will not match a token whose iss is cluster B. Fixing only the :aud value changes nothing, because the value was already sts.amazonaws.com.
The loose copy is the dangerous one: two Federated principals, :aud set, and :sub of system:serviceaccount:*:* under StringLike. Any service account in either cluster can assume the role. The aud line is identical to a locked-down role, so a review that only checks “aud is sts.amazonaws.com” passes it. :sub is the service account. :aud is the intended consumer of the token. The issuer host is the cluster.
ClientIDList is the first aud check
STS rejects a JWT whose aud is not in the provider ClientIDList, with Incorrect token audience. The in-cluster token mounted for the API server has aud of https://kubernetes.default.svc and the same iss and sub. With a provider that lists only sts.amazonaws.com, that API token cannot assume the role even if the trust policy forgot :aud.
That protection disappears when someone adds a second audience to the provider:
eks.amazonaws.com/audienceset on a service account to a private string, and that string added toClientIDListso the pod can assume one role- a vendor’s audience added to the same provider
https://kubernetes.default.svcadded while debugging “Incorrect token audience”
Every role on that provider whose trust policy checks only :sub now accepts tokens minted for the new audience. StringEquals on :aud is what keeps those roles on sts.amazonaws.com. StringEqualsIfExists is the wrong operator: a token that omits aud makes the condition pass. Use StringEquals.
Do not add https://kubernetes.default.svc to ClientIDList. That is the API-server audience. Combined with a trust policy that has no :aud key, the default projected token becomes a cloud credential.
Custom audience annotation
The webhook reads eks.amazonaws.com/audience on the service account. Omitted, the projected token’s aud is sts.amazonaws.com.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/payments-api
eks.amazonaws.com/audience: sts.amazonaws.com
If you set a different audience, three places have to carry the same string:
- The annotation (what the webhook asks the API server to mint)
- The provider
ClientIDList(what STS will accept at all) - The trust policy
:audvalue (what this role will accept)
Annotation changed, trust policy still sts.amazonaws.com: AccessDenied on the role. Annotation changed, ClientIDList not updated: Incorrect token audience. Annotation left at the default and the trust policy updated to a custom string: AccessDenied again. Decode the file the SDK is actually sending before editing the third place.
cut -d. -f2 < "$AWS_WEB_IDENTITY_TOKEN_FILE" \
| tr '_-' '/+' | base64 -d 2>/dev/null \
| jq '{iss,aud,sub}'
iss must be your cluster’s issuer. aud must be the trust policy value and a member of ClientIDList. sub must be system:serviceaccount:<namespace>:<name> with that namespace and name, not a wildcard you meant to tighten later.
A second statement on the same role that checks only :sub, left in place “for Terraform,” accepts every audience the provider lists. Delete that statement. Pod Identity’s trust (pods.eks.amazonaws.com, sts:AssumeRole, sts:TagSession) is not an aud statement; leaving the IRSA statement beside it keeps the OIDC door open after you add an association.
Checklist
-
:audkey is<issuer-host>:aud, notaud -
:audvalue isStringEqualssts.amazonaws.com, or the custom audience on all three of annotation,ClientIDList, and trust policy -
:subis onesystem:serviceaccount:namespace:name, notStringLike*:* -
Federatedprincipal and the condition prefix are this cluster’s issuer id -
ClientIDListincludessts.amazonaws.comand does not includehttps://kubernetes.default.svc - No second statement on the role allows
AssumeRoleWithWebIdentitywithout:aud - Decoded
AWS_WEB_IDENTITY_TOKEN_FILEmatchesiss,aud, andsubbefore the trust policy is rewritten again
Related: EKS Pod Identity vs IRSA · Projected service account tokens · GitHub Actions OIDC to AWS