Secrets of type kubernetes.io/service-account-token
What does ZopNight detect here?
ZopNight has a rule for Secrets of type `kubernetes.io/service-account-token` in EKS, GKE and AKS clusters, but its discovery currently skips every such Secret, so the rule raises no findings yet. These Secrets hold tokens that never expire or rotate, and since v1.24 Kubernetes has stopped creating them automatically.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1754 · RC-1854 · RC-1954 |
| Category | security |
| Severity | medium |
| Metric | none — pure configuration read |
| Threshold | Secret type kubernetes.io/service-account-token |
| Source | ZopNight |
| Permissions used | list secrets · list serviceaccounts |
Where it applies
Static tokens outlive the reason they were created
A service account token Secret stores a signed credential for a ServiceAccount. The service accounts page marks this method as not recommended: these tokens do not expire and do not rotate, and static long-lived credentials carry risk, especially at scale. Anyone who copies the token from a backup, a CI log or a laptop keeps API access for as long as the Secret exists.
Kubernetes moved away from them in stages. From v1.22, pods get a short-lived, automatically rotating token from the TokenRequest API, mounted as a projected volume. From v1.24 the control plane stopped generating a permanent token Secret for each ServiceAccount by default. You can still create these Secrets by hand, which is why they keep turning up.
Finding token Secrets and their owners
kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token \ -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,SA:.metadata.annotations.kubernetes\.io/service-account\.nameThe kubernetes.io/legacy-token-last-used label, where present, records the last date the token
was used, which helps separate live credentials from leftovers.
Matching on the Secret type
The rule reads the type recorded for each Secret and is written to fire when it is exactly
kubernetes.io/service-account-token, whatever the name, age or usage. Today, though, ZopNight’s
discovery skips every Secret of that type when it lists Secrets, hand-made ones included, so the
rule has nothing to match and raises no findings yet. Until that changes, use the kubectl
command above to find these Secrets.
What the check does not cover
Other Secret types, including TLS and registry credentials, are out of scope; Secrets carrying
unusually many keys are covered by
Secret has excessive keys.
Projected tokens mounted into pods are not Secrets and never appear here. The control plane also
cleans some tokens up by itself: since v1.30, when the legacy token cleaner became stable,
auto-generated legacy tokens unused for a year are labelled kubernetes.io/legacy-token-invalid-since and later purged. That cleaner targets
auto-generated tokens, so manually created ones usually remain until you remove them.
Credential risk with no saving
There is no dollar amount. The exposure is a non-expiring API credential whose reach equals every RBAC permission granted to its ServiceAccount.
Replacing the static token
- Identify the consumer from the ServiceAccount annotation, the last-used label and your CI or automation configuration.
- For workloads in the cluster, rely on the projected token Kubernetes mounts automatically.
- For tools outside the cluster, issue a time-bound token with
kubectl create token <serviceaccount> --duration 1h, or call the TokenRequest API from the tool. - Once nothing uses the old credential, run
kubectl delete secret <name> -n <namespace>.