Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for HashiCorp Vault By James Joyner IV · · 9 min read Last reviewed Jul 2026

Vault Error: 'cannot create resource tokenreviews' and 'service account name not authorized' on Kubernetes Auth

Quick answer

Fix Vault Kubernetes auth login failures: grant system:auth-delegator to the token reviewer, fix kubernetes_host and CA cert, bound service account names, audiences, and issuer validation.

  • #vault
  • #secrets
  • #security-hardening
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this HashiCorp Vault error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Exact Error Message

$ vault write auth/kubernetes/login role=app jwt="$SA_TOKEN"
Error writing data to auth/kubernetes/login: Error making API request.

URL: PUT https://vault.example.com:8200/v1/auth/kubernetes/login
Code: 500. Errors:

* error validating token: failed to review token: tokenreviews.authentication.k8s.io
is forbidden: User "system:serviceaccount:vault:vault" cannot create resource
"tokenreviews" in API group "authentication.k8s.io" at the cluster scope

A different misconfiguration produces a 400 instead:

Code: 400. Errors:

* service account name not authorized

What It Means

Vault’s Kubernetes auth method does not verify a service account token on its own. It hands the JWT to the Kubernetes API server’s TokenReview endpoint (authentication.k8s.io/v1, POST /apis/authentication.k8s.io/v1/tokenreviews) and asks the cluster whether the token is valid and which service account it belongs to. The first error is Kubernetes RBAC refusing that TokenReview call: the identity Vault used — either the token_reviewer_jwt configured on the mount or, when that field is empty, the login JWT itself — has no permission to create tokenreviews. Vault reports the rejection as a 500 because the review request failed rather than returned “not authenticated.”

The second error is a different stage entirely. The TokenReview succeeded, Kubernetes returned a valid identity such as system:serviceaccount:payments:api, and Vault then compared that identity against the role’s bound_service_account_names and bound_service_account_namespaces. service account name not authorized means the name did not match; the namespace equivalent reports namespace not authorized. So a 500 is an infrastructure or RBAC problem, while a 400 is a policy-binding problem on the Vault role.

Common Causes

  • The reviewer service account has no ClusterRoleBinding to the built-in system:auth-delegator ClusterRole.
  • token_reviewer_jwt is unset (or expired) so Vault attempts the review using the caller’s own token, which lacks TokenReview rights.
  • kubernetes_host points at an address the Vault process cannot reach, or kubernetes_ca_cert does not match the API server’s serving certificate.
  • The role’s bound_service_account_names / bound_service_account_namespaces do not include the workload actually logging in.
  • The projected service account token was issued for a different audience than the role’s audience field expects.
  • The token’s iss claim does not match what Vault expects on a managed cluster (EKS, GKE, AKS) that uses an external OIDC issuer URL.

Diagnostic Commands

Read the mount configuration and confirm the host, CA, and issuer settings:

vault read auth/kubernetes/config

Inspect the role that is failing, paying attention to the bound fields and audience:

vault read auth/kubernetes/role/app

From inside a pod using the workload’s service account, read the projected token and try the login by hand:

SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
vault write auth/kubernetes/login role=app jwt="$SA_TOKEN"

Decode the token’s claims to see the issuer, audience, namespace, and expiry:

echo "$SA_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

Check whether the reviewer service account is actually allowed to create TokenReviews:

kubectl auth can-i create tokenreviews \
  --as=system:serviceaccount:vault:vault

Confirm Vault can reach the API server from wherever it runs:

kubectl exec -n vault vault-0 -- \
  wget -qO- --no-check-certificate https://kubernetes.default.svc/version

Watch the Vault audit or server log while a login is attempted:

kubectl logs -n vault vault-0 --tail=50 | grep -i kubernetes

Step-by-Step Resolution

  1. Create the reviewer service account and bind it to system:auth-delegator, the ClusterRole that grants exactly the TokenReview and SubjectAccessReview permissions this flow needs:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault
  namespace: vault
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: vault-tokenreview-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
  - kind: ServiceAccount
    name: vault
    namespace: vault
  1. Configure the auth mount. When Vault runs inside the cluster it can use the pod’s own projected token and CA bundle:
vault auth enable kubernetes

vault write auth/kubernetes/config \
  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token

Recent Vault versions can omit token_reviewer_jwt when running in-cluster and will re-read the pod’s projected token from disk on each review, which avoids the expiry problem described in step 3. If you set the field explicitly, you are pinning a static string.

  1. For an external Vault, mint a long-lived reviewer token deliberately and point at the API server’s externally reachable address. Create a bound secret rather than relying on a short projected token that will expire:
apiVersion: v1
kind: Secret
metadata:
  name: vault-reviewer-token
  namespace: vault
  annotations:
    kubernetes.io/service-account.name: vault
type: kubernetes.io/service-account-token
REVIEWER_JWT=$(kubectl get secret vault-reviewer-token -n vault \
  -o go-template='{{.data.token | base64decode}}')
K8S_CA=$(kubectl get secret vault-reviewer-token -n vault \
  -o go-template='{{index .data "ca.crt" | base64decode}}')

vault write auth/kubernetes/config \
  kubernetes_host="https://api.cluster.example.com" \
  kubernetes_ca_cert="$K8S_CA" \
  token_reviewer_jwt="$REVIEWER_JWT"
  1. Fix the role bindings so the 400 goes away. The names and namespaces are lists, and * is accepted as a wildcard — use it sparingly, because a wildcard on both fields lets any pod in the cluster assume the role:
vault write auth/kubernetes/role/app \
  bound_service_account_names=api,worker \
  bound_service_account_namespaces=payments \
  token_policies=payments-read \
  token_ttl=1h
  1. Reconcile audiences. Modern clusters issue projected tokens with a specific aud claim, and Vault will reject a token whose audience does not match the role. Either set the role’s audience to what the workload actually requests, or project a token with the audience the role expects:
vault write auth/kubernetes/role/app \
  bound_service_account_names=api \
  bound_service_account_namespaces=payments \
  audience="vault" \
  token_policies=payments-read
      volumes:
        - name: vault-token
          projected:
            sources:
              - serviceAccountToken:
                  path: vault-token
                  audience: vault
                  expirationSeconds: 600
  1. Handle issuer mismatches on managed clusters. If logins fail with a validation error mentioning the issuer, check the token’s iss claim against the cluster’s OIDC issuer and set the mount’s issuer field to match. Leave disable_iss_validation alone unless you understand the tradeoff — it stops Vault checking who issued the token, which weakens the trust chain:
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer

vault write auth/kubernetes/config \
  kubernetes_host="https://api.cluster.example.com" \
  kubernetes_ca_cert="$K8S_CA" \
  token_reviewer_jwt="$REVIEWER_JWT" \
  issuer="https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE"

If login now succeeds but subsequent secret reads fail, the problem has moved from authentication to authorization — see Vault error: “permission denied on path” for reading the resulting token’s policies, and Vault error: “missing client token” if the token never made it into the request at all.

Prevention

  • Keep the system:auth-delegator ClusterRoleBinding in the same manifest or chart that deploys Vault so the two never drift apart.
  • Prefer running Vault in-cluster with no static token_reviewer_jwt, letting it re-read the projected token, rather than pinning a JWT that silently expires.
  • Create one role per workload with tight bound_service_account_names and bound_service_account_namespaces; avoid wildcards on both fields simultaneously.
  • Set an explicit audience on roles and request the matching audience in the pod’s projected token volume.
  • Store kubernetes_ca_cert from the cluster’s own CA bundle rather than trusting system roots, and re-run the config step whenever the API server certificate is rotated.
  • Add a CI smoke test that performs a real vault write auth/kubernetes/login from a throwaway pod after every cluster or Vault upgrade.
  • permission denied on login — the role exists but the policy attached to the resulting token lacks capabilities.
  • x509: certificate signed by unknown authoritykubernetes_ca_cert or the Vault listener CA is wrong, not an RBAC failure.
  • Vault is sealed — the server is up but cannot serve any auth request until unsealed.
  • namespace not authorized — the sibling of the name error, caused by bound_service_account_namespaces.

Frequently Asked Questions

Why is the error a 500 rather than a 401? Because the TokenReview call itself failed. Vault never got an answer about the token’s validity, so it reports an internal error rather than an authentication rejection. A genuine bad token returns a 400 with a validation message instead.

Do I still need token_reviewer_jwt? Only when Vault runs outside the cluster or under a service account that differs from the reviewer. In-cluster Vault on recent versions can leave it empty and use its own projected token, which is preferable because it rotates automatically. See Vault error: “certificate signed by unknown authority” if the connection to the API server fails after you change this.

My login worked yesterday and fails today with no config change. The most common cause is an expired token_reviewer_jwt. Projected tokens are short-lived by design, so a JWT copied out of a pod once will stop working. Use a bound kubernetes.io/service-account-token Secret or let Vault read the token from disk.

Should I set disable_iss_validation? Treat it as a diagnostic, not a fix. Turning it off tells you the failure is issuer-related, but leaving it off permanently removes a check on token provenance. Set the issuer field to the cluster’s real OIDC issuer instead. More Vault troubleshooting is collected in the Vault guides.

Free download · 368-page PDF

Fixed it? Get 500 HashiCorp Vault & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.