Secrets de Kubernetes y robo de tokens de cuenta de servicio
Detecta robo de secrets y tokens de cuentas de servicio de Kubernetes en los audit logs: list globales, ráfagas, TokenRequest, uso desde IP pública, can-i.
TL;DR. El robo de credenciales en Kubernetes deja dos tipos de huellas. En el lado de los secrets: un list o watch sobre secrets sin namespace (todos los secrets del clúster, con su contenido), o una identidad que lee muchos secrets distintos en poco tiempo. En el lado de los tokens: una cuenta de servicio usada desde una IP pública o con kubectl/curl como user agent, una ráfaga de kubectl auth can-i, ráfagas de 403 y tokens nuevos emitidos con TokenRequest. Cada secret leído es una credencial que rotar, y en GKE las lecturas solo se registran si los logs de Data Access están activados.
Los secrets son la razón por la que la mayoría de los atacantes se molestan con un clúster: contraseñas de bases de datos, claves cloud, credenciales de registry, tokens de CI, claves de firma. ATT&CK lo clasifica como T1552.007, Container API, y la parte de tokens como T1528, Steal Application Access Token.
Cómo se filtran los secrets a través de la API
Las buenas prácticas de RBAC de Kubernetes subrayan algo que muchos equipos pasan por alto: list y watch sobre secrets revelan su contenido, igual que get. Un rol que «solo lista» secrets es un rol que los lee todos.
| Solicitud | Rastro de auditoría | Significado |
|---|---|---|
kubectl get secrets -A | list sobre secrets, sin objectRef.namespace | Todos los secrets del clúster en una respuesta |
kubectl get secrets -n shop | list sobre secrets, namespace shop | Todos los secrets del namespace |
kubectl get secret db-creds -o yaml | get sobre secrets, con nombre | Un secret |
| Crear un pod que monta un secret | create sobre pods con un volumen secret | Lectura indirecta: quien puede crear pods puede leer los secrets que montan |
La última fila importa: el permiso para crear workloads en un namespace concede implícitamente acceso a sus secrets y a sus tokens de cuentas de servicio. Esa lectura no aparece como un evento get secrets.
En los niveles por defecto de EKS y GKE, y en cualquier política sensata, los secrets se registran en Metadata: ves quién leyó qué secret, nunca el valor. Es suficiente para acotar la rotación.
La salvedad de GKE
En GKE, get y list van al log de auditoría de Data Access, que está desactivado por defecto (consulta la guía de exportación). Sin él, las lecturas de secrets no dejan ningún rastro. En AKS, la categoría más barata kube-audit-admin también descarta get y list.
Detecciones de secrets
El analizador de audit logs tiene dos reglas:
- Secrets listados en todos los namespaces (alta):
listowatchsobresecretssin namespace, permitido, por una identidad que no es de sistema. Los controladores que vigilan legítimamente todos los secrets (por ejemplo, operadores de ingress o de certificados) deben añadirse a la lista de permitidos tras comprobarlo. - Ráfaga de lecturas de secrets (media): una identidad que lee 10 o más secrets distintos en 10 minutos. Contar nombres distintos evita marcar una aplicación que vuelve a leer su propio secret.
Los kubelets (system:node:*) leen todo el día los secrets de los pods programados en su nodo; es normal y está excluido.
Cómo se roban los tokens de cuentas de servicio
Los pods reciben un token de su cuenta de servicio, montado por defecto en /var/run/secrets/kubernetes.io/serviceaccount/token. Desde Kubernetes 1.22 son tokens de corta duración, rotados automáticamente y obtenidos a través de la API TokenRequest; desde la 1.24, los tokens de larga duración guardados en Secrets ya no se crean automáticamente, según la documentación de cuentas de servicio. Los Secrets de tokens heredados, creados a mano o antes de una actualización, siguen presentes en muchos clústeres.
Vías de robo típicas:
- leer el token montado tras explotar una aplicación (RCE, SSRF, path traversal);
kubectl exec … cat /var/run/secrets/kubernetes.io/serviceaccount/token;- leer un Secret de token heredado, un kubeconfig guardado en un Secret o una variable de CI;
- un dashboard o herramienta expuestos que corren con una cuenta de servicio con demasiados privilegios;
- emitir un token nuevo con
kubectl create token(TokenRequest) cuando RBAC permitecreatesobreserviceaccounts/token.
Cómo se ve un token reutilizado
Un token usado por su workload sigue un patrón estable: IPs de pods, el user agent propio de la aplicación y las mismas pocas llamadas a la API. Un token reutilizado rompe ese patrón:
| Señal | Regla del analizador | Severidad |
|---|---|---|
| Solicitud de una cuenta de servicio desde una IP pública (Internet) | Token de cuenta de servicio usado desde una IP pública | Alta |
Cuenta de servicio con user agent kubectl/, curl/, python-requests/, Go-http-client/… | Token de cuenta de servicio usado con kubectl / curl | Media |
5 o más selfsubjectaccessreviews / selfsubjectrulesreviews en 5 minutos | Enumeración de permisos (kubectl auth can-i) | Media |
| 10 o más respuestas 403 en 10 minutos | Ráfaga de solicitudes denegadas | Media |
| User agent de kube-hunter, peirates, kubeletctl, kubiscan… | User agent de una herramienta de ataque para Kubernetes | Alta |
create sobre serviceaccounts/token | Token de cuenta de servicio generado | Media |
kubectl auth can-i --list produce una SelfSubjectRulesReview; kubectl auth can-i create pods, una SelfSubjectAccessReview. Los workloads rara vez llaman a ninguna de las dos, así que una ráfaga desde una cuenta de servicio es alguien descubriendo qué puede hacer con su token robado (ATT&CK T1613).
Salvedades que conviene tener presentes:
- El user agent lo controla el cliente. Su ausencia no prueba nada; su presencia sigue siendo una buena pista.
- La IP de origen se toma de la última entrada de
sourceIPs. En clústeres detrás de un proxy o con endpoint privado, puede que nunca aparezca una dirección «pública». - Algunos operadores escritos en Go usan un agente genérico
Go-http-client. Compruébalo antes de escalar.
Seguir un token
Las versiones recientes de Kubernetes añaden un identificador de credencial (el JTI del token) en user.extra para los tokens de cuentas de servicio. Cuando está presente, permite distinguir dos tokens de la misma cuenta de servicio: el que usa el pod y el que el atacante copió o emitió. Pivota sobre él igual que sobre una IP.
En una TokenRequest, el cuerpo de la solicitud (registrado en nivel Request en EKS y en la política recomendada) muestra expirationSeconds y las audiencias. Un token pedido para un año (31536000) por un cliente interactivo es un paso de persistencia, no una renovación de un workload. El API server puede limitar la vida de los tokens con --service-account-max-token-expiration.
Alcance y remediación
- Lista cada secret leído por las identidades comprometidas, incluidos los de las respuestas de
listglobales: ante unlistde todo el clúster, asume que todos los secrets quedaron expuestos. - Rótalos en origen: la contraseña de la base de datos, la clave de acceso cloud, la credencial del registry, no solo el objeto Secret de Kubernetes.
- Invalida los tokens. Los tokens ligados mueren con el objeto al que están ligados; los emitidos con TokenRequest siguen siendo válidos hasta que caducan salvo que borres la cuenta de servicio. Borrar y volver a crear la cuenta de servicio, y reiniciar después sus workloads, es la salida fiable. Borra los Secrets de tokens heredados.
- Encuentra el punto de entrada: el pod que filtró el token, el dashboard, el job de CI.
- Corta el camino:
automountServiceAccountToken: falsedonde no se necesite la API, roles de mínimo privilegio sinlistsobre secrets, y nada decreatesobreserviceaccounts/tokenfuera de los controladores.
Revisa también el lado cloud: una clave cloud robada de un Secret se usa contra la API del proveedor, no contra Kubernetes. Los sitios hermanos AWS Forensics, GCP Forensics y Azure Forensics cubren esos logs.