Instalación de Open Policy Agent Gatekeeper para Kubernetes
Open Policy Agent (OPA) Gatekeeper es un controlador de admisión para Kubernetes que permite definir y aplicar políticas de seguridad y gobernanza de forma declarativa usando el lenguaje Rego, garantizando que todos los recursos que se crean en el clúster cumplan con las políticas definidas antes de ser aceptados. A diferencia de PodSecurityPolicy (deprecado), Gatekeeper es extensible y permite crear políticas personalizadas para cualquier tipo de recurso Kubernetes. Esta guía cubre la instalación y configuración de OPA Gatekeeper.
Requisitos Previos
- Clúster Kubernetes 1.25+
- kubectl configurado con acceso de administrador al clúster
- Helm 3 instalado
- Comprensión básica de recursos Kubernetes (Pods, Deployments, etc.)
Instalación de Gatekeeper
# Método 1: Usando Helm (recomendado para producción)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
# Instalar Gatekeeper en el namespace dedicado
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set replicas=2 \
--set controllerManager.resources.limits.memory=512Mi \
--set audit.resources.limits.memory=512Mi \
--set auditInterval=30 \
--set constraintViolationsLimit=20
# Método 2: Instalación directa con kubectl
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
# Verificar que los pods están en ejecución
kubectl get pods -n gatekeeper-system
kubectl rollout status deployment gatekeeper-controller-manager -n gatekeeper-system
# Verificar los CRDs instalados por Gatekeeper
kubectl get crd | grep gatekeeper
Constraint Templates
Un ConstraintTemplate define una política reutilizable en Rego. Actúa como una plantilla de la que se pueden crear múltiples instancias con diferentes configuraciones:
# Ejemplo: política que requiere límites de recursos en todos los contenedores
cat > /tmp/required-resource-limits.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresourcelimits
annotations:
description: "Requiere que todos los contenedores definan límites de CPU y memoria"
spec:
crd:
spec:
names:
kind: K8sRequiredResourceLimits
validation:
openAPIV3Schema:
properties:
containers:
type: array
items:
type: string
description: "Lista de contenedores que se pueden excluir de la política"
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresourcelimits
violation[{"msg": msg}] {
# Obtener todos los contenedores del pod (incluye initContainers)
container := input_containers[_]
# Verificar que no está en la lista de exclusiones
not container.name in input.parameters.containers
# Comprobar que el contenedor tiene límites definidos
not container.resources.limits
msg := sprintf(
"El contenedor '%v' no tiene límites de recursos definidos. Añadir resources.limits.cpu y resources.limits.memory",
[container.name]
)
}
violation[{"msg": msg}] {
container := input_containers[_]
not container.name in input.parameters.containers
not container.resources.limits.memory
msg := sprintf(
"El contenedor '%v' no tiene límite de memoria (resources.limits.memory)",
[container.name]
)
}
violation[{"msg": msg}] {
container := input_containers[_]
not container.name in input.parameters.containers
not container.resources.limits.cpu
msg := sprintf(
"El contenedor '%v' no tiene límite de CPU (resources.limits.cpu)",
[container.name]
)
}
# Función auxiliar: obtener todos los contenedores del pod
input_containers[c] {
c := input.review.object.spec.containers[_]
}
input_containers[c] {
c := input.review.object.spec.initContainers[_]
}
EOF
kubectl apply -f /tmp/required-resource-limits.yaml
# Verificar que el template se creó correctamente
kubectl get constrainttemplate k8srequiredresourcelimits
Constraints (Instancias de Política)
Una vez creado el template, se crean Constraints para aplicar la política en namespaces específicos:
# Aplicar la política de límites de recursos a todos los namespaces de producción
cat > /tmp/constraint-resource-limits-prod.yaml << 'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResourceLimits
metadata:
name: require-resource-limits-production
spec:
# Modo de aplicación: Deny (bloquea) o Warn (solo avisa)
enforcementAction: deny
# Ámbito: a qué recursos se aplica esta constraint
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
# Aplicar solo en namespaces de producción
namespaceSelector:
matchLabels:
environment: production
# Excluir namespaces del sistema
excludedNamespaces:
- kube-system
- gatekeeper-system
- monitoring
# Parámetros de la política (definidos en el ConstraintTemplate)
parameters:
containers:
- "istio-proxy" # Excluir el sidecar de Istio
- "vault-agent" # Excluir el agente de Vault
EOF
kubectl apply -f /tmp/constraint-resource-limits-prod.yaml
# Verificar que la constraint se creó
kubectl get k8srequiredresourcelimits
kubectl describe k8srequiredresourcelimits require-resource-limits-production
Políticas de Seguridad Comunes
# 1. Política: prohibir contenedores privilegiados
cat > /tmp/no-privileged.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snoprivilegedcontainer
spec:
crd:
spec:
names:
kind: K8sNoPrivilegedContainer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snoprivilegedcontainer
violation[{"msg": msg}] {
c := input_containers[_]
c.securityContext.privileged
msg := sprintf("Contenedor privilegiado no permitido: %v", [c.name])
}
input_containers[c] { c := input.review.object.spec.containers[_] }
input_containers[c] { c := input.review.object.spec.initContainers[_] }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoPrivilegedContainer
metadata:
name: no-privileged-containers
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]
EOF
kubectl apply -f /tmp/no-privileged.yaml
# 2. Política: requerir etiquetas obligatorias en todos los recursos
cat > /tmp/required-labels.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
required_label := input.parameters.labels[_]
not input.review.object.metadata.labels[required_label]
msg := sprintf("Etiqueta obligatoria faltante: %v", [required_label])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-and-app-labels
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]
parameters:
labels:
- "app.kubernetes.io/name"
- "app.kubernetes.io/team"
- "app.kubernetes.io/version"
EOF
kubectl apply -f /tmp/required-labels.yaml
# 3. Política: solo imágenes de registros autorizados
cat > /tmp/allowed-registries.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
properties:
registries:
type: array
items: { type: string }
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not any_registry_match(container.image)
msg := sprintf("Imagen '%v' no proviene de un registro autorizado", [container.image])
}
any_registry_match(image) {
registry := input.parameters.registries[_]
startswith(image, registry)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: only-approved-registries
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]
parameters:
registries:
- "registry.empresa.com/"
- "docker.io/empresa-oficial/"
- "ghcr.io/empresa/"
EOF
kubectl apply -f /tmp/allowed-registries.yaml
Modo de Auditoría
El modo de auditoría permite evaluar políticas sin bloquear recursos existentes:
# Cambiar una constraint a modo "warn" para empezar sin bloquear
kubectl patch k8srequiredresourcelimits require-resource-limits-production \
--type merge \
-p '{"spec":{"enforcementAction":"warn"}}'
# Ver violaciones de políticas detectadas en el clúster
kubectl get k8srequiredresourcelimits -o json | \
python3 -m json.tool | grep -A5 "violations"
# Ver todas las violaciones de todas las políticas
kubectl get constraints -o json | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data.get('items', []):
name = item['metadata']['name']
violations = item.get('status', {}).get('violations', [])
if violations:
print(f'Política: {name} - {len(violations)} violaciones')
for v in violations[:3]:
print(f' - {v[\"kind\"]}/{v[\"name\"]} en {v[\"namespace\"]}: {v[\"message\"]}')
"
# Activar la aplicación estricta después de revisar las violaciones
kubectl patch k8srequiredresourcelimits require-resource-limits-production \
--type merge \
-p '{"spec":{"enforcementAction":"deny"}}'
Verificación de Imágenes
# Probar que una política rechaza recursos incorrectos
cat > /tmp/test-no-limits.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: test-sin-limites
namespace: produccion
labels:
app.kubernetes.io/name: test
app.kubernetes.io/team: devops
app.kubernetes.io/version: "1.0"
spec:
containers:
- name: app
image: registry.empresa.com/nginx:latest
# Sin resources.limits - debe ser rechazado
EOF
kubectl apply -f /tmp/test-no-limits.yaml
# Debe mostrar: Error from server ([require-resource-limits-production] El contenedor 'app' no tiene límites...)
# Recurso correcto que debe ser aceptado
cat > /tmp/test-con-limites.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: test-con-limites
namespace: produccion
labels:
app.kubernetes.io/name: test
app.kubernetes.io/team: devops
app.kubernetes.io/version: "1.0"
spec:
containers:
- name: app
image: registry.empresa.com/nginx:latest
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
EOF
kubectl apply -f /tmp/test-con-limites.yaml # Debe funcionar correctamente
Solución de Problemas
# Ver los logs de Gatekeeper
kubectl logs -n gatekeeper-system -l control-plane=controller-manager -f
# Estado de un ConstraintTemplate (ver si tuvo errores de compilación Rego)
kubectl describe constrainttemplate k8srequiredresourcelimits | grep -A10 "Status"
# Estado de una Constraint (ver violaciones detectadas)
kubectl describe k8srequiredresourcelimits require-resource-limits-production
# Probar una política Rego sin desplegarla (usando opa eval)
docker run --rm -v $(pwd):/data openpolicyagent/opa:latest eval \
--data /data/policy.rego \
--input /data/test-input.json \
"data.k8srequiredresourcelimits.violation"
# Gatekeeper en modo seco (solo logging, sin bloquear)
# En la instalación de Helm, usar:
# --set validatingWebhookFailurePolicy=Ignore
# Ver webhooks de admission configurados por Gatekeeper
kubectl get validatingwebhookconfigurations | grep gatekeeper
kubectl describe validatingwebhookconfigurations gatekeeper-validating-webhook-configuration
Conclusión
OPA Gatekeeper convierte las políticas de seguridad de Kubernetes en código versionable y auditable, eliminando la dependencia de convenciones manuales y procesos de revisión informales para garantizar el cumplimiento normativo. El uso del modo de auditoría antes de aplicar políticas en modo deny permite una adopción gradual sin interrupciones, y la biblioteca de políticas de la comunidad (Gatekeeper Policy Library) ofrece un punto de partida sólido para las políticas de seguridad más comunes en Kubernetes.


