Módulo 6: Runtime Security Admission Control And Image Scanning
2. Qué es un *admission controller*, y por qué corre antes de que el objeto exista
Descripción
kube-apiserver, desde el Módulo 1, lección 6, es "la única puerta de entrada al clúster". Esa misma lección ya adelantó, en una frase, el destino de este módulo: "Todo —kubectl, cualquier operador como ArgoCD (Módulo 5), cualquier admission controller (Módulo 6)— habla con Kubernetes exclusivamente a través de este componente (...) y —lo más importante para el Módulo 6— es el punto exacto donde corre el admission control antes de que un objeto se guarde". Esta lección abre esa frase palabra por palabra: qué pasos exactos atraviesa un kubectl apply antes de llegar a etcd, dónde exactamente se para un admission controller dentro de esos pasos, y por qué eso —y no otra cosa— es lo que hace que esta capa sea "preventiva" en el mismo sentido exacto en que cloud-security-and-guardrails-guide ya usó esa palabra.
Conexión con el módulo
Esta lección es puramente conceptual —ningún comando nuevo, ningún manifiesto—, y existe por la misma razón que la lección 2 del Módulo 4 de cloud-security-and-guardrails-guide fue puramente conceptual antes de instalar conftest: para que cuando la lección 4 te ponga a instalar Gatekeeper de verdad, no estés aprendiendo el ciclo de vida completo de un request y la sintaxis de un ConstraintTemplate al mismo tiempo.
Analogía: el portero, con el ciclo completo esta vez
La lección 1 dejó al portero parado en la puerta, revisando una lista. Esta lección abre esa escena en cámara lenta, con los pasos exactos que ocurren antes de que cualquiera cruce el umbral: primero, alguien en la entrada confirma que tú eres quien dices ser —tu identificación, no tu intención— (autenticación); después, otra persona revisa si a esa identidad, específicamente, se le permite entrar a esta sección del edificio —quizás tienes identificación válida, pero tu tarjeta no abre el piso 12 (autorización); y solo entonces, con identidad y permiso ya confirmados, el portero de la lección 1 revisa una lista distinta —no quién eres, sino qué traes contigo: ¿la maleta cumple el peso máximo? ¿el paquete tiene la etiqueta correcta? (admission control). Recién ahí, si las tres puertas se cruzaron sin objeción, el evento queda anotado en el registro oficial del edificio (etcd). Cambiar cualquiera de los tres primeros pasos —tu identidad, tu permiso, o el contenido de tu maleta— nunca cambia el hecho de que el registro solo existe después de las tres revisiones, nunca antes.
El ciclo de vida completo de un request contra kube-apiserver
Cuando corres kubectl apply -f bad-pod-no-limits.yaml, ese YAML no llega a etcd de un salto. Atraviesa, en orden estricto, cuatro fases dentro de kube-apiserver — y las cuatro son parte del mismo componente, el mismo proceso, la misma "única puerta de entrada" del Módulo 1:
kubectl apply -f bad-pod-no-limits.yaml
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ kube-apiserver │
│ │
│ 1. AUTENTICACIÓN ¿quién eres? (certificado, token) │
│ │ falla → 401 Unauthorized │
│ ▼ │
│ 2. AUTORIZACIÓN (RBAC) ¿tienes permiso para esta acción? │
│ │ falla → 403 Forbidden │
│ ▼ │
│ 3. ADMISSION CONTROL ¿este objeto específico tiene permitido │
│ │ existir? (MutatingWebhook, después │
│ │ ValidatingWebhook — ESTE MÓDULO) │
│ │ falla → 403 Forbidden, "admission webhook │
│ │ denied the request" │
│ ▼ │
│ 4. PERSISTENCIA el objeto se escribe en etcd │
└─────────────────────────────────────────────┬─────────────────────┘
▼
etcd
(el objeto EXISTE, recién ahora)
Fíjate en algo importante: hasta este módulo, tus requests siempre atravesaron las fases 1, 2 y 4 sin objeción —tu kubeconfig de kind te autentica automáticamente como kubernetes-admin, con un ClusterRoleBinding que te autoriza para todo—. La fase 3, admission control, existía técnicamente desde el primer kubectl apply de esta guía (kind trae algunos admission controllers internos activados por defecto, como NamespaceLifecycle o LimitRanger), pero nunca tuvo ninguna política tuya cargada — nunca rechazó nada porque nunca le pediste que evaluara nada. Este módulo no inventa la fase 3: la puebla, por primera vez, con reglas que tú escribiste.
MutatingWebhookConfiguration y ValidatingWebhookConfiguration, por su nombre exacto
La fase de admission control tiene, en realidad, dos sub-fases, en orden estricto:
MutatingWebhookConfiguration— corre primero. Un webhook de este tipo puede modificar el objeto antes de que continúe —por ejemplo, inyectarle una etiqueta, o un valor por defecto que faltaba—. Gatekeeper y Kyverno registran webhooks de este tipo (Gatekeeper los usa para su motor deAssign/ModifySet, fuera del alcance de este módulo), pero ninguna de las políticas de este módulo muta nada — las dos políticas que vas a construir (lecciones 4 y 6) son puramente de validación.ValidatingWebhookConfiguration— corre después, sobre el objeto ya mutado (si algo lo mutó). Un webhook de este tipo solo puede responderallowed: trueoallowed: false— no cambia nada, solo decide si el objeto pasa. Esta es la pieza exacta que Gatekeeper y Kyverno registran para las políticas de este módulo: cuando instales Gatekeeper (lección 4), vas a ver aparecer un objeto llamado, literalmente,gatekeeper-validating-webhook-configuration; cuando instales Kyverno (lección 6), vas a verkyverno-resource-validating-webhook-cfg. Los dos nombres, con el sufijovalidating-webhook, delatan exactamente en qué sub-fase actúan.
Cuando un ValidatingWebhookConfiguration responde allowed: false, kube-apiserver nunca llega a la fase 4. El objeto que rechazaste no queda "guardado y marcado como inválido" en ningún lado — sencillamente nunca existió. No hay un registro de "este Pod fue rechazado" en etcd, porque etcd nunca supo de su existencia. La única evidencia de que el intento ocurrió es el mensaje de error que kubectl te devolvió en tu propia terminal, en el momento exacto del intento — vas a ver ese mensaje, literal, en la lección 4.
Por qué esto es preventivo, no detectivo
cloud-security-and-guardrails-guide, en su Módulo 7, dejó esta distinción con nombre formal: "Un guardrail preventivo no pudo pasar. Actúa antes de que el evento exista (...) Un guardrail detectivo queda registro. Actúa después: no impidió nada, pero deja evidencia consultable de que algo ocurrió." El diagrama de arriba es la prueba de que un admission controller cae, sin ambigüedad, en la primera categoría: el rechazo ocurre en la fase 3, antes de la fase 4 (persistencia en etcd). No hay ningún momento en el que el objeto rechazado haya "existido brevemente" para después ser eliminado — el rechazo previene que exista, punto.
Contrasta esto con lo que un guardrail detectivo haría con el mismo escenario: si en vez de un ValidatingWebhookConfiguration tuvieras únicamente los logs de auditoría de Kubernetes (kube-apiserver puede registrar cada request, aceptado o no, en un archivo — fuera del alcance de este módulo, análogo a lo que CloudTrail hace del lado de AWS), ese Pod sin límites de recursos sí existiría, corriendo, consumiendo memoria sin techo, y el log de auditoría solo te permitiría responder, después del hecho, "¿quién creó este Pod, y cuándo?" — nunca "impedir" que se creara. La misma guía que ya construyó ese vocabulario con CloudTrail te da, aquí, el ejemplo especular exacto: CloudTrail es la cámara; el admission controller de este módulo es el portero. Los dos existen en el mismo ecosistema, resolviendo el mismo problema general —"¿qué pasó, o qué está por pasar?"— en capas completamente distintas.
| Admission control (este módulo) | Logs de auditoría de Kubernetes (fuera de alcance) | CloudTrail (cloud-security-and-guardrails-guide, M7) | |
|---|---|---|---|
| Categoría | Preventivo | Detectivo | Detectivo |
| Cuándo actúa | Antes de la fase 4 (persistencia en etcd) | Después de cualquier fase, registrando lo que pasó | Después de cualquier llamada a la API de AWS |
| Qué puede hacer | Rechazar el objeto — nunca llega a existir | Nada — solo deja constancia | Nada — solo deja constancia |
| Pregunta que responde | "¿Tiene permitido existir?" | "¿Qué pasó, exactamente?" | "¿Qué pasó, exactamente?" |
Errores comunes
Pensar que un objeto rechazado por admission control "existió un instante" antes de borrarse (de modelo mental incorrecto). Qué pasa: alguien, viendo el mensaje admission webhook denied the request, imagina que Kubernetes creó el objeto y después lo eliminó. Cómo detectarlo: si tu explicación de lo que pasó incluye la palabra "borrado" o "eliminado". Cómo corregirlo: mira el diagrama de esta lección — la fase de admission control (3) ocurre antes de la fase de persistencia (4). Un objeto rechazado nunca llega a la fase 4; no hay nada que borrar porque nunca se escribió. kubectl get pods nunca lo va a mostrar, ni siquiera por una fracción de segundo — no existe ningún estado intermedio.
Confundir autorización (RBAC) con admission control (de capas mezcladas). Qué pasa: alguien asume que si kubectl te deja correr el comando (RBAC te autoriza), el objeto necesariamente se crea. Cómo detectarlo: si tu razonamiento es "tengo permiso de cluster-admin, así que nada me puede detener". Cómo corregirlo: RBAC (fase 2) responde una pregunta distinta de admission control (fase 3) — RBAC decide si tú, como identidad, puedes intentar crear un Pod en general; admission control decide si ese Pod específico, con ese contenido exacto, tiene permitido existir. Ser cluster-admin te garantiza pasar la fase 2 siempre — no te exime de la fase 3. La lección 4 lo confirma en vivo: vas a intentar crear un Pod con las credenciales de administrador completo del clúster, y Gatekeeper lo va a rechazar igual.
Asumir que admission control es exclusivo de Gatekeeper/Kyverno (de alcance incompleto). Qué pasa: alguien concluye que sin instalar uno de los dos motores, la fase 3 del ciclo de vida simplemente no existe. Cómo detectarlo: si tu resumen de esta lección es "admission control = Gatekeeper o Kyverno". Cómo corregirlo: Kubernetes trae admission controllers internos, compilados en el propio binario de kube-apiserver, activos por defecto en cualquier clúster —incluido kind— desde el primer día (NamespaceLifecycle, que impide crear objetos en un namespace que está siendo eliminado; LimitRanger, que aplica límites por defecto declarados con un objeto LimitRange, si existiera uno). Gatekeeper y Kyverno no crean la fase 3 — se conectan a ella, vía ValidatingWebhookConfiguration, para agregar reglas propias además de las que ya venían por defecto.
Ejercicios
Ejercicio 1 — Ordena las cuatro fases sin mirar el diagrama. Sin volver a mirar el diagrama de esta lección, ordena estas cuatro fases en el orden correcto en el que kube-apiserver las ejecuta: (a) persistencia en etcd; (b) admission control; (c) autorización (RBAC); (d) autenticación.
Ver solución
El orden correcto es: (d) autenticación → (c) autorización (RBAC) → (b) admission control → (a) persistencia en etcd. La regla mnemotécnica: primero se confirma quién eres (autenticación), después si tienes permiso general para la acción (autorización), después si ese objeto específico tiene permitido existir (admission control), y solo al final se guarda (persistencia). Si invertiste (b) y (c), recuerda: RBAC nunca mira el contenido del objeto que estás creando, solo el verbo (create, get, delete) y el tipo de recurso (pods, deployments) — admission control es la única fase que sí inspecciona el contenido completo del YAML.
Ejercicio 2 — Explica, sin usar la palabra "preventivo", por qué admission control no es lo mismo que un log de auditoría. Un compañero, después de leer sobre los logs de auditoría de Kubernetes, pregunta: "¿no es lo mismo? Los dos revisan cada request". ¿Qué le responderías, sin usar la palabra "preventivo" ni "detectivo"?
Ver solución
Una explicación razonable: "Los dos ven cada request, es cierto, pero en momentos distintos del proceso y con capacidades distintas. Un log de auditoría se activa después de que kube-apiserver ya decidió qué hacer con el request —solo describe lo que pasó, no puede cambiar el resultado—. Un admission controller se activa antes de esa decisión final, y su respuesta (allowed: true o false) es la que determina si el objeto llega a existir. Si el admission controller dice que no, no hay nada que auditar sobre 'un Pod que se creó', porque ese Pod nunca se creó." Si tu explicación distingue "puede cambiar el resultado" de "solo describe el resultado", tienes el criterio correcto sin necesitar el vocabulario formal.
Ejercicio 3 — Predice qué pasaría si Gatekeeper mismo se cayera. Si el Deployment de gatekeeper-controller-manager (que vas a instalar en la lección 4) dejara de responder —por ejemplo, sus tres réplicas se cayeran a la vez—, ¿qué pasaría con un kubectl apply de un Pod nuevo contra andes-cargo-cluster? Piensa en el campo failurePolicy de un ValidatingWebhookConfiguration antes de responder.
Ver solución
Depende exactamente del valor de failurePolicy en el ValidatingWebhookConfiguration de Gatekeeper. El manifiesto oficial v3.23.0 (el mismo que vas a aplicar en la lección 4) declara el webhook principal —validation.gatekeeper.sh, el que evalúa cada Constraint— con failurePolicy: Ignore: si Gatekeeper no responde a tiempo, kube-apiserver sigue adelante como si el webhook hubiera dicho "sí", y el Pod se crea igual, sin que ninguna Constraint lo haya evaluado. Es una decisión deliberada de disponibilidad sobre seguridad: un clúster completo bloqueado porque el motor de políticas está caído sería, para la mayoría de los equipos, un riesgo operativo peor que dejar pasar temporalmente un objeto sin revisar. (El mismo manifiesto sí trae un segundo webhook, check-ignore-label.gatekeeper.sh —uno que solo verifica una etiqueta administrativa, no tus Constraint—, ese con failurePolicy: Fail; no lo confundas con el que evalúa las políticas de este módulo.) Esta es, precisamente, la razón por la que Gatekeeper se instala con tres réplicas de su controlador (lo vas a confirmar tú mismo en la lección 4): reducir al mínimo las ventanas en las que el guardrail queda, aunque sea brevemente, abierto.
Resumen y siguiente paso
Esta lección abrió, en cámara lenta, la frase que el Módulo 1 dejó pendiente: kube-apiserver procesa cada request en cuatro fases estrictas —autenticación, autorización, admission control, persistencia en etcd—, y un admission controller se para exactamente en la tercera, antes de que cualquier objeto llegue a existir de verdad. Viste el nombre exacto de las dos piezas que Gatekeeper y Kyverno registran (MutatingWebhookConfiguration y ValidatingWebhookConfiguration), y por qué esto hace que esta capa sea preventiva, en el mismo sentido exacto en que cloud-security-and-guardrails-guide ya usó esa palabra para distinguir SCPs de CloudTrail.
Antes de avanzar deberías poder: ordenar de memoria las cuatro fases del ciclo de vida de un request; explicar la diferencia entre MutatingWebhookConfiguration y ValidatingWebhookConfiguration; y predecir qué pasaría si el Deployment de un motor de políticas se cayera, según su failurePolicy.
Siguiente lección: OPA Gatekeeper, ConstraintTemplate y Constraint. Ahí vas a ver la sintaxis exacta con la que Rego —el mismo lenguaje declarativo que cloud-security-and-guardrails-guide usó con conftest— se empaqueta dentro de un CRD de Kubernetes, y la diferencia técnica precisa entre evaluar un archivo estático fuera del clúster y evaluar un objeto en vivo dentro de él.
Recursos
kubernetes-and-eks-in-production-guide(NIEVA), Módulo 1, lección 6 — la cita original sobrekube-apiservercomo única puerta de entrada, la base de esta lección.cloud-security-and-guardrails-guide(NIEVA), Módulo 7, lección 1 — el vocabulario preventivo/detectivo, retomado aquí con un nuevo ejemplo central.- Kubernetes — Controlling Access to the Kubernetes API — el ciclo de vida completo de un request, documentación oficial.
- Kubernetes — Dynamic Admission Control — referencia oficial de
MutatingWebhookConfigurationyValidatingWebhookConfiguration, las dos piezas que Gatekeeper y Kyverno registran. - Kubernetes — Admission Controllers Reference — la lista completa de admission controllers internos que
kindya trae activados por defecto.