Skip to content

Conversation

jaireddjawed
Copy link
Contributor

@jaireddjawed jaireddjawed commented Jun 2, 2025

Overview

Currently, Vault's Kubernetes authentication method does not require roles to specify an audience. As a result, an attacker could potentially craft a malicious JWT from a compromised or rogue Kubernetes cluster and use it to authenticate against Vault, especially if the associated cluster does not validate the aud claim.

This pull request introduces a change that requires the audience field to be specified when creating or updating a Kubernetes role. Additionally, authentication attempts will fail if they use a role that lacks a configured audience.

Since this is a breaking change, it will be included in Vault v1.21+. However, another PR will be backported into older Vault versions that logs a warning when a role does not have an audience.
 

Impact on users

  • When a role is created, it is now required to provide an audience.
  • Existing roles will need to be updated to have an audience. Roles can't be updated without providing an audience first. Existing role data is intact and remains unaffected otherwise.
  • An authentication attempt that uses a role that does not have an audience configured will fail with the following error:
    "role does not have an audience defined, please update the role to include an audience".

Manual Testing

Testing was similar to the testing done in PR 301. However, errors are expected instead of warnings.

  • Set up Kind cluster and Vault dev server.
  • Configured Kubernetes authentication and created role.
  • Verified that configuring a role without an audience yields an error.
vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat vault-sa-token)" \
    kubernetes_host="$KUBE_HOST" \
    [email protected]
Success! Data written to: auth/kubernetes/config
jaired.jawed@jaired k8s-demo % vault write auth/kubernetes/role/my-role \
    bound_service_account_names=my-app \
    bound_service_account_namespaces=default \
    policies=my-policy \
    ttl=24h
Error writing data to auth/kubernetes/role/my-role: Error making API request.

URL: PUT http://127.0.0.1:8200/v1/auth/kubernetes/role/my-role
Code: 400. Errors:

* "audience" can not be empty

@jaireddjawed jaireddjawed self-assigned this Jun 2, 2025
@jaireddjawed jaireddjawed requested a review from a team as a code owner June 2, 2025 17:04
@jaireddjawed jaireddjawed changed the title Added Audience Validation Unit Test Prevent authentication into Vault without an audience Jun 16, 2025
@tsaarni
Copy link
Contributor

tsaarni commented Aug 12, 2025

Hi @jaireddjawed 👋

Could this be made optional, so that admins can opt out if they need? Without such an option, upgrade of Vault could cause serious issues for existing users, since reconfiguring roles can be tricky, especially when considering it needs to be coordinated with multiple clients at once, which might be developed by different teams, and they might use different types of tokens.

Here are some scenarios that illustrate the complexity:

1. Clients can use the default audience

Some clients don’t set an audience explicitly and instead use whatever Kubernetes provides by default:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-sa
---
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  serviceAccountName: my-sa
  containers:
  - name: my-container
    image: alpine:3
    command: ["sleep", "9999999"]
EOF

On a default Kind cluster, decoding the token shows:

$ kubectl exec my-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | jq -R 'split(".") | .[1] | @base64d | fromjson'
{
  "aud": [
    "https://kubernetes.default.svc.cluster.local"
  ],
  ...
}

Vault admins or even Kubernetes cluster admins may not know this value. It comes from the kube-apiserver --service-account-issuer or --api-audiences flags and may be set automatically (e.g., https://kubernetes.default.svc.<dns-domain> used by kubeadm by default).

2. Clients can use ServiceAccount token Secrets

Tokens created as kubernetes.io/service-account-token have no aud claim

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: my-sa-token
  annotations:
    kubernetes.io/service-account.name: my-sa
type: kubernetes.io/service-account-token
EOF
$ kubectl get secret my-sa-token -o jsonpath='{.data.token}' | base64 --decode | jq -R 'split(".") | .[1] | @base64d | fromjson'
{
  ... there is no "aud" claim at all
}

These tokens would not be usable at all after Vault upgrade.

3. Clients can use projected volume tokens or token request API with a custom audience

Here, the audience is explicitly set by whoever deploys the workload:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: my-pod-projected
spec:
  serviceAccountName: my-sa
  containers:
  - name: my-container
    image: alpine:3
    command: ["sleep", "9999999"]
    volumeMounts:
    - name: token-volume
      mountPath: /var/run/secrets/kubernetes.io/my-projected-serviceaccount
  volumes:
  - name: token-volume
    projected:
      sources:
      - serviceAccountToken:
          path: token
          audience: bar
EOF

Audience in this case:

$ kubectl exec my-pod-projected -- cat /var/run/secrets/kubernetes.io/my-projected-serviceaccount/token | jq -R 'split(".") | .[1] | @base64d | fromjson'
{
  "aud": [
    "bar"
  ],
  ...
}

While audience validation is the correct approach, it would be great to clarify threat scenario

As a result, an attacker could potentially craft a malicious JWT from a compromised or rogue Kubernetes cluster and use it to authenticate against Vault, especially if the associated cluster does not validate the aud claim.

If an attacker controls the cluster, they can already create tokens with any aud value, like shown in scenario 3 or by calling the token request API directly:

$ kubectl create token my-sa --audience=whatever-i-want | jq -R 'split(".") | .[1] | @base64d | fromjson'
{
  "aud": [
    "whatever-i-want"
  ],
  ...
}

Perhaps the intended risk is that an attacker could steal a token never meant for Vault and still use it if the service account name and namespace match an existing Vault role?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants