Saltar al contenido

Temas

EKS seguro

Identidad por pod con Pod Identity e IRSA, RBAC mínimo, Pod Security Standards, network policies, control de admisión con Kyverno y GuardDuty EKS Protection.

Mover la API de Citas de ECS Fargate a EKS te da flexibilidad, pero añade un plano de control entero —el de Kubernetes— con su propia identidad, su propia autorización y sus propias formas de escalar privilegios. Un clúster de EKS tiene dos capas de seguridad que deben encajar: la de Kubernetes (RBAC, admisión, aislamiento de pods) y la de AWS (qué permisos IAM recibe cada pod). Si cualquiera de las dos es laxa, comprometer un pod puede convertirse en comprometer la cuenta. Este es el recorrido para endurecer ambas.

Identidad: credenciales por pod, no por nodo#

El error más caro en EKS es dejar que los pods usen el rol del nodo (el instance profile de la EC2). Ese rol suele tener permisos para la VPC CNI, ECR, logs… y, si un pod puede llegar al IMDS del nodo, hereda todos esos permisos. Un solo pod comprometido pasa a hablar con la API de AWS como el nodo entero. La solución es dar a cada pod su propia identidad, acotada a lo que ese servicio necesita.

Hay dos mecanismos, y conviene saber cuándo usar cada uno:

  • IRSAIRSAMecanismo de EKS que federa un service account de Kubernetes con un rol IAM mediante un proveedor OIDC, dando credenciales temporales por pod. (IAM Roles for Service Accounts): federa un service account con un rol IAM a través de un proveedor OIDC por clúster. Anotas el service account con el ARN del rol y los SDK asumen el rol vía sts:AssumeRoleWithWebIdentity. Es la opción válida en Fargate.
  • EKS Pod IdentityEKS Pod IdentityMétodo de EKS que asocia un rol IAM a un service account sin proveedor OIDC; un agente entrega credenciales por pod a través de la API de EKS Auth.: más reciente y simple. No usa OIDC: defines una asociación rol ↔ service account y añades el principal pods.eks.amazonaws.com a la política de confianza del rol. Un agente (DaemonSet) entrega las credenciales por nodo. Un mismo rol se reutiliza entre clústeres sin tocar su política de confianza.
serviceaccount-irsa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: citas-api
namespace: citas
annotations:
# La política de confianza del rol condiciona el 'sub' a este SA+namespace
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/citas-api
asociación Pod Identity
aws eks create-pod-identity-association \
--cluster-name citas-prod \
--namespace citas \
--service-account citas-api \
--role-arn arn:aws:iam::123456789012:role/citas-api
# La política de confianza del rol confía en pods.eks.amazonaws.com

En ambos casos, el rol citas-api solo concede lo que la API necesita (leer su secreto en Secrets Manager, su cola SQS), siguiendo la lógica de evaluación de IAM. Y para cerrar el círculo, restringe el acceso al IMDS del nodo (HttpPutResponseHopLimit = 1) para que un pod no pueda robar el rol del nodo aunque lo intente.

Admisión: la política que decide qué entra#

Antes de que un pod llegue a etcd, el API server pasa la petición por autenticación, por RBAC y, por último, por el control de admisiónControl de admisiónFase de la API de Kubernetes que intercepta cada objeto tras autenticarlo y autorizarlo, para validarlo o mutarlo antes de persistirlo en etcd.. Ese último filtro es donde impones tus reglas de seguridad sobre todo lo que entra al clúster, venga del pipeline o de un kubectl apply manual.

Control de admisión e identidad en EKS
NODO EC2CI/CDkubectl applyAPI serverauthn · RBACKyvernowebhook admisiónPod privilegiadoimagen sin firmarPod conformeno root · firmadoRol IAMdel podevalúa la políticaDENY · privilegedADMITPod Identityno usa el rol del nodoDeploymentAdmissionReviewprivilegedrestrictedcreds

Paso 1 de 5Despliegue

Un pipeline (o un kubectl apply) manda dos manifiestos al API server de EKS. Primero pasan por autenticación y por RBAC: ¿puede este sujeto crear pods en ese namespace?

RBAC con mínimo privilegio#

El RBACRBAC de KubernetesModelo de autorización de Kubernetes que concede permisos sobre la API uniendo sujetos (usuarios, grupos, service accounts) a Roles mediante RoleBindings. de Kubernetes es puramente aditivo: no hay reglas de denegación, así que el mínimo privilegio se consigue concediendo poco. Evita cluster-admin por comodidad y no montes el service account default. Un rol típico para la API de Citas no necesita casi nada del plano de control:

rbac-citas.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: citas
name: citas-api
rules:
- apiGroups: ['']
resources: ['configmaps']
verbs: ['get', 'list'] # solo lectura, solo su namespace

Pod Security Standards#

Los Pod Security StandardsPod Security StandardsTres perfiles de Kubernetes (privileged, baseline, restricted) que limitan lo que puede pedir un pod; se aplican por namespace con Pod Security Admission. definen tres perfiles —privileged, baseline, restricted— que se aplican por namespace con Pod Security Admission (integrado y estable desde Kubernetes 1.25). Son el suelo de seguridad barato: una etiqueta en el namespace rechaza pods privilegiados sin instalar nada.

namespace-citas.yaml
apiVersion: v1
kind: Namespace
metadata:
name: citas
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted

El perfil restricted obliga a runAsNonRoot, allowPrivilegeEscalation: false, seccompProfile: RuntimeDefault y capabilities drop: ALL. Es exactamente lo que ya preparaste al endurecer la imagen en contenedores y ECR.

Políticas a medida con Kyverno u OPA Gatekeeper#

PSA solo tiene tres plantillas fijas. Para reglas propias —exigir que las imágenes vengan de tu ECR, que estén firmadas, que lleven límites de recursos— necesitas un controlador de admisión como KyvernoKyvernoMotor de políticas para Kubernetes que valida, muta, genera y verifica firmas de imágenes como controlador de admisión, con políticas escritas en YAML. u OPA Gatekeeper. Kyverno usa políticas en YAML (sin lenguaje aparte como el Rego de Gatekeeper) y puede validar, mutar y verificar firmas de imágenes.

kyverno-solo-ecr-firmado.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: solo-imagenes-ecr-confiables
spec:
rules:
- name: exigir-registro-ecr
match:
any:
- resources:
kinds: ['Pod']
validate:
# En Kyverno reciente la acción se fija por regla (Audit | Enforce).
# El antiguo spec.validationFailureAction quedó obsoleto en 1.13.
failureAction: Enforce
message: 'Solo se permiten imágenes de nuestro ECR.'
pattern:
spec:
containers:
- image: '123456789012.dkr.ecr.eu-west-1.amazonaws.com/*'

Red: nada de “todos hablan con todos”#

Por defecto, en Kubernetes cualquier pod puede conectarse a cualquier otro. Es la cáscara de huevo aplicada a la red del clúster: basta comprometer un pod para escanear y alcanzar la base de datos o el plano de control. Una NetworkPolicy de default-deny por namespace, y luego permisos explícitos, aplica zero trust dentro del clúster. (En EKS necesitas un CNI que las implemente: el VPC CNI ya soporta network policies, o usa Calico/Cilium.)

default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: citas
spec:
podSelector: {} # todos los pods del namespace
policyTypes: [Ingress, Egress] # sin reglas = se deniega todo

Detección: GuardDuty EKS Protection#

Prevenir no basta; necesitas ver qué pasa dentro. GuardDuty EKS Protection aporta dos capas:

  • EKS Audit Log Monitoring: analiza los logs de auditoría del API server (sin que tengas que habilitarlos ni gestionarlos) y detecta patrones sospechosos: uso de credenciales anónimas, peticiones desde IPs maliciosas conocidas, intentos de escalada de privilegios.
  • EKS Runtime Monitoring: con un agente, observa el comportamiento a nivel de sistema operativo en los nodos (procesos, conexiones, acceso a ficheros) y detecta actividad propia de un contenedor comprometido, como la ejecución de una reverse shell o el minado de criptomonedas.

Sus hallazgos llegan a Security Hub y a EventBridge, igual que los del resto de la cuenta; encaja con lo que ves en GuardDuty y Detective. Es la capa que te avisa cuando la prevención ha fallado.

Tu turno

Endurece un namespace de tu clúster de prácticas

En un clúster de EKS (o kind/minikube para la parte de Kubernetes) de tu propio lab:

  • Aplica pod-security.kubernetes.io/enforce: restricted a un namespace e intenta desplegar un pod con privileged: true. ¿Qué mensaje devuelve el API server?
  • Crea un service account con un Role de solo lectura y comprueba, con kubectl auth can-i, que no puede listar secrets suplantándolo (--as=system:serviceaccount:NAMESPACE:SA).
  • Si tienes EKS: asocia ese service account a un rol IAM con Pod Identity o IRSA y verifica desde el pod (aws sts get-caller-identity) que la identidad es la del rol del pod y no la del nodo.
  • Aplica una NetworkPolicy default-deny y observa qué se rompe. Eso te dice qué comunicaciones estabas permitiendo sin saberlo.

Reto

Pones RBAC estricto y Pod Security Standards restricted. ¿Hace falta algo más para que un pod no toque toda tu cuenta de AWS?

▸Mostrar solución

Sí: la identidad de AWS. RBAC y PSS son controles de Kubernetes; no limitan qué permisos IAM recibe el pod. Si el pod usa el rol del nodo (o un rol con *), un atacante dentro del pod habla con la API de AWS con esos permisos. Dale a cada pod su rol acotado con Pod Identity o IRSA y restringe el IMDS del nodo.

Reto

¿Por qué no basta con Pod Security Admission para exigir que las imágenes estén firmadas y vengan de tu ECR?

▸Mostrar solución

Porque PSA solo conoce tres plantillas fijas (privileged, baseline, restricted) centradas en el securityContext del pod. No sabe nada de registros ni de firmas. Esa clase de regla —procedencia e integridad de la imagen— es trabajo de un controlador de admisión con políticas propias, como Kyverno (verifyImages) u OPA Gatekeeper.

En resumen#

  • Da a cada pod su propia identidad IAM con Pod Identity (o IRSA en Fargate); nunca dejes que use el rol del nodo, y restringe el IMDS.
  • RBAC es aditivo: concede poco, evita cluster-admin y vigila los verbos que escalan (secrets, pods/exec, bind/escalate).
  • Pon el suelo con Pod Security Standards restricted (PSA, estable desde 1.25) y añade reglas propias con Kyverno u OPA Gatekeeper en el control de admisión.
  • Aplica NetworkPolicies default-deny: en un clúster, por defecto todos los pods se hablan.
  • Detecta con GuardDuty EKS Protection (audit logs + runtime monitoring): la capa que avisa cuando la prevención falla.

Referencias#

Notas relacionadas

ESC
↑↓ para navegar↵ para abriresc para cerrar