Kubernetes admission controllers
Kubernetes permite validar y modificar objetos de forma nativa con CEL, sin webhooks ni herramientas externas. Las ValidatingAdmissionPolicy rechazan recursos que no cumplen tus reglas; las MutatingAd
Hace unos días os hablaba sobre Common Expression Language (CEL) en Kubernetes.
CEL es un lenguaje de expresiones ligero creado por Google y diseñado para validaciones, evaluaciones lógicas rápidas y portabilidad. A diferencia de un lenguaje de programación completo, CEL es restringido y seguro: no permite ejecución de código arbitrario, lo que lo hace ideal para las APIs de Kubernetes.
Hoy os cuento cómo puedes utilizarlo para configurar un Validating Admission Policy o Mutating Admission Policy.
Si has trabajado con Kubernetes, es posible que esta información te resuene.
Existen herramientas como Kyverno o OPA Gatekeeper para gestionar las políticas en tu cluster kubernetes
Validating Admission Policy y Mutating Admission Policy funcionan de forma declarativa y utilizan CEL para definir sus reglas, sin necesidad de desplegar webhooks ni aplicaciones externas.
Cualquier política se ejecuta en el control-plane de nuestro cluster y podemos llegar a bloquear el propio control-plane dependiendo de las reglas definidas.
Cuando solicitamos la creación de un objeto en Kubernetes, el flujo funciona de la siguiente manera:
Cuando un cliente envía una petición al cluster, el API Server primero verifica la identidad y permisos. Si la autenticación es correcta, la petición pasa por el Mutating Admission, que puede modificar el objeto antes de continuar, y después por el Validating Admission, que evalúa la expresión CEL y toma la decisión final. Si la expresión se cumple, el objeto se persiste en etcd; si no, la petición se rechaza con un 403 Forbidden y el mensaje definido en la policy.
¡Gracias por pasarte por aquí! 🙌
Si te interesa seguir recibiendo guías prácticas sobre Kubernetes, Azure, DevOps y contenedores directamente en tu email…
Suscríbete gratis y no te pierdas ninguna novedad. 🚀
Validating Admission Policy
Este tipo de políticas nos van a permitir validar cualquier petición realizada a nuestro cluster Kubernetes, no solo por nosotros, también se incluyen cualquier llamada que pueda hacer el propio cluster o herramientas GitOps como Argo CD o Flux CD.
Para desplegar una politica y hacer uso de ella debemos crear dos objetos.
ValidatingAdmissionPolicy
Definimos la política.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-memory-requests
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: >
object.spec.template.spec.containers.all(c,
has(c.resources) &&
has(c.resources.requests) &&
has(c.resources.requests.memory)
)
message: "Todos los contenedores deben tener requests.memory definido"failurePolicy
Define qué hacer si hay un error en la política (por ejemplo, error de sintaxis CEL, error en tiempo de ejecución o configuración inválida). Valores permitidos:
Fail (valor por defecto): rechaza la petición
Ignore: continúa como si la política no existiera
matchConstraints
Especifica a qué recursos se aplica esta política. Es el filtro principal. Dentro de él, lo más usado es resourceRules: una lista de reglas que definen:
apiGroups: grupo de la API (ej. [“apps”] o [““] para core)
apiVersions: versiones (ej. [”v1”])
operations: operaciones a interceptar: CREATE, UPDATE, DELETE, *
resources: tipo de recurso (ej. [”deployments”], [”pods”]). También puedes usar namespaceSelector y objectSelector para filtrar por labels
validations
Aquí va la lógica real de validación usando CEL (Common Expression Language). Cada elemento tiene:
expression: la expresión CEL que debe evaluarse a true. Si es false, se considera fallo de validación. Ejemplo:
object.spec.replicas <= 5message: mensaje estático que se devuelve al usuario si falla
messageExpression: (opcional) expresión CEL que genera un mensaje dinámico
reason: código de razón HTTP (Invalid, Forbidden, etc.)
Otras propiedades útiles
matchConditions: filtro adicional con CEL (se evalúa antes de las validaciones). Muy útil para excluir system namespaces o recursos internos.
auditAnnotations: genera anotaciones en los eventos de auditoría cuando la validación pasa.
variables: define variables reutilizables en CEL para no repetir expresiones complejas.
paramKind: si quieres que la política sea parametrizable (ej. el límite de réplicas venga de un CRD).
Un ejemplo más completo con todas las propiedades disponibles:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: enforce-production-deployment-standards.example.com
spec:
# 1. Qué hacer si hay un error en la política (sintaxis CEL, runtime error, etc.)
failurePolicy: Fail
# 2. Filtros iniciales: a qué recursos se aplica la política
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
# 3. Condiciones adicionales con CEL (se evalúan ANTES de las validaciones)
matchConditions:
- name: "only-production-namespaces"
expression: "request.namespace.startsWith('prod-') || request.namespace == 'production'"
- name: "exclude-system-resources"
expression: "!(request.resource.group == 'coordination.k8s.io' && request.resource.resource == 'leases')"
# 4. Parámetros (hace la política reutilizable y configurable)
paramKind:
apiVersion: "rules.example.com/v1"
kind: "DeploymentPolicyConfig"
# El parámetro se referenciará después en el Binding con paramRef
# 5. Variables reutilizables en CEL (mejora la legibilidad y evita duplicación)
variables:
- name: "hasReadOnlyRootFS"
expression: "object.spec.template.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.readOnlyRootFilesystem) && c.securityContext.readOnlyRootFilesystem == true)"
- name: "approvedRegistries"
expression: "params.allowedRegistries.split(',').map(r, r.trim())"
# 6. Las validaciones reales (lógica principal)
validations:
- name: "minimum-replicas"
expression: "object.spec.replicas >= params.minReplicas"
messageExpression: "'Deployment debe tener al menos ' + string(params.minReplicas) + ' réplicas en producción.'"
reason: Invalid
- name: "readonly-root-filesystem"
expression: "variables.hasReadOnlyRootFS"
message: "Todos los contenedores deben tener securityContext.readOnlyRootFilesystem = true por seguridad"
reason: Forbidden
- name: "approved-image-registries"
expression: >
object.spec.template.spec.containers.all(c,
variables.approvedRegistries.exists(r, c.image.startsWith(r))
)
message: "Todas las imágenes deben provenir de registros aprobados"
reason: Forbidden
# 7. Anotaciones para auditoría (útil para compliance y trazabilidad)
auditAnnotations:
- key: "policy.enforced"
valueExpression: "'true'"
- key: "policy.minReplicas"
valueExpression: "string(params.minReplicas)"
- key: "validated-by"
valueExpression: "'validating-admission-policy'"ValidatingAdmissionPolicyBinding
Enlazamos la política creada con uno o varios objetos de Kubernetes, en este caso queremos aplicarla únicamente a los recursos que se ejecutan en un namespace por la label env: production.
# Enlazar la política a un namespace (o a todo el cluster)
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-memory-requests-binding
spec:
policyName: require-memory-requests
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
env: productionpolicyName
Nombre exacto de la ValidatingAdmissionPolicy que se va a aplicar.
validationActions
Cómo se aplica el fallo de validación. No puedes combinar Deny + Warn.
Deny: rechaza la petición
Warn: avisa pero permite (emite un warning visible con
kubectl)Audit: solo registra en el audit log
matchResources
Igual que en la política, pero aquí puedes afinar aún más el alcance (por ejemplo, aplicar solo a ciertos namespaces con namespaceSelector). Si lo omites, se aplica a todos los recursos que coincidan con los criterios de la política.
Mutating Admission Policy
Esta funcionalidad se encuentra actualmente en beta y está previsto que pase a GA en Kubernetes 1.36.
Este tipo de políticas nos van a permitir modificar el contenido de un objeto de Kubernetes (ej. añadir un label).
MutatingAdmissionPolicy
Definimos la política.
# 1. Definir la política mutante
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicy
metadata:
name: add-managed-by-label
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
metadata: ObjectMeta{
labels: {"managed-by": "platform-team"}
}
}
failurePolicy
Define el comportamiento cuando ocurre un error en la política (error de sintaxis CEL, error en tiempo de ejecución, etc.). Valores permitidos:
Fail (por defecto): rechaza la creación/actualización del objeto
Ignore: continúa la operación ignorando esta política
matchConstraints
Es el filtro principal. Define a qué recursos y operaciones se aplica esta política de mutación.
apiGroups: grupo de la API (ej. [“apps”] o [““] para core)
apiVersions: versiones (ej. [”v1”])
operations: operaciones a interceptar: CREATE, UPDATE, DELETE, *
resources: tipo de recurso (ej. [”deployments”], [”pods”]). También puedes usar namespaceSelector y objectSelector para filtrar por labels
mutations
Es una lista de mutaciones que se aplicarán al objeto. Cada elemento de la lista representa una mutación independiente.
patchType: Indica el tipo de parche que se va a aplicar. Dos valores posibles:
ApplyConfiguration: Usa un estilo declarativo similar al Server-Side Apply. Es más seguro y legible
JSONPatch: Permite usar operaciones clásicas de JSON Patch (add, remove, replace, etc.)
applyConfiguration.expression (cuando patchType: ApplyConfiguration) Expresión CEL que devuelve un objeto con la configuración que se quiere fusionar (merge) en el objeto original. En el ejemplo:
Object{
metadata: ObjectMeta{
labels: {"managed-by": "platform-team"}
}
}Otras propiedades importantes
reinvocationPolicy Controla si la política debe volver a ejecutarse después de que otras mutaciones hayan modificado el objeto. Valores:
IfNeeded: Se ejecuta en caso de que el objeto haya sido modificado por otra política
Never (valor por defecto): la política no se ejecuta si el objeto ya ha sido modificado por otra política
matchConditions (lista) Filtros adicionales escritos en CEL que se evalúan antes de aplicar las mutaciones. Muy útil para añadir condiciones complejas, por ejemplo:
No aplicar la mutación si el Pod ya tiene cierta etiqueta
Excluir Pods del sistema (kube-system, kube-node-lease, etc.
Un ejemplo más completo con todas las propiedades disponibles:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingAdmissionPolicy
metadata:
name: add-platform-labels
spec:
failurePolicy: Ignore
reinvocationPolicy: IfNeeded
# 1. matchConstraints → ¿A qué recursos se aplica?
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
# 2. matchConditions → Condiciones adicionales con CEL (opcional pero muy útil)
matchConditions:
- name: "skip-system-namespaces"
expression: "!(request.namespace in ['kube-system', 'kube-node-lease', 'kube-public'])"
# 3. mutations → Aquí definimos los cambios
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
metadata: ObjectMeta{
labels: {
"managed-by": "platform-team",
"environment": "production"
},
annotations: {
"mutated-by": "mutating-admission-policy",
"mutation-timestamp": string(request.time)
}
}
}MutatingAdmissionPolicyBinding
Enlazamos la política creada con uno o varios objetos de Kubernetes, en este caso queremos aplicarla únicamente a los recursos que se ejecutan en un namespace por la label env: production o staging.
# 2. MutatingAdmissionPolicyBinding - Enlace de la política
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingAdmissionPolicyBinding
metadata:
name: add-platform-labels-binding
spec:
policyName: add-platform-labels
# Podemos restringir a qué namespaces se aplica
matchResources:
namespaceSelector:
matchExpressions:
- key: env
operator: In
values: ["production", "staging"]policyName
Nombre exacto de la ValidatingAdmissionPolicy que se va a aplicar.
matchResources
Igual que en la política, pero aquí puedes afinar aún más el alcance (por ejemplo, aplicar solo a ciertos namespaces con namespaceSelector). Si lo omites, se aplica a todos los recursos que coincidan con los criterios de la política.
Si este post te ha ayudado o aclarado algo, me harías muy feliz con un ❤️
Y si crees que le puede servir a alguien más… ¡compártelo! 🙌
Conclusión
Kubernetes está apostando fuertemente por incorporar de forma nativa capacidades avanzadas de admisión, reduciendo la dependencia de herramientas externas como Kyverno o Gatekeeper para casos de uso habituales. Esto permite simplificar el mantenimiento de la plataforma y disminuir su complejidad.
Por otro lado, la adopción de Common Expression Language (CEL) como lenguaje de expresiones parece consolidarse, y no sería extraño que termine convirtiéndose en un estándar dentro del ecosistema.
Validar y mutar objetos de manera declarativa es fundamental para mantener un clúster consistente, seguro y alineado con las configuraciones deseadas, lo que finalmente nos facilita el trabajo diario y nos permite centrarnos en entregar valor real.
Referencias
https://github.com/kubernetes/enhancements/issues/3962
https://github.com/kubernetes/kubernetes/issues/122936
https://github.com/kubernetes/kubernetes/pull/136039
https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/
https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/

