Lesson  in  Kubernetes 101

NetworkPolicy

Por defecto, en Kubernetes cualquier Pod habla con cualquier Pod. Cierra el namespace con una política de denegación total y abre solo el camino que web necesita hasta api.

Hay una propiedad de Kubernetes que sorprende a todo el que llega desde el mundo de las redes tradicionales: cualquier Pod puede hablar con cualquier Pod. Sin cortafuegos, sin VLANs, sin listas de acceso. Un Pod comprometido en el rincón más tonto del clúster puede abrir una conexión a tu base de datos, porque la red plana es la premisa sobre la que se construye todo lo demás.

La NetworkPolicy es la herramienta para ponerle puertas a ese campo. Y funciona con la misma lógica que RBAC: nada cambia hasta que aparece la primera política, y a partir de ese momento lo que no está permitido está prohibido.

Trabaja desde la pestaña dev-machine.

Paso 1: El escenario abierto de par en par

Tres piezas: una api que hay que proteger, un web que tiene derecho a llamarla y un intruso que no. Crea escenario.yaml:

cat << 'EOF' > escenario.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: nginx
        image: ghcr.io/iximiuz/labs/nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
  - name: cliente
    image: ghcr.io/iximiuz/labs/nginx:alpine
    command: ["sh", "-c", "sleep infinity"]
---
apiVersion: v1
kind: Pod
metadata:
  name: intruso
  labels:
    app: intruso
spec:
  containers:
  - name: cliente
    image: ghcr.io/iximiuz/labs/nginx:alpine
    command: ["sh", "-c", "sleep infinity"]
EOF

Aplícalo y comprueba la situación de partida, que es la que quita el sueño:

kubectl apply -f escenario.yaml
kubectl wait --for=condition=Available deployment/api --timeout=60s
kubectl wait --for=condition=Ready pod/web pod/intruso --timeout=60s
kubectl exec web -- wget -qO- -T 3 http://api | head -4
kubectl exec intruso -- wget -qO- -T 3 http://api | head -4

Los dos entran. El intruso no tiene ningún permiso, ningún RBAC, ninguna relación con la api, y sin embargo la alcanza sin despeinarse. Nadie se lo ha permitido: es que nadie se lo ha prohibido.

Paso 2: Denegación total

La primera política de cualquier namespace serio no permite nada: lo prohíbe todo. Crea denegar-todo.yaml:

cat << 'EOF' > denegar-todo.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: denegar-todo
spec:
  podSelector: {}
  policyTypes:
  - Ingress
EOF

El YAML, explicado con preguntas y respuestas

¿Qué significa un podSelector vacío?

"Todos los Pods de este namespace". Es el mismo mecanismo de selección de siempre, pero un selector sin condiciones casa con todo. Si pusieras matchLabels: {app: api}, la política solo afectaría a la api.

¿Dónde están las reglas de esta política?

No hay. Y ahí está el truco: una NetworkPolicy que declara policyTypes: Ingress sin ninguna sección ingress significa "cero orígenes permitidos". El objeto no bloquea explícitamente nada; lo que hace es hacer que los Pods seleccionados pasen a estar gobernados por políticas, y a partir de ese momento solo se permite lo que alguna política permita.

¿Por qué solo Ingress y no Egress?

Porque son dos dimensiones independientes: Ingress controla quién puede entrar a estos Pods, Egress a dónde pueden salir. Cerrar la entrada es el primer paso y el más rentable. Cerrar la salida es más agresivo (rompe hasta el DNS si te olvidas de permitir el puerto 53 hacia CoreDNS) y suele venir después.

¿Las políticas son aditivas o se pisan?

Aditivas, exactamente como RBAC. El tráfico se permite si alguna política lo permite. No existen las reglas de denegación explícita: la denegación es el estado por defecto en cuanto un Pod queda seleccionado por al menos una política.

¿Sirven las NetworkPolicies en cualquier clúster?

Solo si el plugin de red las implementa. Igual que un Ingress sin Ingress controller es un objeto decorativo, una NetworkPolicy en un clúster cuyo CNI no la soporte se crea sin errores y no protege absolutamente nada. Es de los espejismos más peligrosos de Kubernetes, porque el objeto existe y da falsa sensación de seguridad. Este playground sí las aplica.

Aplícala y repite las dos llamadas:

kubectl apply -f denegar-todo.yaml
kubectl exec web -- wget -qO- -T 3 http://api
kubectl exec intruso -- wget -qO- -T 3 http://api

Ahora las dos se quedan colgadas hasta agotar el tiempo de espera. Fíjate en el detalle: el tráfico no se rechaza, se descarta en silencio. No hay un "conexión rehusada" limpio, hay un timeout. Cuando en producción una aplicación empiece a dar timeouts raros justo después de un despliegue, esta lección será lo primero que recuerdes.

Paso 3: Abrir solo la puerta necesaria

Con el namespace cerrado, se abre lo justo. Crea permitir-web.yaml:

cat << 'EOF' > permitir-web.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: permitir-web
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 80
EOF

El YAML, en tres preguntas

¿Cuántos selectores hay aquí y qué elige cada uno?

Dos, y confundirlos es el error número uno. El podSelector de arriba elige el destino (a quién protege esta regla: la api). El podSelector dentro de from elige el origen permitido (el web). Se leen así: "hacia los Pods app=api, permite entrada desde los Pods app=web, en el puerto TCP 80".

¿Puedo permitir el acceso desde otro namespace?

Sí, con namespaceSelector (que selecciona namespaces por sus etiquetas) y combinándolo con podSelector. Ojo con la sintaxis: dos elementos separados en la lista from son un OR, mientras que namespaceSelector y podSelector dentro del mismo elemento son un AND. Ese guion de más o de menos cambia por completo el significado de la política.

¿Y para permitir tráfico desde fuera del clúster?

Con ipBlock y un CIDR. Es la vía para dejar entrar, por ejemplo, a un balanceador que no vive en la red de Pods.

Aplícala y comprueba las dos caras:

kubectl apply -f permitir-web.yaml
kubectl exec web -- wget -qO- -T 3 http://api | head -4
kubectl exec intruso -- wget -qO- -T 3 http://api

El web entra. El intruso sigue fuera, y ahora ya no por accidente sino por diseño. No hemos tocado ni el Service, ni el Deployment, ni una sola línea de la aplicación: la red es una capa de gobierno independiente, gobernada por etiquetas, como todo en Kubernetes.

Resumen

  • Sin políticas, la red de Kubernetes es plana: todos hablan con todos.
  • Una NetworkPolicy no bloquea, selecciona: en cuanto un Pod queda seleccionado por alguna, solo se le permite lo que alguna política le permita.
  • El patrón profesional es siempre el mismo: denegar-todo primero, y luego una política por cada camino legítimo.
  • El tráfico bloqueado se descarta en silencio: se manifiesta como timeouts, no como errores de conexión.
  • Sin un CNI que las implemente, las NetworkPolicies son decoración. Compruébalo antes de confiar en ellas.
Previous lesson
Gateway API