KubernetesLIX · kubectl authkubectl auth
kubectl create token — issuing and caching tokens
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
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
- 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
-
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". -
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:
- Long duration. A token issued with
--duration=24his a 24-hour credential. The fix is to use the minimum duration that the workload needs. - No audience. A token with the default audience
works against every consumer. The fix is to set
--audienceto the consumer’s URL. - No bound object. A token that is not bound can
be exfiltrated and used from any context. The fix
is
--bound-object-kind=Podfor in-cluster workloads.
Production failure modes
- 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.
- Token issued with wrong audience. A CI token
issued with
--audience=vaultis rejected by the API server. The fix is to verify the audience matches the consumer. - Token store in plaintext. A token cached in a
file with mode 0644 is world-readable. The fix is
chmod 600. - Bound object name doesn’t match. A token bound
to
Pod/api-7f8b9cis 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
Q1. What is the maximum duration for a bound ServiceAccount token by default?
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.
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`.
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.