Skip to content

feat: restringir el ingress al Postgres con una allowlist explícita - #133

Merged
az-adhoc merged 2 commits into
mainfrom
fix/networkpolicy-ingress-pg
Aug 10, 2026
Merged

feat: restringir el ingress al Postgres con una allowlist explícita#133
az-adhoc merged 2 commits into
mainfrom
fix/networkpolicy-ingress-pg

Conversation

@az-adhoc

@az-adhoc az-adhoc commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Cierra el frente de NetworkPolicies del chart. Cambia comportamiento de red: leer la sección de riesgo.

El estado actual

La NetworkPolicy del CNPG nunca funcionó. Su selector se armaba con adhoc-odoo.fullname (<release>-adhoc-odoo-pg) mientras el operador etiqueta los pods con el nombre del CR Cluster (<release>-pg). No seleccionaba ningún pod en ninguna de las 327 bases, así que el Postgres queda hoy alcanzable en el 5432 desde cualquier pod de cualquier namespace del cluster.

El riesgo que hay que entender antes de mergear

En un namespace sin ninguna policy efectiva, la primera que matchea convierte el ingress de "todo permitido" a "denegado lo no listado". No alcanza con arreglar el selector: la allowlist tiene que cubrir todo el tráfico legítimo hacia la base, no solo el de Odoo.

Por eso esto se validó aplicándolo en una base de test real, no solo renderizando.

Validación empírica

Policy aplicada en un namespace de test con Odoo y Postgres vivos, midiendo la conectividad desde cada origen:

Origen 5432 9187 8000
Odoo del propio ns ✅ conecta
Postgres de otro ns 🚫 timeout
ns monitoring 🚫 timeout ✅ conecta 🚫 timeout
ns cnpg-system 🚫 timeout 🚫 timeout ✅ conecta

Cada excepción abre solo su puerto y solo desde su namespace. Y con la policy activa:

  • El pod siguió Ready y sin restarts: las probes del kubelet (httpGet al 8000 para /healthz, /readyz, /startupz) no las filtra NetworkPolicy, así que no hace falta listarlas. Esto se verificó, no se asumió — si se bloquearan, CNPG reiniciaría el pod en loop.
  • El operador siguió reportando Cluster in healthy state.
  • Odoo siguió conectando normalmente.

Nota metodológica: una primera prueba negativa desde el pod de Odoo dio falso "conecta" en todos los puertos. El motivo es que 9187 y 8000 pasan por el sidecar de Istio, que acepta el connect() localmente y nunca sale del pod. La prueba válida se hizo desde pods sin sidecar.

Se borra allow-prometheus

Tenía el mismo bug de naming. No se arregla: con el selector corregido capturaría los cinco pods del release permitiendo solo monitoring en 9101/9113/9114, o sea cortando el tráfico web y el 5432. Hoy no protege nada — sin default-deny en el namespace, el PodMonitor scrapea igual.

Fixture de CI

Se suma ci/selectors-values.yaml, que enciende cloudNativePG y monitoring. Los values por defecto los dejan apagados y por eso ningún render de CI ejercitaba estos recursos — la razón por la que el bug sobrevivió sin que nadie lo viera. Con el fixture, el chequeo de selectores cubre estos casos de acá en adelante.

Test plan

  • Chequeo de selectores en verde con el fixture (antes reportaba los dos casos).
  • helm lint + helm template sobre los 14 charts y todos sus fixtures, en verde. pre-commit en verde.
  • Validación en base de test con la matriz de arriba; policy de prueba eliminada y el namespace verificado de vuelta en su estado original.

Rollout

El recurso cambia de nombre (allow-same-namespace<fullname>-pg-ingress), así que Helm borra el huérfano y crea el nuevo. Conviene ir por olas, no las 327 de una, verificando en cada una que cnpg_collector_up siga en 1 y que el operador no reporte degradación.

La NetworkPolicy del CNPG nunca funcionó: su selector se armaba con
adhoc-odoo.fullname ("<release>-adhoc-odoo-pg") mientras el operador etiqueta los pods
con el nombre del CR Cluster ("<release>-pg"). No seleccionaba ningún pod en ninguna de
las 327 bases, así que la base quedaba alcanzable en el 5432 desde cualquier pod de
cualquier namespace del cluster.

Se rehace con el helper correcto y con la allowlist completa. El punto delicado es que
en un namespace sin ninguna policy efectiva, la primera que matchea convierte el ingress
de "todo permitido" a "denegado lo no listado": la allowlist tiene que cubrir todo el
tráfico legítimo, no solo el de Odoo.

Validado en una base de test aplicando la policy y midiendo desde cada origen:

  origen              5432      9187      8000
  Odoo del ns         conecta   -         -
  PG de otro ns       TIMEOUT   -         -
  ns monitoring       TIMEOUT   conecta   TIMEOUT
  ns cnpg-system      TIMEOUT   TIMEOUT   conecta

Cada excepción abre solo su puerto y solo desde su namespace. Con la policy activa el
pod siguió Ready y sin restarts: las probes del kubelet (httpGet al 8000) no las filtra
NetworkPolicy, así que no hace falta listarlas. El operador siguió reportando el cluster
sano y Odoo siguió conectando.

Se borra también <release>-allow-prometheus, que tenía el mismo bug de naming. No se
arregla porque su selector corregido capturaría los cinco pods del release permitiendo
solo monitoring en 9101/9113/9114, o sea cortando el tráfico web y el 5432. Hoy no
protege nada: sin default-deny en el namespace, el PodMonitor scrapea igual.

Se suma el fixture ci/selectors-values.yaml, que enciende CNPG y monitoring. Los values
por defecto los dejan apagados, y por eso ningún render de CI ejercitaba estos recursos
— la razón por la que el bug sobrevivió sin que nadie lo viera. Con el fixture, el
chequeo de selectores cubre estos casos de acá en adelante.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR corrige y endurece la política de red de CloudNativePG en el chart adhoc-odoo, pasando el ingress al Postgres a un esquema default-deny con allowlist explícita, y agrega un fixture de CI para que el render/chequeo de selectores cubra estos recursos cuando cloudNativePG y monitoring están habilitados.

Changes:

  • Reemplaza la NetworkPolicy de CNPG por una allowlist explícita y corrige el selector para que matchee los pods reales del Cluster.
  • Elimina la NetworkPolicy allow-prometheus (que no estaba seleccionando pods por un bug de naming) del template monitoringCommon.yaml.
  • Agrega ci/selectors-values.yaml para ejercitar en CI los recursos que por default quedan apagados.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
charts/adhoc-odoo/templates/monitoringCommon.yaml Elimina una NetworkPolicy de Prometheus que no estaba siendo efectiva por selector incorrecto.
charts/adhoc-odoo/templates/cnpg/networkPolicy.yaml Define una NetworkPolicy de ingress para CNPG con selector corregido y allowlist por origen/puerto (5432/9187/8000).
charts/adhoc-odoo/ci/selectors-values.yaml Agrega un fixture de values para que CI renderice y valide selectores cuando CNPG/monitoring están habilitados.

Comment on lines +25 to 27
# Odoo del propio namespace: el único origen legítimo de tráfico SQL. Se discrimina por
# adhoc.ar/app-name porque los pods de nginx comparten los app.kubernetes.io/*.
- from:
Bloqueante encontrado revisando el PR: el pod del Job wait-pg no tenía ningún label. Es
un hook post-install/post-upgrade que corre pg_isready contra el 5432, así que con la
policy activa quedaba bloqueado, el hook fallaba y con él el helm upgrade entero de las
327 bases.

Los labels del template estaban puestos en el Job, no en spec.template, que es de donde
los toma el pod. Se le da adhoc.ar/app-name: "pg-hook" y su regla propia en la allowlist,
en vez de hacerlo pasar por el label de Odoo.

Validado en la base de test: un pod con ese label obtiene "accepting connections" y uno
sin el label obtiene "no response".

Se precisa además el comentario sobre las probes del kubelet: no las filtra Kubernetes
NetworkPolicy por semántica del recurso, y además está validado en GKE Dataplane V2; un
CNI con host firewall podría comportarse distinto.

Y se documenta lo que la allowlist no cubre por si alguna vez aparece: poolers, standby o
subscribers lógicos fuera del namespace, y tooling externo que conecte por SQL. Verificado
que hoy no hay ninguno: 0 Poolers y 0 clusters en modo replica sobre los 1315 del parque.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

charts/adhoc-odoo/templates/cnpg/networkPolicy.yaml:8

  • El comentario afirma de forma general que Kubernetes NetworkPolicy no filtra tráfico del nodo hacia el pod. Esto no es universal (depende del CNI / configuración) y puede inducir a asumir que las probes siempre van a funcionar sin excepción. Conviene acotar la afirmación a lo validado (GKE Dataplane V2) y dejar explícito que en otros entornos podría requerirse una regla adicional (p.ej. ipBlock con rangos de nodos) o una validación específica.
# solo el de Odoo. Las probes del kubelet no hacen falta listarlas: Kubernetes NetworkPolicy
# no filtra el tráfico del nodo hacia el pod (validado además en GKE Dataplane V2).

@az-adhoc
az-adhoc merged commit 8c78c05 into main Aug 10, 2026
5 checks passed
@az-adhoc
az-adhoc deleted the fix/networkpolicy-ingress-pg branch August 10, 2026 14:23
az-adhoc added a commit that referenced this pull request Aug 10, 2026
…#136)

INCIDENTE: desde que se mergeó #133 las bases nuevas no terminaban de crearse. El job
fixdb del pipeline queda colgado en "Waiting until the database server is listening..."
porque la allowlist no lo contempla, y como su podAntiAffinity es required con
topologyKey hostname y namespaceSelector sobre todos los namespaces, cada fixdb colgado
ocupa un nodo entero: el nodepool de cell01 escaló de 9 a 20 nodos.

Los jobs del pipeline no los crea este chart, así que no aparecen en su render: los crea
el provider dentro del namespace de la base. Todos llevan la clave adhoc.ar/odoo-job
(fixdb, restore), así que la regla matchea por Exists para cubrir los que se sumen.

Validado desplegando el chart en test-texterfeed-10-08-4:

  adhoc.ar/odoo-job=fixdb     -> accepting connections
  adhoc.ar/odoo-job=restore   -> accepting connections
  adhoc.ar/app-name=odoo      -> accepting connections
  adhoc.ar/app-name=intruso   -> no response

Por qué no se detectó antes: la validación de #133 se hizo midiendo pg_stat_activity de
bases en régimen, donde fixdb ya había terminado hacía rato, y revisando los jobs del
chart (donde sí apareció wait-pg). Los jobs del pipeline viven fuera del chart y solo
corren durante la creación. Se agrega esa advertencia al comentario del manifiesto para
que la próxima allowlist se valide contra una creación completa.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants