Módulo 4: Networking Ingress And Networkpolicy

6. `NetworkPolicy`: por defecto, todos hablan con todos — y por qué eso no dura

Descripción

La lección 2 de este módulo demostró, con evidencia real, que el CNI garantiza que cualquier Pod pueda hablarle a cualquier otro Pod, sin NAT, sin importar en qué nodo viva cada uno. Esa lección terminó con una advertencia que esta retoma de lleno: "el CNI garantiza capacidad técnica de comunicación — no dice nada sobre si esa comunicación debería existir". Hoy, sin ninguna restricción declarada, cualquier Pod de andes-cargo-cluster —incluido uno que tú (o alguien más con acceso al clúster) creara mañana, sin relación con Andes Cargo— puede hacerle curl directo a status-api-service, saltándose por completo el Ingress que las lecciones 4-5 construyeron. NetworkPolicy es el objeto de Kubernetes que cierra esa puerta abierta.

Conexión con el módulo

Esta lección es conceptual — el fundamento sobre el que se apoya la lección 7, que construye y verifica una NetworkPolicy real. También es la lección que cumple, textualmente, una promesa que otra guía de este ecosistema dejó por escrito.


La delegación textual: lo que cloud-security-and-guardrails-guide dejó fuera, a propósito

Esta guía no decidió construir NetworkPolicy por iniciativa propia — es, en parte, una frontera de scope que otra guía hermana, ya diseñada, declaró explícitamente en su propio DISEÑO.md. La cita completa, ya adelantada en la lección 1 de este módulo, merece su desarrollo completo aquí:

"EKS y runtime de contenedores (admission controllers en clúster — OPA Gatekeeper, Kyverno —, escaneo de imágenes Docker, NetworkPolicy) → kubernetes-and-eks-in-production-guide. Andes Cargo no tiene contenedores en este ecosistema... el escaneo y la firma de esta guía son de artefacto de despliegue y de IaC, no de imagen de contenedor."

cloud-security-and-guardrails-guide construye seguridad de cadena de suministro de infraestructura: SBOM, firma de artefactos con cosign, escaneo de HCL de Terraform, políticas sobre lo que un terraform plan va a crear antes de que exista. Ninguna de esas herramientas opera dentro de un clúster de Kubernetes corriendo — todas evalúan código o artefactos antes del despliegue. NetworkPolicy, en cambio, es una regla que vive dentro del clúster, evaluada en tiempo real, cada vez que un paquete de red intenta cruzar de un Pod a otro — una capa de seguridad completamente distinta, que solo tiene sentido una vez que el clúster ya existe y tiene tráfico real corriendo. Esa es, precisamente, la razón técnica de la frontera: no es que NetworkPolicy fuera menos importante para esa guía, es que pertenece a una disciplina distinta (seguridad de runtime de un clúster) de la que esa guía cubre (seguridad de cadena de suministro de infraestructura).


El modelo por defecto: sin ninguna NetworkPolicy, todo permitido

Antes de construir la solución, confirma el problema con precisión. Un namespace de Kubernetes, sin ninguna NetworkPolicy declarada, no tiene ninguna restricción de tráfico entre Pods — ni siquiera entre namespaces distintos. Un Pod cualquiera, corriendo en el namespace default, sin ninguna relación con Andes Cargo, puede hacerle curl directo a status-api-service.andes-cargo.svc.cluster.local (el mismo nombre DNS que ya conoces del Módulo 3), exactamente igual que lo haría un Pod dentro del propio namespace andes-cargo.

       EL MODELO POR DEFECTO: TODOS HABLAN CON TODOS

  namespace: default              namespace: andes-cargo
  ┌───────────────────┐           ┌──────────────────────────┐
  │  cualquier-pod       │────────►│  andes-cargo-status-api    │
  │  (sin relación con   │  curl    │  (Pod real, con datos       │
  │   Andes Cargo)        │  directo │   sensibles del negocio)   │
  └───────────────────┘           └──────────────────────────┘

  namespace: ingress-nginx        namespace: andes-cargo
  ┌───────────────────┐           ┌──────────────────────────┐
  │  ingress-nginx-       │────────►│  andes-cargo-status-api    │  ← tráfico
  │  controller           │  curl    │                             │    legítimo,
  └───────────────────┘           └──────────────────────────┘    vía Ingress

  Sin ninguna NetworkPolicy: ambas flechas de arriba
  son técnicamente indistinguibles para el clúster.

El segundo diagrama es el punto central de esta lección: el tráfico legítimo (desde ingress-nginx, siguiendo el camino que las lecciones 4-5 construyeron) y el tráfico que debería estar bloqueado (cualquier Pod, de cualquier namespace, saltándose el Ingress por completo) son técnicamente indistinguibles para el clúster de hoy. Ambos llegan por el mismo mecanismo —una conexión TCP directa a la IP del Service—, y ninguno encuentra ninguna resistencia.

Esto no es un descuido de diseño de Kubernetes — es una decisión deliberada del proyecto: el modelo de red por defecto prioriza que todo funcione sin fricción desde el primer momento (exactamente lo que hizo posible que el Módulo 2 funcionara sin que declararas ninguna regla de red). La seguridad de red es, a propósito, una capa que agregas cuando la necesitas — nunca una restricción de fábrica que tengas que desactivar primero.


NetworkPolicy: el objeto que restringe, no el que permite

Aquí es donde el nombre del objeto puede confundir: NetworkPolicy no es, por defecto, una lista de reglas de permiso — es un selector de Pods a aislar. Cuando declaras una NetworkPolicy que selecciona un conjunto de Pods (por ejemplo, todos los Pods con la etiqueta app: andes-cargo-status-api) y declara policyTypes: [Ingress], el efecto inmediato es: esos Pods dejan de aceptar tráfico entrante de cualquier tipo, salvo el que las reglas ingress de esa misma política —o de otra NetworkPolicy adicional— permitan explícitamente.

Esto tiene una consecuencia importante que vale la pena internalizar antes de la lección 7: las NetworkPolicy de Kubernetes son aditivas, nunca restan permisos entre sí. Si dos políticas distintas seleccionan el mismo Pod, el resultado es la unión de todo lo que ambas permiten —nunca la intersección—. No existe, en el modelo estándar de NetworkPolicy, una regla de "denegar explícitamente" que anule un permiso de otra política; cada política solo puede agregar una excepción al aislamiento que activó.

        EL PATRÓN deny-by-default, EN DOS POLÍTICAS

  Política 1: default-deny-ingress
  ┌────────────────────────────────────────┐
  │  podSelector: {}   (todos los Pods       │
  │                      del namespace)        │
  │  policyTypes: [Ingress]                    │
  │  (sin ninguna regla "from" declarada)      │
  └────────────────────────────────────────┘
           │
           ▼  aísla TODO el tráfico entrante del namespace

  Política 2: allow-from-ingress-nginx
  ┌────────────────────────────────────────┐
  │  podSelector: app=andes-cargo-status-api │
  │  policyTypes: [Ingress]                    │
  │  from: namespace ingress-nginx             │
  │  ports: 8080/TCP                            │
  └────────────────────────────────────────┘
           │
           ▼  agrega UNA excepción: solo ingress-nginx,
              solo al puerto 8080, solo hacia esos Pods

  Resultado combinado: TODO bloqueado, EXCEPTO
  ingress-nginx → andes-cargo-status-api:8080

Este patrón de dos políticas —una que aísla todo (podSelector: {}, que selecciona todos los Pods del namespace), otra que agrega la excepción exacta que necesitas— es, textualmente, el patrón deny-by-default que la lección 7 de este módulo construye contra andes-cargo-cluster de verdad.


Por qué "todos hablan con todos" no dura en un clúster de producción

El título de esta lección no es retórico: el modelo por defecto de Kubernetes es exactamente correcto para un laboratorio de aprendizaje de una sola persona —es lo que permitió que los Módulos 1-3 de esta guía funcionaran sin que tuvieras que pensar en reglas de red ni una sola vez—, pero deja de ser aceptable en cuanto un clúster tiene más de un equipo, más de un cliente, o datos que uno de esos Pods no debería poder leer. Tres razones concretas, cada una con un caso real:

  • Multi-tenancy dentro del mismo clúster. Un clúster real de producción rara vez sirve a un solo equipo — distintos namespaces suelen pertenecer a distintos equipos, o incluso a distintos clientes de una plataforma. Sin NetworkPolicy, el equipo A puede alcanzar directamente cualquier servicio interno del equipo B, sin ninguna revisión.
  • Reducir la superficie de un compromiso. Si un atacante logra ejecutar código dentro de un Pod (por una vulnerabilidad de una dependencia, por ejemplo — el mismo tipo de hallazgo que trivy image va a detectar en el Módulo 6 de esta guía), el modelo por defecto le da, de regalo, la capacidad de moverse lateralmente hacia cualquier otro Pod del clúster. NetworkPolicy reduce ese movimiento lateral al mínimo posible: solo hacia lo que una regla explícita permite.
  • El principio de menor privilegio, aplicado a la red. Es el mismo principio que ya conoces de IAM (aws-core-services-guide) y de RBAC de Kubernetes, aplicado a una capa distinta: nadie debería tener más acceso del que necesita, y "acceso de red" no es una excepción a esa regla solo porque Kubernetes no lo restrinja por defecto.

Analogía: el edificio donde ninguna puerta interior abre sola

Retomando la analogía de la lección 1: si Ingress es la recepción única del edificio, NetworkPolicy es la política de acceso a las puertas interiores — y hoy, sin ninguna política declarada, todas esas puertas están sin llave. Cualquier persona que ya esté dentro del edificio (cualquier Pod, de cualquier piso/namespace) puede caminar hasta la oficina de Andes Cargo y entrar directo, sin pasar por la recepción, sin que nadie se lo impida. Un edificio de oficinas real nunca opera así: cada puerta interior tiene una cerradura que, por defecto, no se abre para nadie —el equivalente de default-deny—, y solo las personas con la tarjeta de acceso correcta (una regla explícita de NetworkPolicy) pueden entrar a cada oficina específica. La lección 7 instala exactamente ese sistema de cerraduras, con evidencia real de que funciona.


Errores comunes

Pensar que NetworkPolicy es una capacidad de seguridad de Kubernetes en sí, disponible automáticamente (el error conceptual más importante de esta lección, que la lección 7 va a poner a prueba con evidencia real). Qué pasa: alguien asume que, porque el objeto NetworkPolicy existe en la API de Kubernetes, cualquier clúster lo hace cumplir automáticamente en cuanto lo aplicas. Cómo detectarlo: si nunca te has preguntado quién, exactamente, es responsable de leer una NetworkPolicy y bloquear el tráfico que no cumple. Cómo corregirlo: NetworkPolicy, igual que Ingress (lección 3), es solo una declaración — el objeto se guarda perfectamente en etcd sin importar si algo lo hace cumplir. Quien realmente bloquea o permite el tráfico es, otra vez, el CNI —el mismo kindnet de la lección 2—, y no todos los CNI implementan esta capacidad. La lección 7 investiga esto a fondo, contra tu propio clúster.

Confundir "aislar" con "bloquear todo, incluida la respuesta a solicitudes ya permitidas" (de configuración, relevante en la lección 7). Qué pasa: alguien asume que una NetworkPolicy con policyTypes: [Ingress] también bloquea el tráfico de salida (egress) del mismo Pod — por ejemplo, el intento de conexión que andes-cargo-status-api hace hacia el endpoint de LocalStack del ConfigMap (que nunca responde, porque ningún módulo de esta guía instala LocalStack). Cómo detectarlo: si esperas que una política de tipo Ingress afecte la capacidad de un Pod para iniciar conexiones hacia afuera. Cómo corregirlo: Ingress y Egress son policyTypes independientes — una NetworkPolicy que solo declara Ingress no toca el tráfico saliente en absoluto, sin importar qué tan restrictivas sean sus reglas de entrada. Este módulo no construye ninguna política de Egress — el alcance completo de esta guía se limita a controlar quién puede entrar.

Asumir que dos políticas que seleccionan el mismo Pod se combinan restando permisos (conceptual, ya explicado arriba, vale la pena repetirlo como error común porque es contraintuitivo). Qué pasa: alguien declara una NetworkPolicy restrictiva y otra más permisiva sobre el mismo conjunto de Pods, esperando que la más restrictiva "gane". Cómo detectarlo: si tu mental model incluye la idea de "prioridad" entre políticas de NetworkPolicy, como existiría en un firewall tradicional con reglas ordenadas. Cómo corregirlo: no existe prioridad ni orden — el resultado siempre es la unión de todo lo que cualquier política aplicable permite. Si necesitas que una regla "gane" sobre otra de forma más sofisticada (por ejemplo, denegar explícitamente algo que otra regla permitiría), eso requiere un motor de políticas más avanzado que el NetworkPolicy estándar de Kubernetes —Calico, nombrado por contraste en la lección 7, ofrece esa capacidad extendida bajo el nombre GlobalNetworkPolicy, fuera del alcance de esta guía.


Ejercicios

Ejercicio 1 — Cita la delegación sin volver a leerla. Sin volver a la sección correspondiente, escribe de memoria: (a) qué guía delegó NetworkPolicy a esta guía; (b) qué tipo de seguridad construye esa guía en su lugar; (c) por qué esas dos capas son distintas, no redundantes.

Ver solución

(a) cloud-security-and-guardrails-guide. (b) Seguridad de cadena de suministro de infraestructura: SBOM, firma de artefactos, escaneo de HCL de Terraform, políticas sobre un terraform plan. (c) Esa guía evalúa código/artefactos antes del despliegue, fuera de cualquier clúster corriendo; NetworkPolicy es una regla evaluada en tiempo real, dentro de un clúster ya corriendo, cada vez que un paquete de red intenta cruzar entre Pods — una disciplina de seguridad de runtime, no de cadena de suministro.

Ejercicio 2 — Explica por qué dos políticas nunca se restan. Un colega, acostumbrado a reglas de firewall tradicionales con prioridad y orden, pregunta por qué no puede simplemente agregar una segunda NetworkPolicy más restrictiva para anular una regla de permiso que ya existe. Explícale, en dos o tres frases, por qué eso no funciona así en Kubernetes.

Ver solución

Una explicación razonable: "Las NetworkPolicy de Kubernetes no tienen orden ni prioridad entre sí — cuando más de una selecciona el mismo Pod, el resultado es la unión de todo lo que cualquiera de ellas permite, nunca la intersección. No existe una regla de 'denegar explícitamente' que le gane a una regla de permiso de otra política — cada política solo puede sumar excepciones al aislamiento, nunca restarlas. Si necesitas ese tipo de prioridad, hace falta un motor de políticas más avanzado que el estándar de Kubernetes."

Ejercicio 3 — Diseña, sin ejecutarlo, el patrón de dos políticas para un caso nuevo. Andes Cargo agrega, en el futuro, un segundo servicio interno (manifest-processor-api) que solo debería recibir tráfico de andes-cargo-status-api, nunca de ingress-nginx ni de ningún otro Pod. Describe, sin escribir YAML, qué dos políticas necesitarías y qué podSelector/from tendría cada una.

Ver solución

Necesitarías, primero, una política default-deny-ingress que aísle todo el tráfico entrante del namespace (podSelector: {}, policyTypes: [Ingress], sin ninguna regla from) — la misma primera política del patrón de esta lección, que probablemente ya cubriría a manifest-processor-api si vive en el mismo namespace andes-cargo. Segundo, una política nueva que seleccione específicamente los Pods de manifest-processor-api (podSelector: app=manifest-processor-api) y declare una regla from que apunte, no a un namespace completo como hace allow-from-ingress-nginx, sino a los Pods con la etiqueta app: andes-cargo-status-api dentro del mismo namespace (podSelector anidado dentro de from, en vez de namespaceSelector) — permitiendo tráfico solo desde ese servicio específico, y de ningún otro, ni siquiera de ingress-nginx.


Resumen y siguiente paso

Esta lección explicó el problema que NetworkPolicy resuelve, sin ejecutar ningún comando: por defecto, cualquier Pod de cualquier namespace puede hablarle directo a status-api-service, saltándose por completo el Ingress que las lecciones 4-5 construyeron — el tráfico legítimo y el ilegítimo son, hoy, técnicamente indistinguibles. También retomó la delegación textual de cloud-security-and-guardrails-guide, y explicó el patrón de dos políticas —default-deny más una excepción explícita— que hace posible el modelo deny-by-default, incluida la regla contraintuitiva de que las políticas nunca se restan entre sí, solo se suman.

Antes de avanzar deberías poder: explicar por qué el modelo de red por defecto de Kubernetes permite todo el tráfico; describir el patrón de dos políticas (default-deny + excepción) sin mirar el diagrama; y citar la delegación de cloud-security-and-guardrails-guide de memoria.

Siguiente lección: manos a la obra, NetworkPolicy real sobre andes-cargo. Ahí construyes exactamente el patrón de esta lección contra tu propio clúster, con un hallazgo real que corrige una idea muy extendida sobre kind — verificado con curl bloqueado, y después permitido, con evidencia literal de los dos resultados.

Recursos

  1. Kubernetes — Network Policies — la especificación oficial completa de NetworkPolicy, incluido el comportamiento aditivo entre políticas múltiples que esta lección explica.
  2. cloud-security-and-guardrails-guide (NIEVA), DISEÑO.md — la fuente completa de la delegación textual citada en esta lección.
  3. Kubernetes — Declare Network Policy — tutorial oficial paso a paso del patrón default-deny que esta lección explica en teoría.
  4. kubernetes-and-eks-in-production-guide (NIEVA), Módulo 4, lección 2 — el modelo de red que hace posible que exista, en primer lugar, el problema que esta lección resuelve.