Skip to main content
RunBook Academy

KubernetesLIX · kubectl authkubectl auth

kubectl create token — issuing and caching tokens

Advanced⏱ ~13 minkubectl

What you'll learn

  • Use `kubectl create token` to issue a projected token for any SA
  • Set the audience and duration to scope the token to its consumer
  • Bind the token to a specific object (Pod, Node, Secret) for stronger scoping
  • Operate the token cache and understand the refresh lifecycle

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

kubectl create token is the kubectl front-end for the TokenRequest API. It issues a short-lived, signed JWT for any ServiceAccount in the cluster. The token is projected (no Secret object is created) and scoped by audience. This lesson covers the issuance flags, the caching lifecycle, the use cases, and the security risks.

The basic syntax

kubectl create token <serviceaccount-name>
  [--namespace <namespace>]
  [--audience <audience>]
  [--duration <duration>]
  [--bound-object-kind Pod|Node|Secret]
  [--bound-object-name <name>]
  [--bound-object-uid <uid>]

The output is a JWT to stdout:

TOKEN=$(kubectl create token ci-runner -n ci)
echo "$TOKEN"
# eyJhbGciOiJSUzI1NiIs...

# Verify the token claims
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
{
  "iss": "https://kubernetes.default.svc",
  "sub": "system:serviceaccount:ci:ci-runner",
  "aud": ["https://kubernetes.default.svc"],
  "exp": 1755356400,
  "kubernetes.io": {
    "namespace": "ci",
    "serviceaccount": {
      "name": "ci-runner",
      "uid": "..."
    }
  }
}

Audience scoping

The --audience flag scopes the token to a specific consumer:

# Default audience: the API server
kubectl create token ci-runner -n ci
# aud: ["https://kubernetes.default.svc"]

# Vault audience
kubectl create token vault-issuer -n prod \
  --audience=vault://prod.example.com
# aud: ["vault://prod.example.com"]
# The API server rejects this token; Vault accepts it (if Vault is configured)

# IRSA audience (AWS)
kubectl create token myapp -n prod \
  --audience=sts.amazonaws.com
# aud: ["sts.amazonaws.com"]
# AWS STS accepts this token and exchanges it for IAM credentials

A token with the wrong audience is rejected by every consumer. A token with no audience (--audience="") works against every consumer that trusts the signing key.

Bound objects

A bound token is rejected unless it is presented from the bound object (a Pod, Node, or Secret):

# Bind to a specific Pod
kubectl create token myapp -n prod \
  --bound-object-kind=Pod \
  --bound-object-name=api-7f8b9c \
  --bound-object-uid=...
# The token is only valid when the request comes from the api-7f8b9c Pod

The bound form prevents exfiltration: a token leaked to a different Pod is rejected. This is the strongest form of projected token.

flowchart LR
    A[TokenRequest] --> B{Bound to Pod?}
    B -->|yes| C[Token issued]
    B -->|no| D[Token issued]
    C --> E[Used by api-7f8b9c]
    C -->|exfiltrated| F[Rejected]
    D --> G[Used by anyone with the token]

The token cache

kubectl create token does not cache by default. The caller stores the token in ~/.kube/cache/ if they use the auth-provider flow (e.g., OIDC). For ad-hoc invocations, the token lives in the shell variable for the duration of the script.

# Cache the token in a CI variable
TOKEN=$(kubectl create token ci-runner -n ci --duration=15m)
export K8S_TOKEN="$TOKEN"

# The token is valid for 15 minutes
kubectl --token="$TOKEN" get pods -n ci

A pipeline that issues a token per run has no cache — each run gets a fresh token. A pipeline that reuses a cached token across runs has the same credential-management problem as a long-lived Secret.

Use cases

  1. CI/CD pipelines — the pipeline authenticates with OIDC or a client cert, requests a token for the CI SA, uses the token to apply manifests.
#!/bin/bash
TOKEN=$(kubectl create token ci-runner -n ci --duration=15m)
kubectl --token="$TOKEN" apply -f manifest.yaml
  1. Debugging — an operator needs to verify a SA’s permissions. The operator requests a token for the SA and runs kubectl auth can-i --list --token="$TOKEN".

  2. Third-party integrations — an external system (Vault, ArgoCD, Flux) needs a token to authenticate to the cluster. The integration requests a token via the API and stores it locally (with appropriate scoping).

Security risks

Three risks:

  1. Long duration. A token issued with --duration=24h is a 24-hour credential. The fix is to use the minimum duration that the workload needs.
  2. No audience. A token with the default audience works against every consumer. The fix is to set --audience to the consumer’s URL.
  3. No bound object. A token that is not bound can be exfiltrated and used from any context. The fix is --bound-object-kind=Pod for in-cluster workloads.

Production failure modes

  1. Pipeline caches tokens across runs. The pipeline issues a token once, stores it in a variable, and reuses it across runs. The token may expire during a long run; the fix is to request a new token per run.
  2. Token issued with wrong audience. A CI token issued with --audience=vault is rejected by the API server. The fix is to verify the audience matches the consumer.
  3. Token store in plaintext. A token cached in a file with mode 0644 is world-readable. The fix is chmod 600.
  4. Bound object name doesn’t match. A token bound to Pod/api-7f8b9c is rejected if the Pod is restarted and gets a new name. The fix is to use --bound-object-uid (which is stable across restarts).

Cross-course references

  • The Observability course covers the audit log entries for TokenRequest operations.
  • The Linux course covers the file permissions for the token cache.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the maximum duration for a bound ServiceAccount token by default?

  2. Q2. A token bound to a specific Pod via `--bound-object-kind=Pod` is rejected if the Pod is restarted and gets a new name, even if the same UID is used.

  3. Q3. Your CI pipeline runs `kubectl create token ci-runner --duration=24h` once at the start of the run and stores the token in an environment variable. The pipeline applies manifests over the course of an hour, then runs integration tests for another hour. At the 90-minute mark, the tests fail with `Unauthorized`. Why, and what is the fix?

    The CI pipeline requests a 24-hour token and stores it in `K8S_TOKEN`. The pipeline applies manifests in the first 30 minutes, runs integration tests from 30-90 minutes. At 90 minutes, the token is still within the 24-hour window but the API server rejected it with `Unauthorized`.

  4. Q4. Name three flags for `kubectl create token` and what each one does.

Passing score: 75%. Answers are checked in this browser.

Production discipline

kubectl create token is the right primitive for issuing short-lived, scoped ServiceAccount tokens. A defensible RBAC programme uses the minimum duration, the right audience, and bound objects where possible. The token cache is per-script; a pipeline that reuses a token across runs has the same credential-management problem as a long-lived Secret. The discipline is to issue a new token per run, with the minimum surface that the consumer needs, and to verify the token’s claims before using it.