Skip to main content

Open Policy Agent (OPA)

logo

Es una extensión que podemos instalar dentro de Kubernetes que nos permite crear políticas personalizadas.

Como vimos anteriormente, una petición primero necesita autenticarse para saber quién es, después pasar por el proceso de autorización RBAC para saber si quien hizo la petición tiene o no permiso para hacer lo solicitado y después vamos al control de admisión que es donde OPA funcionará.

Open Policy Agent (OPA) es un mecanismo de control de políticas de código abierto que permite definir, gestionar y aplicar políticas de forma centralizada en diversos sistemas, incluyendo Kubernetes. En el contexto de Kubernetes, OPA se utiliza generalmente como una solución de Policy-as-Code para definir reglas que controlan el comportamiento de recursos dentro del clúster, como Pods, Deployments, ConfigMaps, etc.

Cómo funciona OPA en Kubernetes

  1. OPA puede implementarse como un controlador de admisión dinámico en Kubernetes. Esto significa que intercepta todas las peticiones de API que llegan a Kubernetes, evaluándolas según políticas definidas antes de que los recursos sean realmente creados o modificados en el clúster.

  2. OPA recibe la solicitud de la API, evalúa si cumple con las políticas (por ejemplo, restricciones de seguridad, estándares de nomenclatura, límites de recursos), y entonces permite o bloquea la operación, devolviendo una respuesta a Kubernetes.

  3. Las políticas en OPA se escriben en un lenguaje declarativo llamado Rego. Con Rego, puedes definir expresiones lógicas para determinar si una determinada operación está permitida o denegada.

  4. Funciona con JSON/YAML. OPA no sabe qué son pods, deployments o cualquier otro recurso de Kubernetes, solo trabaja basándose en JSON o YAML.

Ejemplos de políticas incluyen:

  • Bloquear la creación de Pods que no tienen una solicitud de CPU/memoria definida.

  • Restringir el uso de imágenes de contenedor que no son de un registro privado aprobado.

  • Garantizar que todas las configuraciones de red estén siguiendo reglas específicas de seguridad.

OPA Gatekeeper vs Kyverno

OPA Gatekeeper utiliza OPA pero trae la facilidad de uso con Kubernetes. En realidad OPA Gatekeeper instala definiciones de recursos personalizados en el clúster para que podamos crear las políticas de OPA.

OPA Gatekeeper y Kyverno son dos herramientas populares de Policy-as-Code para Kubernetes, utilizadas para definir, aplicar y auditar políticas dentro de clústeres Kubernetes. Aunque ambas tienen el objetivo de gestionar políticas de seguridad y conformidad, difieren en su enfoque, lenguaje de definición de políticas, facilidad de uso y casos de uso específicos.

  • Lenguaje de las políticas:

    • OPA Gatekeeper: Utiliza el lenguaje Rego para definir políticas. Rego es un lenguaje declarativo, expresivo y potente, pero tiene una curva de aprendizaje, especialmente para quien no tiene familiaridad con programación lógica.
    • Kyverno: Utiliza YAML, que es el mismo formato utilizado para definir recursos en Kubernetes. Como la mayoría de los operadores de Kubernetes ya están familiarizados con YAML, la creación de políticas en Kyverno es generalmente más intuitiva y rápida.
  • Enfoque de políticas:

    • OPA Gatekeeper: Es un mecanismo de política genérico, no específico para Kubernetes, que puede extenderse a otros sistemas y contextos más allá de Kubernetes. Sus políticas se definen con Rego y puede utilizarse en cualquier lugar que soporte OPA.

    • Kyverno: Fue diseñado específicamente para Kubernetes. Se enfoca directamente en las necesidades de los operadores de Kubernetes, proporcionando una sintaxis YAML amigable y recursos específicos de Kubernetes, como copia de configuraciones, mutaciones y generación de recursos.

  • Recursos y funcionalidades:

    • OPA Gatekeeper:

      • Verificación de políticas (Validation): Verifica solicitudes contra políticas antes de aplicarlas.
      • Aplicación de políticas (Enforcement): Rechaza o permite requisiciones de acuerdo con las reglas definidas.
      • Auditoría: Soporta auditoría de políticas, permitiendo identificar qué recursos existentes violan las políticas.
    • Kyverno:

      • Verificación de políticas (Validation): Similar a Gatekeeper, valida las requisiciones según las reglas definidas.
      • Mutaciones (Mutation): Permite modificar recursos automáticamente (ej., añadir etiquetas, anotaciones, definir estándares).
      • Generación de recursos (Generation): Puede generar automáticamente recursos de Kubernetes, como ConfigMaps o Secrets, en respuesta a eventos.
      • Auditoría: Soporta auditoría y generación de informes de conformidad de políticas.
  • Facilidad de uso e integración:

    • OPA Gatekeeper: Requiere el aprendizaje del lenguaje Rego y puede ser más complejo de configurar y usar para nuevos usuarios. Es más flexible y genérico, pero esa flexibilidad viene con mayor complejidad.

    • Kyverno: Centrado en Kubernetes, es más fácil para equipos ya acostumbrados con YAML y Kubernetes. La integración con Kubernetes es directa, y el desarrollo de políticas puede ser más rápido y fácil debido a la familiaridad con el formato YAML.

  • Casos de uso:

    • OPA Gatekeeper: Ideal para organizaciones que desean una solución de políticas más genérica, que pueda aplicarse a múltiples sistemas más allá de Kubernetes. Utilizado cuando hay necesidad de lógica compleja y personalizada, donde la expresividad del lenguaje Rego es una ventaja.

    • Kyverno: Ideal para equipos que quieren enfocarse directamente en Kubernetes y necesitan una solución simple y fácil de implementar. Excelente para escenarios donde hay necesidad de mutación o generación automática de recursos, además de validación.

Es posible utilizar ambos al mismo tiempo aprovechando lo mejor de cada uno, pero necesitaría ser algo muy bien ejecutado.

  • Rendimiento: Cada herramienta añade un poco de sobrecarga al procesamiento de solicitudes en el clúster. Si usas ambas, es importante monitorear el impacto en el rendimiento y ajustar las políticas según sea necesario.

  • Mantenimiento: Tener dos herramientas de políticas puede aumentar la complejidad de mantenimiento y operación, ya que necesitarás monitorear y gestionar políticas en dos sistemas diferentes.

  • Conflicto de políticas: Hay potencial para conflictos si las políticas definidas en las dos herramientas se superponen o contradicen. Por lo tanto, es importante coordinar las políticas entre ambas para evitar resultados indeseados.

Instalación de OPA Gatekeeper

Los prerrequisitos para que OPA Gatekeeper funcione son que los plugins de admisión ValidatingAdmissionWebhook y MutatingAdmissionWebhook estén cargados. Usando kubeadm para crear el clúster están activados por defecto. Véase en la documentación. La gran mayoría de los clústeres ofrecidos por las nubes también poseen estos plugins ya activados.

Plugins por defecto:

  • CertificateApproval
  • CertificateSigning
  • CertificateSubjectRestriction
  • DefaultIngressClass
  • DefaultStorageClass
  • DefaultTolerationSeconds
  • LimitRanger
  • MutatingAdmissionWebhook
  • NamespaceLifecycle
  • PersistentVolumeClaimResize
  • PodSecurity
  • Priority
  • ResourceQuota
  • RuntimeClass
  • ServiceAccount
  • StorageObjectInUseProtection
  • TaintNodesByCondition
  • ValidatingAdmissionPolicy
  • ValidatingAdmissionWebhook
# Aquí solo hay plugins extras.
root@cks-master:~# k get pod -n kube-system kube-apiserver-cks-master -o yaml | grep admission
- --enable-admission-plugins=NodeRestriction

# Aunque no se muestre arriba, fueron cargados.
root@cks-master:~# kubectl logs -n kube-system kube-apiserver-kind-control-plane| grep ValidatingAdmissionWebhook
I0918 19:16:46.978587 1 plugins.go:160] Loaded 13 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority,PersistentVolumeClaimResize,RuntimeClass,CertificateApproval,CertificateSigning,ClusterTrustBundleAttest,CertificateSubjectRestriction,ValidatingAdmissionPolicy,ValidatingAdmissionWebhook,ResourceQuota.

https://open-policy-agent.github.io/gatekeeper/website/docs/install

Podemos hacer la instalación usando helm o aplicando el conjunto de manifiestos disponible.

# Versión de lanzamiento
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.17.0/deploy/gatekeeper.yaml


# O usando helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace

NAME STATUS AGE
default Active 14d
gatekeeper-system Active 2m12s
ingress-nginx Active 11d
kube-node-lease Active 14d
kube-public Active 14d
kube-system Active 14d
kubernetes-dashboard Active 11d

root@cks-master:~# k get -n gatekeeper-system pod,svc,deploy
NAME READY STATUS RESTARTS AGE
pod/gatekeeper-audit-5cf8bcb8f7-pq9tp 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-2kprv 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-846m8 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-vch4t 1/1 Running 0 3m7s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/gatekeeper-webhook-service ClusterIP 10.103.64.162 <none> 443/TCP 3m7s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/gatekeeper-audit 1/1 1 1 3m7s
deployment.apps/gatekeeper-controller-manager 3/3 3 3 3m7s

# Recursos personalizados de gatekeeper a nivel de namespace
root@cks-master:~# k api-resources --namespaced | grep gatekeeper
configs config.gatekeeper.sh/v1alpha1 true Config
constraintpodstatuses status.gatekeeper.sh/v1beta1 true ConstraintPodStatus
constrainttemplatepodstatuses status.gatekeeper.sh/v1beta1 true ConstraintTemplatePodStatus
expansiontemplatepodstatuses status.gatekeeper.sh/v1beta1 true ExpansionTemplatePodStatus
mutatorpodstatuses status.gatekeeper.sh/v1beta1 true MutatorPodStatus

# Recursos personalizados de gatekeeper a nivel de clúster
root@cks-master:~# k api-resources --namespaced=false | grep gatekeeper
expansiontemplate expansion.gatekeeper.sh/v1beta1 false ExpansionTemplate
providers externaldata.gatekeeper.sh/v1beta1 false Provider
assign mutations.gatekeeper.sh/v1 false Assign
assignimage mutations.gatekeeper.sh/v1alpha1 false AssignImage
assignmetadata mutations.gatekeeper.sh/v1 false AssignMetadata
modifyset mutations.gatekeeper.sh/v1 false ModifySet
syncsets syncset.gatekeeper.sh/v1alpha1 false SyncSet
constrainttemplates templates.gatekeeper.sh/v1 false ConstraintTemplate

Es necesario entender un poco sobre Dynamic Admission Control. Admission webhook es como un controlador de admisión. Si creamos un webhook personalizado como OPA Gatekeeper crea para nosotros, cada vez que exista una petición pasará por estos webhooks.

Kubernetes ofrece dos formas posibles para enganchar esto dentro del sistema.

  • Validating Admission Webhook que solo sirve como validación de la petición hecha y solamente puede ser aprobado o denegado.
  • Mutating admission Webhook que da la posibilidad de alterar la petición como por ejemplo añadir labels, anotaciones, número de réplicas mínimas para un deployment, etc.

Vamos a crear una regla inicial DENY ALL para todo y ver qué pasa.

Necesitamos crear una plantilla que define restricciones (constraints). Las constraints dependen de una plantilla.

Primero creamos la plantilla, en esta plantilla crearemos la definición de recurso personalizado que nos dará el kind que será usado para crear una constraint, así como los parámetros que serán definidos en la constraint. Si intentamos crear directamente la constraint tendremos un error, porque el CRD para ese kind no existe. Vamos a probarlo.

Definiendo una constraint.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAlwaysDeny # ¿De dónde viene esto? Por ahora lo estamos inventando
metadata:
name: pod-always-deny
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
message: "ACCESS DENIED!"

root@cks-master:~# vim constraint.yaml

root@cks-master:~# k apply -f constraint.yaml
error: resource mapping not found for name: "pod-always-deny" namespace: "" from "constraint.yaml": no matches for kind "K8sAlwaysDeny" in version "constraints.gatekeeper.sh/v1beta1"
ensure CRDs are installed first

Una plantilla crea la definición de recurso personalizado para las constraints. Las constraints solo nos dicen sobre qué recurso vamos a actuar y algunas cosas más, pero la lógica de violación está en la plantilla.

Ahora vamos a crear una plantilla que define una constraint.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny # Se utilizará el mismo nombre en Rego.
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny # La constraint que estamos definiendo en esta plantilla
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
message:
type: string # Observa que en la constraint tenemos el parámetro.message que se define aquí.
# Este es el bloque del lenguaje Rego usado por OPA. Aquí definimos las reglas de violación.
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 0
msg := input.parameters.message
}

En el bloque target usamos el lenguaje Rego (Lenguaje con propósito de creación de política) y tenemos la referencia al propio metadata.name de la plantilla.

En la sección violation recibimos un parámetro msg que más tarde será mostrado caso se cumpla la condición. La violación será lanzada si todas las condiciones son verdaderas. En este momento, tenemos solo una condición simple, 1 es mayor que cero, lo que significa que la condición es verdadera, no hay más condiciones, lo que significa que la violación será lanzada y todos los pods serán denegados y el mensaje será lanzado.

Si no entendiste está bien, vamos a crear y verificar.

root@cks-master:~# vim template.yaml

root@cks-master:~# cat template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny
validation:
openAPIV3Schema:
properties:
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 0
msg := input.parameters.message
}

root@cks-master:~# k apply -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8salwaysdeny created

root@cks-master:~# k get constrainttemplates
NAME AGE
k8salwaysdeny 22s

# Buscando el recurso creado
root@cks-master:~# k get K8sAlwaysDeny
No resources found

# Creando el recurso que no conseguimos crear anteriormente.
root@cks-master:~# k apply -f constraint.yaml
k8salwaysdeny.constraints.gatekeeper.sh/pod-always-deny created

root@cks-master:~# k get K8sAlwaysDeny
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
pod-always-deny deny

# Vamos a intentar crear un pod ahora.
root@cks-master:~# k run nginx --image nginx
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] ACCESS DENIED!

Solamente nuevos pods serán analizados, ya que la regla solo tiene valor en el momento que fue creada, pero no interfiere en lo que ya existe. Si un replicaset es escalado hacia arriba y necesita crear nuevos pods para un deployment no podrá o cualquier otra forma que sea.

root@cks-master:~# k get pods
NAME READY STATUS RESTARTS AGE
app 2/2 Running 0 14h

root@cks-master:~# k get pod app -o yaml | kubectl replace -f - --force
pod "app" deleted
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] ACCESS DENIED!

root@cks-master:~# k get pods
No resources found in default namespace.

Sabiendo esto podemos observar que en el bloque status tenemos 21 ocurrencias de violaciones que son pods que ya existen en el clúster teóricamente si fueran recreados no pasarían la regla.

# Total de pods que tenemos en el clúster
root@cks-master:~# k get pod -A --no-headers | wc -l
21

root@cks-master:~# k describe k8salwaysdeny pod-always-deny
Name: pod-always-deny
Namespace:
Labels: <none>
Annotations: <none>
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sAlwaysDeny
Metadata:
Creation Timestamp: 2024-08-30T14:26:51Z
Generation: 1
Resource Version: 1402472
UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Spec:
Enforcement Action: deny
Match:
Kinds:
API Groups:

Kinds:
Pod
Parameters:
Message: ACCESS DENIED!
Status:
Audit Timestamp: 2024-08-30T14:41:11Z
By Pod:
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-audit-5cf8bcb8f7-pq9tp
Observed Generation: 1
Operations:
audit
mutation-status
status
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-2kprv
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-846m8
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-vch4t
Observed Generation: 1
Operations:
mutation-webhook
webhook
Total Violations: 21
Violations:
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-metrics-scraper-5485b64c47-8jsxc
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-kong-7696bb8c88-kw4s7
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-auth-784d848dcb-zg8wq
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-api-9567bc759-zfnk6
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-scheduler-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-proxy-w2xzr
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-proxy-5mx5m
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-controller-manager-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-apiserver-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: etcd-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: coredns-7db6d8ff4d-hsmkr
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: coredns-7db6d8ff4d-7ktqv
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: canal-8nn2f
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: canal-665nh
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: calico-kube-controllers-75bdb5b75d-wfr98
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: ingress-nginx-controller-7d4db76476-xxqvt
Namespace: ingress-nginx
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-vch4t
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-846m8
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-2kprv
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-audit-5cf8bcb8f7-pq9tp
Namespace: gatekeeper-system
Version: v1
Events: <none>

Podemos hacer este análisis para ir comprobando si tendremos o no problemas en el futuro cuando definimos estas políticas en un clúster que ya esté operando.

Si cambiamos la condición 1 > 0 que en este caso es true para 1 > 2 sería false entonces todo sería permitido y la violación no ocurriría.

root@cks-master:~# vim template.yaml

root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny
validation:
openAPIV3Schema:
properties:
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 2
msg := input.parameters.message
}

# Observa arriba que cambiamos la condición para ser siempre false
root@cks-master:~# k run nginx --image nginx
pod/nginx created

# No tenemos ninguna violación más
root@cks-master:~# k describe k8salwaysdeny pod-always-deny
Name: pod-always-deny
Namespace:
Labels: <none>
Annotations: <none>
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sAlwaysDeny
Metadata:
Creation Timestamp: 2024-08-30T14:26:51Z
Generation: 1
Resource Version: 1403247
UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Spec:
Enforcement Action: deny
Match:
Kinds:
API Groups:

Kinds:
Pod
Parameters:
Message: ACCESS DENIED!
Status:
Audit Timestamp: 2024-08-30T14:49:11Z
By Pod:
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-audit-5cf8bcb8f7-pq9tp
Observed Generation: 1
Operations:
audit
mutation-status
status
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-2kprv
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-846m8
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-vch4t
Observed Generation: 1
Operations:
mutation-webhook
webhook
Total Violations: 0 # <<<<<
Events: <none>

Limpia las constraints para el próximo escenario.

root@cks-master:~# k delete -f constraint.yaml
root@cks-master:~# k delete -f template.yaml
root@cks-master:~# rm -rf constraint.yaml template.yaml

Vamos a crear una política en la que todos los namespaces necesitan tener la etiqueta cks. Vamos a seguir los pasos y explicando a lo largo del camino.

root@cks-master:~# vim template.yaml

# La idea es crear una plantilla general que requiere etiquetas de los objetos y usar esa plantilla para cosas diferentes.
root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels # El nombre del kind que será creado
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
# labels es un tipo array de strings que necesitará existir en la constraint.
labels:
type: array
items: string

# Vamos a analizar esto un poco más abajo después de la explicación.
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels

violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}

# Aquí vamos a crear dos constraints con el mismo kind una para namespaces y otra para pods que implementan lo mismo pero para objetos diferentes de kubernetes.

root@cks-master:~# vim constraint-ns.yaml
root@cks-master:~# cat constraint-ns.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["cks"]
root@cks-master:~#

root@cks-master:~# vim constraint-pod.yaml
root@cks-master:~# cat constraint-pod.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: pod-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
labels: ["cks"]

Vamos a analizar la plantilla en la parte de target.

  targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels

violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}

Nuevamente violation recibe los parámetros, en este caso msg y details donde msg será definida internamente en msg := sprintf("you must provide labels: %v", [missing]) y será missing_labels: missing donde missing será la cantidad de etiquetas que faltan.

  • input: es la variable global en Rego que contiene los datos que están siendo evaluados.
  • input.review: Dentro del contexto de Gatekeeper es el camino estándar para acceder a la petición siendo revisada, o sea, objeto sometido al controlador de admisión.
  • input.review.object: Es el recurso de kubernetes que está siendo creado, modificado o eliminado. Puede ser un pod, puede ser un namespace, puede ser cualquier recurso siendo enviado a la API de kubernetes.
  • input.review.object.metadata.labels: Es el clave-valor que estamos buscando. En este caso fue pasado [label] para obtener la lista de etiquetas.
  • En Rego label | está creando un conjunto a partir de la entrada.
  • input.parameters está buscando la lista en la constraint.

Ahora mirando las variables en cómo obtenemos los valores.

  • provided contiene el conjunto (lista) de etiquetas de la petición. Si es namespace será del objeto namespace, si es pod será etiqueta del pod.

  • required está recibiendo la lista de etiquetas de la constraint del mismo tipo de objeto.

  • missing Estamos eliminando todo el conjunto de etiquetas de la petición de las etiquetas de required. Si conseguimos eliminar todo entonces fueron definidas.

  • Por últimos usamos una función count para ver cuántas etiquetas aún falta en la condición y caso esté faltando alguna etiqueta, o sea, missing sea mayor que cero será denegado y enviado el mensaje parametrizado diciendo las etiquetas que debemos tener y cuántas faltan.

Vamos a crear y ya comprobar violaciones de una forma más simple.

root@cks-master:~# k create -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created
root@cks-master:~# k create -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-cks created
root@cks-master:~# k create -f constraint-pod.yaml
k8srequiredlabels.constraints.gatekeeper.sh/pod-must-have-cks created

# Espera un poquito que TOTAL-VIOLATIONS aparecerá.
root@cks-master:~# k get k8srequiredlabels
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
ns-must-have-cks deny 7
pod-must-have-cks deny 22

# Las violaciones están en todos los pods y en todos los namespaces porque ninguno de ellos tiene esa etiqueta definida.
root@cks-master:~# k get pod -A --no-headers | wc -l
22
root@cks-master:~# k get ns --no-headers | wc -l
7

# Si ponemos una etiqueta en un namespace será reducido? Sí. Puede ser cualquier valor para la etiqueta, ya que la regla espera que la etiqueta exista solamente.
root@cks-master:~# k label namespaces default cks=true
namespace/default labeled

root@cks-master:~# k describe ns default
Name: default
Labels: cks=true
kubernetes.io/metadata.name=default
Annotations: <none>
Status: Active

No resource quota.

No LimitRange resource.
root@cks-master:~# k get k8srequiredlabels
ns-must-have-cks pod-must-have-cks
root@cks-master:~# k get k8srequiredlabels ns-must-have-cks
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
ns-must-have-cks deny

# Vamos a forzar para que el ns esté obligado a tener dos etiquetas, cks y team.
root@cks-master:~# vim constraint-ns.yaml
root@cks-master:~# cat constraint-ns.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["cks","team"]

root@cks-master:~# k replace -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-cks replaced

root@cks-master:~# k create namespace test
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-cks] you must provide labels: {"cks", "team"}

# Ahora vamos a crear un namespace con esas etiquetas.
root@cks-master:~# k create ns test -oyaml --dry-run=client > ns.yaml
root@cks-master:~# vim ns.yaml
root@cks-master:~# cat ns.yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: test
labels:
cks: isnothard
team: devops
spec: {}
status: {}
root@cks-master:~# k apply -f ns.yaml
namespace/test created

# Probando para el pod
root@cks-master:~# k run pod --image=nginx
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-must-have-cks] you must provide labels: {"cks"}

root@cks-master:~# k run pod --image=nginx --labels cks=amazing
pod/pod created

Elimina las plantillas, constraints y vamos a hacer otro para averiguar si un pod tiene el mínimo de replica count.

root@cks-master:~# k delete -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh "ns-must-have-cks" deleted
root@cks-master:~# k delete -f constraint-pod.yaml
k8srequiredlabels.constraints.gatekeeper.sh "pod-must-have-cks" deleted
root@cks-master:~# k delete -f template.yaml
constrainttemplate.templates.gatekeeper.sh "k8srequiredlabels" deleted
root@cks-master:~# rm -rf template.yaml constraint-*

Aquí los archivos que vamos a necesitar.

root@cks-master:~# vim template.yaml
root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sminreplicacount
spec:
crd:
spec:
names:
kind: K8sMinReplicaCount
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
min:
type: integer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sminreplicacount

violation[{"msg": msg, "details": {"missing_replicas": missing}}] {
provided := input.review.object.spec.replicas
required := input.parameters.min
missing := required - provided
missing > 0
msg := sprintf("you must provide %v more replicas", [missing])
}

root@cks-master:~# vim constraint-rc.yaml
root@cks-master:~# cat constraint-rc.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMinReplicaCount
metadata:
name: deployment-must-have-min-replicas
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
min: 2
root@cks-master:~#

Es prácticamente lo mismo la única diferencia es que ahora en vez de buscar en metadata buscamos en input.review.object.spec.replicas. Como no es una lista podemos apenas comprobar ese valor con el valor de la constraint. No necesitamos usar la función count, ya que ya tenemos el número que necesitamos.

Si la cantidad de réplicas es 3, 3-2=1 que es mayor que cero. Si fuera 2 quedaría 2-2=0. Aunque parezca que 0 > 0 no satisfaría en realidad en Rego satisface ya que en ese caso consiguió atender el requisito.

root@cks-master:~# k apply -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8sminreplicacount created
root@cks-master:~# k apply -f constraint-rc.yaml
k8sminreplicacount.constraints.gatekeeper.sh/deployment-must-have-min-replicas created

root@cks-master:~# k get k8sminreplicacount deployment-must-have-min-replicas
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
deployment-must-have-min-replicas deny 8

root@cks-master:~# k create deployment nginx --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [deployment-must-have-min-replicas] you must provide 1 more replicas
root@cks-master:~# k create deployment nginx --image=nginx --replicas 2
deployment.apps/nginx created

Vale mencionar este github con algunas plantillas y políticas que vale la pena como estudio. Es un buen punto de partida.

Rego Playground

Para crear políticas este playground puede ser de gran ayuda incluyendo algunos ejemplos para kubernetes.