Skip to main content

Service Accounts

Haz una revisión en cka service accounts.

Lo que necesitamos incluir.

No es posible crear un pod con una cuenta de servicio que no existe.

Es una buena práctica que cada aplicación cree una cuenta de servicio incluso si no hace un binding con ningún role o cluster role. Esta SA puede ser usada en aplicaciones que no necesiten acceso al clúster.

Si no definimos el serviceAccountName en el pod se usará la SA default. Imagina que todos los pods del mismo namespace estén usando la cuenta de servicio default, si uno de ellos necesita algún permiso mayor en esta cuenta de servicio, todos los otros pods tendrán el mismo permiso sin necesidad.

Otro detalle al que debemos prestar atención es que la mayoría de las aplicaciones no necesitan acceso a la API de Kubernetes siendo que la SA es exactamente para eso. En ese caso podemos evitar que el token sea montado cortando totalmente el acceso.

kubectl run nginx --image=nginx -o yaml --dry-run=client
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

kubectl run nginx --image=nginx -o yaml --dry-run=client > pod-nginx.yaml

k apply -f pod-nginx.yaml

# Vamos a entrar al pod para comprobar unos detalles
k exec -it nginx -- bash

root@nginx:/# cd /run/secrets/kubernetes.io/serviceaccount/
root@nginx:/run/secrets/kubernetes.io/serviceaccount# ls
ca.crt namespace token
root@nginx:/run/secrets/kubernetes.io/serviceaccount# cat namespace
default

root@nginx:/run/secrets/kubernetes.io/serviceaccount# cat token
eyJhbGciOiJSUzI1NiIsImtpZCI6InNOUENuTnQxUVVoYjBrb0NTWXJMdE14ZW4xSzd1VHYtVGpwYWNRV1JYcncifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzU1ODA4NDA0LCJpYXQiOjE3MjQyNzI0MDQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZjI3NzUzM2EtYWRhNi00M2EzLWI2YWUtYjY5YTk1MTJjYWU0Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoicGVyc29uYWwtY2x1c3Rlci13b3JrZXIzIiwidWlkIjoiYWIwOWU2YzAtMDg5NC00ZjkwLWJlMGQtYzIzYTc5NzA5N2FkIn0sInBvZCI6eyJuYW1lIjoibmdpbngiLCJ1aWQiOiI2Njk2Y2IzMy01YzlmLTQzYmUtYjRhYS1kYjAxZmIwOTg2YjUifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJhYjAyODA5ZS0yNDAxLTQ2ZmYtYTNjNi0wMzc4OTdkMGQxZmUifSwid2FybmFmdGVyIjoxNzI0Mjc2MDExfSwibmJmIjoxNzI0MjcyNDA0LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.WgmNnF2dblSDjnlbH9j0MuzRu4yX1XKj4Q3wTlkQi-r482HNT-vsyHmmtJwy_6XLEFgMeNe0NSizhrRpgluYPdERwQcyaX8Y_GX0GIM0eGaiGm0qHS31oK115Ch_7SaGHPRdNv-cNQutQz1R-8ptTXEnxRNK2a8furX-mWPBXMdQkTrgObOBWnmP-O0lsiCBqCFDQmw6qXTIUby-7ipg3M82xBp8JP_ml-nOpFI5cMoq2SKcaryOk2t8mWT1KdsrROmtuQrEqcK_eBD6KY2Sm84VrB_XWHdUJ3jikn5utpIkiqzWjFntHoos5asVxNwvH-R_Y2_xKqGzxYsCU9b1pg

root@nginx:/run/secrets/kubernetes.io/serviceaccount# env | grep KUBE
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443

# Vamos a intentar llegar a la API, pero tenemos un problema, nos estamos autenticando como system:anonymous.
root@nginx:/run/secrets/kubernetes.io/serviceaccount# curl https://$KUBERNETES_SERVICE_HOST -k
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}

# Vamos a pasar el token. Este token siendo cuenta de servicio default vamos a autenticarnos como system:serviceaccount:default:default
root@nginx:/run/secrets/kubernetes.io/serviceaccount# curl https://$KUBERNETES_SERVICE_HOST -k -H "Authorization: Bearer $(cat token)"
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:serviceaccount:default:default\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
root@nginx:/run/secrets/kubernetes.io/serviceaccount# curl https://$KUBERNETES_SERVICE_HOST/version -k -H "Authorization: Bearer $(cat token)"
{
"major": "1",
"minor": "30",
"gitVersion": "v1.30.0",
"gitCommit": "7c48c2bd72b9bf5c44d21d7338cc7bea77d0ad2a",
"gitTreeState": "clean",
"buildDate": "2024-05-13T22:00:36Z",
"goVersion": "go1.22.2",
"compiler": "gc",
"platform": "linux/amd64"
}
exit

# Y solo para mostrar que los volúmenes fueron montados

❯ k get pod nginx -o yaml
apiVersion: v1
kind: Pod
metadata:
...
namespace: default
resourceVersion: "11254882"
uid: 08243b7c-1208-49a7-affc-58348a481cb6
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount #<<<<
name: kube-api-access-mmcdn
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: personal-cluster-worker3
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
# Aquí tenemos los volúmenes con los archivos
volumes:
- name: kube-api-access-mmcdn
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token #<<<<
- configMap:
items:
- key: ca.crt
path: ca.crt #<<<<
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace #<<<<

Generalmente un pod no necesita acceder a la API a menos que sea parte de un operador que trabajará con recursos del clúster.

Ahora vamos a crear el mismo pod con cuenta de servicio específica y retirar el automontaje del token. Edita el archivo pod-nginx.yaml

apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
# Definimos la cuenta de servicio y desactivamos el automontaje
serviceAccountName: acessor # una cuenta de servicio que ya existe en el namespace
automountServiceAccountToken: false
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
❯ k replace -f pod-nginx.yaml --force
pod "nginx" deleted
pod/nginx replaced

# No montó ni la carpeta secrets
k exec -it nginx -- bash
root@nginx:/# ls /run/
lock/ nginx.pid
exit

# Y podemos comprobar que no montó ni un volumen

❯ k get pod nginx -o yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2024-08-21T20:55:24Z"
labels:
run: nginx
name: nginx
namespace: default
resourceVersion: "11253497"
uid: 362c77fb-e4dc-4195-9287-0fa3fcabf4d7
spec:
automountServiceAccountToken: false
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: personal-cluster-worker3
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: acessor # Cuenta de servicio usada
serviceAccountName: acessor
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2024-08-21T20:55:26Z"
status: "True"
type: PodReadyToStartContainers
- lastProbeTime: null
lastTransitionTime: "2024-08-21T20:55:24Z"
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2024-08-21T20:55:26Z"
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2024-08-21T20:55:26Z"
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2024-08-21T20:55:24Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: containerd://747371ca8c09824d1689d89cee6c494431eb6bf6cbaa68bfb11daae12b8c8c70
image: docker.io/library/nginx:latest
imageID: docker.io/library/nginx@sha256:447a8665cc1dab95b1ca778e162215839ccbb9189104c79d7ec3a81e14577add
lastState: {}
name: nginx
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2024-08-21T20:55:25Z"
hostIP: 172.18.0.4
hostIPs:
- ip: 172.18.0.4
phase: Running
podIP: 10.32.0.11
podIPs:
- ip: 10.32.0.11
qosClass: BestEffort
startTime: "2024-08-21T20:55:24Z"