TT Lab
Get started
Learn Learning paths Courses

Kubernetes Operations

Working With Token Lifetime and Audience

Continue in TT Lab

Goal

You check for yourself how a Pod's identity is issued and what lifetime and audience it has, and apply the setting that does not give a token at all when it is not needed.

Why it matters

When a container is breached, the first thing an attacker looks for is the token at /var/run/secrets/kubernetes.io/serviceaccount. So this module's key questions are two — does this Pod really need a token, and when does that token expire. The approach before 1.24 had bad answers to both questions. Creating an account automatically produced a token with no expiry, and Pods had it mounted regardless of whether they needed it. Now the token a Pod receives has a lifetime and is renewed by the kubelet, and you can even turn it off entirely with automountServiceAccountToken: false. On top of that, with audience, the token also carries "where it will be used," preventing a token issued to give to Vault from being reused elsewhere. This structure leads directly to cloud workload identity and becomes a design that eliminates static keys. Finally, do not forget: creating an identity does not create permissions. Permissions are still decided by RBAC.

Steps

  1. Create the namespace sa-lab and in it create the ServiceAccount payments. This account must not have an automatically created Secret attached. And in /root/ops/sa/out/sa-note.txt, write in one line why secrets are not created automatically these days (it must include the background of the change to short-lived tokens after 1.24, and expressions such as expiry and lifetime).
  2. In sa-lab, create the Pod payments-api with spec.serviceAccountName set to payments. With default settings, a projected volume holding the service account token is attached automatically.
  3. Create the ServiceAccount locked with automountServiceAccountToken: false. Then create the Pod hardened with spec.serviceAccountName as locked and spec.automountServiceAccountToken as false. This Pod must have no token volume attached at all.
  4. Issue a token for the payments account with a lifetime of 1 hour (3600 seconds) and the audience vault, and save the JSON decoded from the payload of that JWT to /root/ops/sa/out/token.json. This JSON must have sub equal to system:serviceaccount:sa-lab:payments, a non-empty aud, a difference between exp and iat of about 3600 seconds, and a kubernetes.io claim.
  5. In sa-lab, create the Pod projected-demo. Set spec.serviceAccountName to payments, and put a serviceAccountToken source in a projected volume, specified as audience: vault, expirationSeconds: 3600, and path: vault-token. The first container must mount this volume at the path /var/run/secrets/vault.
  6. In sa-lab, create the Secret payments-legacy-token. The type is kubernetes.io/service-account-token, and the annotation kubernetes.io/service-account.name is payments. And in /root/ops/sa/out/legacy-note.txt, write in at least 60 bytes, about two sentences, why this approach is not recommended (it must include that there is no expiry and the burden of revocation and rotation).
  7. Give the payments account only minimal permissions. In sa-lab, it must be able to get and list ConfigMaps, and must not be able to read Secrets or delete ConfigMaps. Connect the permissions with a RoleBinding whose subject is payments.
  8. Create /root/ops/sa/out/sa-audit.json. pods is an array and must exactly match the actual number of Pods in sa-lab. Each element has three fields, name, service_account, and automount. For payments-api, service_account must be payments; for hardened, automount must be false; and there must be no Pod whose service_account is default. Finally, write 2 or more improvement recommendations in the recommendations array.

Reference

Create a dedicated service account

Create the namespace sa-lab and in it create the ServiceAccount payments. This account must not have an automatically created Secret attached. And in /root/ops/sa/out/sa-note.txt, write in one line why secrets are not created automatically these days (it must include the background of the change to short-lived tokens after 1.24, and expressions such as expiry and lifetime).

In current versions, creating an account does not bring a secret along. Sum up in one line why it changed that way.

Specify a dedicated account for a Pod

In sa-lab, create the Pod payments-api with spec.serviceAccountName set to payments. With default settings, a projected volume holding the service account token is attached automatically.

Check the name of the field that specifies the account in the Pod spec. Once you specify it, the token volume is attached automatically.

Turn off the automatic token mount

Create the ServiceAccount locked with automountServiceAccountToken: false. Then create the Pod hardened with spec.serviceAccountName as locked and spec.automountServiceAccountToken as false. This Pod must have no token volume attached at all.

You can use the same field on both the Pod and the account. If you turned it off, no token may remain in the volume list.

Issue a short-lived token and take apart the payload

Issue a token for the payments account with a lifetime of 1 hour (3600 seconds) and the audience vault, and save the JSON decoded from the payload of that JWT to /root/ops/sa/out/token.json. This JSON must have sub equal to system:serviceaccount:sa-lab:payments, a non-empty aud, a difference between exp and iat of about 3600 seconds, and a kubernetes.io claim.

A JWT is three pieces separated by dots, and the middle one is the payload. It is base64url, so you have to fix the padding for it to decode. Check the lifetime from the difference between the two timestamps.

Use a projected token with a specified audience

In sa-lab, create the Pod projected-demo. Set spec.serviceAccountName to payments, and put a serviceAccountToken source in a projected volume, specified as audience: vault, expirationSeconds: 3600, and path: vault-token. The first container must mount this volume at the path /var/run/secrets/vault.

If you stamp an audience into a token, it is valid only at that audience. Match the file name and the mount path exactly.

Create a legacy long-lived token Secret and sort out the risk

In sa-lab, create the Secret payments-legacy-token. The type is kubernetes.io/service-account-token, and the annotation kubernetes.io/service-account.name is payments. And in /root/ops/sa/out/legacy-note.txt, write in at least 60 bytes, about two sentences, why this approach is not recommended (it must include that there is no expiry and the burden of revocation and rotation).

The controller fills in the token only if the specific type and annotation are present. The real problem with this approach is not convenience but that there is no expiry.

Give the account only minimal permissions

Give the payments account only minimal permissions. In sa-lab, it must be able to get and list ConfigMaps, and must not be able to read Secrets or delete ConfigMaps. Connect the permissions with a RoleBinding whose subject is payments.

Creating an account and granting permissions are separate. Open only what is needed and verify by checking that the rest is blocked.

Audit how accounts are used

Create /root/ops/sa/out/sa-audit.json. pods is an array and must exactly match the actual number of Pods in sa-lab. Each element has three fields, name, service_account, and automount. For payments-api, service_account must be payments; for hardened, automount must be false; and there must be no Pod whose service_account is default. Finally, write 2 or more improvement recommendations in the recommendations array.

The Pod count must exactly match the real cluster. There must not be even one Pod that uses the default account.