Skip to main content

Service Accounts

There are two types of accounts in Kubernetes.

  • User Accounts: These accounts are used by humans.
  • Service Accounts: Used by applications.

In any namespace created, a default service account is created by default. Let's check it out.

kubectl create namespace demo
namespace/demo created

kubectl get serviceaccounts -n demo
NAME SECRETS AGE
default 0 16s

# But nothing prevents us from creating a specific service account.
create sa -n default demo-sa
serviceaccount/demo-sa created

kubectl get serviceaccounts -n demo
NAME SECRETS AGE
default 0 2m20s
demo-sa 0 4s

# To delete
kubectl delete sa -n demo demo-sa
serviceaccount "demo-sa" deleted

Notice that there is no secret linked to this service account yet, which is why we have 0. The secret there is the name of the secret that will store a token used by the application as a bearer token for access to the Kubernetes API.

Before version 1.24, the secret was automatically generated and the token was placed inside the secret.

Let's understand a bit of the history.

Every time a pod is created, the default service account of the namespace is automatically mounted inside the pod as a volumeMount.

kubectl create deployment nginx --image nginx

kubectl describe pods nginx-7854ff8877-8r7hs

Name: nginx-7854ff8877-8r7hs
Namespace: default
Priority: 0
Service Account: default #<<<< WE DIDN'T EVEN PASS THIS
...
Containers:
nginx:
Container ID: containerd://c7c95a9dda456e6da5ad14e17f8d5ab496991d177b1cadbc69ca28614a903a8a
Image: nginx
Image ID: docker.io/library/nginx@sha256:84c52dfd55c467e12ef85cad6a252c0990564f03c4850799bf41dd738738691f
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-c9f7j (ro) #<<<<The kube-api-access-c9f7j resource is not directly accessible for inspection or viewing at the user level. It is created and managed internally by Kubernetes as part of the pod creation and execution process to generate the token automatically.
...
Volumes:
kube-api-access-c9f7j:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
...

Of course, we could use another service account if we want by passing it inside the pod spec.

apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
serviceAccountName: my-service-account #<<<
automountServiceAccountToken: true #<<< THIS IS THE DEFAULT VALUE
status: {}

By default, Kubernetes automounts the service account token, but it is possible to disable it if needed.

What do we have there?

kubectl exec -it nginx-7854ff8877-8r7hs -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token

# Cluster CA
kubectl exec -it nginx-7854ff8877-8r7hs -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIeL8dKSH2rh8wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDAyMDgyMjU3MThaFw0zNDAyMDUyMzAyMThaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDRCy18tHA4HJbSDtOZo2yp4yzyJ7QnAirXImfoqjjjOe0uF8Jo4GjbCb61
GhBiWt3+A4rfJ3yGdx75PuOcnhN6siOR5oOWUHx2zDbJwSDA9pdvXJa0GdU0LTUY
mvSkUOn7DtZtLZ8q/lElIHphnSX4uqQTTL87NPL9ComFTHZAXqR8WjSQTFcYle4b
hiu6coMUq/1BKCTeN84omVqnLxuDdkdjoxnXm+Q3Jj4iQujFJ5NrxFOfCR2g4OTY
x92p1K5krnXS+z26NrzR1Up9k/2fQs129Rsn4+oAPkgs4s1CXfs9mS/5rJhqemRo
IghxaAKP+0hmSWSyKbZgqxDs3dAPAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQaEQhPwBz2wWOm66FEI7HiE+V1JjAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCRkBml9DKD
iX7Qfc/jguqr+K5FRtvraanbl4v6/fP2duGzU6fvRMv0Qs4yKnPSg0QsvXyItHXZ
4P+AgPLTYnzAuK8uqUoCLF3q/DCuH7plWn7HmXGL/JQ0KBIxwgk3pYPCO0Y53ROJ
vxiXPvFgGovtZWYj5RKn9T4F+dir6gQufXctKHel2o0OP8KfzNqz5L2pbXE58vMO
Jk6Fvd82mH1N7SpWBJgW6d3Co4FW+Zp527sjmu+IHzyctLUfK/hSSDhLDC1MWmP4
99DV0sgY1mRXTz7AIzjXyDTy0z0Rdgy9ibPSwDRbVqiZ+v2mfszkPunazohlHv8J
5w63PiNNZBeA
-----END CERTIFICATE-----
# The namespace

kubectl exec -it nginx-7854ff8877-8r7hs -- cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
default

# And the token created automagically by kube-api-access
kubectl exec -it nginx-7854ff8877-8r7hs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzM5Mjc3NDY0LCJpYXQiOjE3MDc3NDE0NjQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueC03ODU0ZmY4ODc3LThyN2hzIiwidWlkIjoiMTk3Y2E2YjctZTEyMC00OTgxLWFmOGEtZTllOTllMmIxZjBiIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiYjRlZDJhMGUtNDBmMC00Yjc0LWEzNzgtZjgwYmJkOTEzNzNiIn0sIndhcm5hZnRlciI6MTcwNzc0NTA3MX0sIm5iZiI6MTcwNzc0MTQ2NCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.KvlOcSuG_W3YqOKUKC9nIEI8ksITGwUoKboubyGcozHRg9v_OycyG-5O6EAOdmYKE5Hd1u-b9Bl8n4NhYDF_ygf8gzu406nEf-BpBgWjogOaGylD0dIopaeC3rK7G47S9nMD1cFDGTz07hygxIQSezUfZd1EkmaSq3nK1g0yk3lcKv9fKZpFHcBsZAOEUMJyQ2cLeE_Rdaf9i4w1XTtWL-5fcbWuOjHq96rBjIejP-myjE971bvCyN3WE39vRUYZMq8aO1XIVt9_bsOd-GaTVXgN1r0LNeCz1ye743J2JYWin2_ic5fkc5vUxeNObgg03WXvUAAkcvOaetSp5yLLIQ

The jwt.io site decodes it visually for us.

alt text

Previously, we didn't have an expiration date and the token for a service account was always the same and valid as long as the service account existed.

All pods in the same namespace that didn't define a service account used the same token. With this modification, all pods can use the same service account, but with different tokens, which improves security and ensures token rotation (every time a pod is recreated, new tokens are generated).

Another situation is that a service account left in the cluster could give access to that entire namespace if it had permission.

That's why a TokenRequestAPI was proposed that would automatically provision tokens with greater security and also solved some scalability issues that existed.

Besides jwt.io, the command below also verifies using jq or the jwt cli.

jq -R 'split(".") | select(length > 0) | .[0],.[1] | @base64d | fromjson' <<< eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzM5Mjc3NDY0LCJpYXQiOjE3MDc3NDE0NjQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueC03ODU0ZmY4ODc3LThyN2hzIiwidWlkIjoiMTk3Y2E2YjctZTEyMC00OTgxLWFmOGEtZTllOTllMmIxZjBiIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiYjRlZDJhMGUtNDBmMC00Yjc0LWEzNzgtZjgwYmJkOTEzNzNiIn0sIndhcm5hZnRlciI6MTcwNzc0NTA3MX0sIm5iZiI6MTcwNzc0MTQ2NCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.KvlOcSuG_W3YqOKUKC9nIEI8ksITGwUoKboubyGcozHRg9v_OycyG-5O6EAOdmYKE5Hd1u-b9Bl8n4NhYDF_ygf8gzu406nEf-BpBgWjogOaGylD0dIopaeC3rK7G47S9nMD1cFDGTz07hygxIQSezUfZd1EkmaSq3nK1g0yk3lcKv9fKZpFHcBsZAOEUMJyQ2cLeE_Rdaf9i4w1XTtWL-5fcbWuOjHq96rBjIejP-myjE971bvCyN3WE39vRUYZMq8aO1XIVt9_bsOd-GaTVXgN1r0LNeCz1ye743J2JYWin2_ic5fkc5vUxeNObgg03WXvUAAkcvOaetSp5yLLIQ

## Using jwt-cli which will verify on jwt.io
npm install -g jwt-cli

jwt eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzM5Mjc3NDY0LCJpYXQiOjE3MDc3NDE0NjQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueC03ODU0ZmY4ODc3LThyN2hzIiwidWlkIjoiMTk3Y2E2YjctZTEyMC00OTgxLWFmOGEtZTllOTllMmIxZjBiIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiYjRlZDJhMGUtNDBmMC00Yjc0LWEzNzgtZjgwYmJkOTEzNzNiIn0sIndhcm5hZnRlciI6MTcwNzc0NTA3MX0sIm5iZiI6MTcwNzc0MTQ2NCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.KvlOcSuG_W3YqOKUKC9nIEI8ksITGwUoKboubyGcozHRg9v_OycyG-5O6EAOdmYKE5Hd1u-b9Bl8n4NhYDF_ygf8gzu406nEf-BpBgWjogOaGylD0dIopaeC3rK7G47S9nMD1cFDGTz07hygxIQSezUfZd1EkmaSq3nK1g0yk3lcKv9fKZpFHcBsZAOEUMJyQ2cLeE_Rdaf9i4w1XTtWL-5fcbWuOjHq96rBjIejP-myjE971bvCyN3WE39vRUYZMq8aO1XIVt9_bsOd-GaTVXgN1r0LNeCz1ye743J2JYWin2_ic5fkc5vUxeNObgg03WXvUAAkcvOaetSp5yLLIQ
...
✻ Header
{
"alg": "RS256",
"kid": "h9S_NPctZsbqi6gHkfi8VJcIJPEewsT2DvDuOG9VlkE"
}

✻ Payload
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1739277464, # This expiration date is an epoch, meaning the number of seconds since 1970
"iat": 1707741464,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "nginx-7854ff8877-8r7hs",
"uid": "197ca6b7-e120-4981-af8a-e9e99e2b1f0b"
},
"serviceaccount": {
"name": "default",
"uid": "b4ed2a0e-40f0-4b74-a378-f80bbd91373b"
},
"warnafter": 1707745071
},
"nbf": 1707741464,
"sub": "system:serviceaccount:default:default"
}
iat: 1707741464 2/12/2024, 9:37:44 AM
nbf: 1707741464 2/12/2024, 9:37:44 AM
exp: 1739277464 2/11/2025, 9:37:44 AM

✻ Signature KvlOcSuG_W3YqOKUKC9nIEI8ksITGwUoKboubyGcozHRg9v_OycyG-5O6EAOdmYKE5Hd1u-b9Bl8n4NhYDF_ygf8gzu406nEf-BpBgWjogOaGylD0dIopaeC3rK7G47S9nMD1cFDGTz07hygxIQSezUfZd1EkmaSq3nK1g0yk3lcKv9fKZpFHcBsZAOEUMJyQ2cLeE_Rdaf9i4w1XTtWL-5fcbWuOjHq96rBjIejP-myjE971bvCyN3WE39vRUYZMq8aO1XIVt9_bsOd-GaTVXgN1r0LNeCz1ye743J2JYWin2_ic5fkc5vUxeNObgg03WXvUAAkcvOaetSp5yLLIQ

# Converting the date to something we can read, just out of curiosity
date -d @1739277464
Tue Feb 11 2025 09:37:44 -03 # 1 YEAR

It is not possible to edit the service account of a pod; you need to delete and recreate a new one. In the case of a deployment, you can edit the deployment because it creates a new replicaset with new pod configurations.

If needed, we can create a token for a service account that is not used in any pod, but for third-party access we can do this.

kubectl get sa
NAME SECRETS AGE
default 0 3d15h
my-service-account 0 157m #<<< let's use this one

kubectl create token my-service-account --duration 87600h
eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoyMDIzMTA4NjU1LCJpYXQiOjE3MDc3NDg2NTUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6Im15LXNlcnZpY2UtYWNjb3VudCIsInVpZCI6IjExMDcwMmZmLTkwNjAtNDAyNy1hMTlmLWNjMDQyOGE2NzBiZiJ9fSwibmJmIjoxNzA3NzQ4NjU1LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpteS1zZXJ2aWNlLWFjY291bnQifQ.BzVKg4oVTCKScv76zb-507q3EenpaQF-0XbcmqDmEK7t23GY1euIMQdolgAriu8KzYet9TY_1SCKRn3vsR4a6qjl89BmWO9yxUuuaLLy1TmGRKr3KC854XaldlhZ7aT4hqg20Ut9V7lnPjUKBvIZ8xzILHkhv_ZlZUS9C7BzBeaR9IJY-qnQ5PUnxb5WqyThmdLWWwyUPQtve7YJuOEGSpvXo3AoXad9OjNXAkjPuxqWtxyIus8BIzTWxn0loQ1L47mHfMY_AAfBhblwNLx15GZr6Mjh1wzR0uNmzh2stcANYudcagz9bGtkMqel-rF-9ingoSm2cHEFeHdBbFY3EQ

Can I actually use this token? And if I want to delete it? Where can I see the tokens created to delete them?

To replicate the old scenario and have a non-expiring token linked to a service account, we first need to have the service account and then create a secret with a specific type and through annotations link the secret to the serviceaccount.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token # Specific token type
metadata:
name: my-service-account-token # name
annotations:
kubernetes.io/service-account.name: my-service-account # Service Account that will use it
EOF
kubectl describe secrets my-service-account-token
Name: my-service-account-token
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: my-service-account
kubernetes.io/service-account.uid: 110702ff-9060-4027-a19f-cc0428a670bf

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1107 bytes
namespace: 7 bytes
## Let's verify this token here
token: eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15LXNlcnZpY2UtYWNjb3VudC10b2tlbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJteS1zZXJ2aWNlLWFjY291bnQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIxMTA3MDJmZi05MDYwLTQwMjctYTE5Zi1jYzA0MjhhNjcwYmYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpteS1zZXJ2aWNlLWFjY291bnQifQ.frN4SSbI-SgoGLNjyapdOaUlc_WmTTFmWBO9hejdCnCkf5NfjiFVNkxSGht-cKNvlI2ADLz1-gVB3vIRbxFw-hXmxsZeH5heK5OeyMctpcRZrj-BRpwo1ubZ14VH94VH-nF8q7MwaA84uuuK0CiBOdSLITXkKRzVUkxR3qPa7MBNUs-0HIlMZQBzxkgQ24lu3cvPRX1rP9HX3UmxZaemARBBrqki03t1udJzDWwPkc48H_r3I0zSjdqxoIJ0NtDXj_fJdv8rjaeJC8TiXJvOmtvhpkleU0NC51BsjRi7ennWZ2MYO_9XjX4NDUkBcsovEAOOo4T7olJpv9lC3zSlwg

# VERIFYING
jwt eyJhbGciOiJSUzI1NiIsImtpZCI6Img5U19OUGN0WnNicWk2Z0hrZmk4VkpjSUpQRWV3c1QyRHZEdU9HOVZsa0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15LXNlcnZpY2UtYWNjb3VudC10b2tlbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJteS1zZXJ2aWNlLWFjY291bnQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIxMTA3MDJmZi05MDYwLTQwMjctYTE5Zi1jYzA0MjhhNjcwYmYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpteS1zZXJ2aWNlLWFjY291bnQifQ.frN4SSbI-SgoGLNjyapdOaUlc_WmTTFmWBO9hejdCnCkf5NfjiFVNkxSGht-cKNvlI2ADLz1-gVB3vIRbxFw-hXmxsZeH5heK5OeyMctpcRZrj-BRpwo1ubZ14VH94VH-nF8q7MwaA84uuuK0CiBOdSLITXkKRzVUkxR3qPa7MBNUs-0HIlMZQBzxkgQ24lu3cvPRX1rP9HX3UmxZaemARBBrqki03t1udJzDWwPkc48H_r3I0zSjdqxoIJ0NtDXj_fJdv8rjaeJC8TiXJvOmtvhpkleU0NC51BsjRi7ennWZ2MYO_9XjX4NDUkBcsovEAOOo4T7olJpv9lC3zSlwg

...
## WE DON'T HAVE AN EXPIRATION TIME
✻ Header
{
"alg": "RS256",
"kid": "h9S_NPctZsbqi6gHkfi8VJcIJPEewsT2DvDuOG9VlkE"
}

✻ Payload
{
"iss": "kubernetes/serviceaccount",
"kubernetes.io/serviceaccount/namespace": "default",
"kubernetes.io/serviceaccount/secret.name": "my-service-account-token",
"kubernetes.io/serviceaccount/service-account.name": "my-service-account",
"kubernetes.io/serviceaccount/service-account.uid": "110702ff-9060-4027-a19f-cc0428a670bf",
"sub": "system:serviceaccount:default:my-service-account"
}

✻ Signature frN4SSbI-SgoGLNjyapdOaUlc_WmTTFmWBO9hejdCnCkf5NfjiFVNkxSGht-cKNvlI2ADLz1-gVB3vIRbxFw-hXmxsZeH5heK5OeyMctpcRZrj-BRpwo1ubZ14VH94VH-nF8q7MwaA84uuuK0CiBOdSLITXkKRzVUkxR3qPa7MBNUs-0HIlMZQBzxkgQ24lu3cvPRX1rP9HX3UmxZaemARBBrqki03t1udJzDWwPkc48H_r3I0zSjdqxoIJ0NtDXj_fJdv8rjaeJC8TiXJvOmtvhpkleU0NC51BsjRi7ennWZ2MYO_9XjX4NDUkBcsovEAOOo4T7olJpv9lC3zSlwg

Now some curiosities I found along the way.

## If we look at the service account, we have the token linked there
kubectl describe sa my-service-account
Name: my-service-account
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: my-service-account-token #<<<<
Events: <none>

## We have the secret
kubectl get secret
NAME TYPE DATA AGE
my-service-account-token kubernetes.io/service-account-token 3 4m54s

## But the Service Account doesn't actually show that it has a secret. CRAZY?
kubectl get serviceaccounts
NAME SECRETS AGE
default 0 3d15h
my-service-account 0 3h2m #<<<< ????

We can create roles and do rolebinding with service accounts normally. Instead of User, it's ServiceAccount.