Skip to content

ADR-0019 — Edge traffic & security architecture

Status: Proposed (en discusión con el operador) · Date: 2026-09-09 Amends: no supersede ningún ADR completo; refina el perímetro de ADR-0006 y extiende la postura de ADR-0014 Fuente de estudio: edge-traffic-security-study-v1.md (lead, 2026-09-09) — live-probes + research upstream.


1. Contexto

Wetopi es managed WordPress hosting: cientos de wepods (s6+nginx+php-fpm+mariadb) detrás de LB, egress por el gateway default del host. En Swarm el perímetro era: iptables+ipset con miles de IPs de feeds (abuse, firehol, GeoIP) sincronizados por consul-template, Suricata en workers+nat-gws, SSL offloading en lbl7 nginx y HTTP interno hasta el pod. La nueva plataforma RKE2 se construye sobre todo por seguridad; este ADR define la replicación de esos controles en k8s.

Estado live verificado (2026-09-09, kubectl read-only): Traefik 3.7.8 sin access logs/tracing/plugins; Hubble server activo :4244 sin hubble-metrics; natgw VM fuera del cluster, sin contadores; otelcol excluye kube-system de logs; 13 HTTPRoutes con 7 middlewares ipAllowList (único control edge actual). Upstream: Traefik v3.7.13 CVE-fix (desplegado 3.7.8 = gap de seguridad, prerrequisito del WAF), Cilium 1.20.1, CRS v4.29.0, CrowdSec bouncer v1.7.1, Coraza plugin WASM v0.3.0 early-stage.

2. Decisiones [DECIDIDAS por el operador]

D-egress — natgw entra al cluster; Cilium Egress Gateway

  • Los nodos natgw (natgw01 + futuros) se incorporan como nodos RKE2 → Cilium Egress Gateway nativo: selección de egressIP/pool por label del wepod (wetopi.io/egress-class) vía CiliumEgressGatewayPolicy; HA futura vía lista egressGateways.
  • Requisito CNI aceptado: kubeProxyReplacement=true + bpf.masquerade cluster-wide (HelmChartConfig rke2-cilium) — reconfiguración del datapath: POC previo en entorno de prueba + ventana + rollback; resolver interacción con kube-proxy de RKE2 ANTES.
  • RAM natgw 2GB→4GB; hardening patrón wan01/02 (taints, nodeSelector, nftables host).
  • Atribución: Hubble ve pod→destino antes del SNAT; contadores por clase.

D-fw-edge — firewall de feeds como DaemonSet + controller central

  • fw-edge-agent: DaemonSet k8s (hostNetwork + privileged estrecho + RBAC propio, patrón cilium-agent) en wan01/wan02/natgw. La variante systemd/Ansible queda descartada.
  • fw-edge-controller: Deployment in-cluster, único descargador/compilador de feeds + GeoIP + bloqueos dinámicos; produce revisiones inmutables (fw-rev-N) a un artifact store (S3/Ceph-RGW; interim HTTP interno).
  • El agente aplica con poll ≤60s + verificación sha256 + swap atómico por-tabla; reporta desired/active revision (drift → alerta). Ansible solo instala; las 500 actualizaciones/día van por revisiones, NO por GitOps (el estado dinámico de incidentes va por API del backend).
  • Fail-safe: pod muerto ≠ flush; last-known-good vive en el kernel.
  • Contrato de coexistencia nftables: ver REC-20260909-FW-COEXIST (tabla exclusiva wetopi_fwedge, flush ruleset PROHIBIDO, priority prerouting antes del REDIRECT de edge_lb, acceptance con edge_lb intacto).

D-tls — SSL offloading [DECIDIDO]

TLS termina en Traefik; HTTP interno hasta el wepod (patrón lbl7 actual). Cilium WireGuard transparente entre nodos como hardening opcional posterior (benchmark primero). Re-encrypt por-pod rechazado (complejidad de certs sin beneficio real).

D-waf-toggle — WAF per-site [DECIDIDO el modelo]

Toggle por wepod vía flag en el manifest renderizado por GitOps (wetopi.io/waf-mode: off|detect|block); progression off→detect→block por sitio, nunca global day-one. Rollout: detect primero, exclusiones WordPress/WooCommerce documentadas por site_id+rule_id+razón.

3. Decisiones ABIERTAS (open questions del ADR)

  • Q1 — Suricata placement: natgw decidido (seguro). ¿También en wan01/02 (ingress público)? Decidir con datos de tráfico real; coste CPU edge + captura (AF_PACKET/mirror).
  • Q2 — Mecanismo WAF: Coraza plugin WASM v0.3.0 (early-stage, CRS en sandbox UNCERTAIN) vs sidecar (Coraza native/BunkerWeb v1.6.14). Requiere POC con gate de decisión (latencia + CRS carga).
  • Q3 — CrowdSec como capa reactiva (bouncer v1.7.1 + LAPI en cluster, alimentando el mismo set nft del fw-edge): propuesta del lead, sin decidir.
  • Q4 — Tetragon (atribución php-fpm→shell): componente nuevo, eBPF por nodo; secuenciar tras estabilizar CNI. Sin decidir.
  • Q5 — Access logs + hubble-metrics (propuestas D1/D2 del estudio): sin decidir; F2 (hubble-metrics) toca el CNI vivo → ventana aparte.

3. Perfiles de egress (propuestos, del ADR-pack)

wordpress-standard (DNS+80/443, SMTP bloqueado), wordpress-restricted (FQDNs aprobados), wordpress-mail-relay (solo relay interno), quarantine (forense only), custom (excepción con owner+expiry). SMTP directo bloqueado por defecto (25/465/587) — control #1 de reputación de IPs. DNS solo CoreDNS.

4. Observabilidad

Métricas (Prometheus): fw-edge (revisiones, entradas por clase, drops), Cilium EG, Hubble (hubble-metrics :9965 — pendiente Q5), Suricata exporters, Traefik (:9100 ya activo). Eventos → OpenSearch: security-suricata-*, security-waf-*, access-traefik-*, security-edgefw-*. Correlación: Suricata IP → Hubble pod/namespace → site_id; identity canónica = labels wepod (site_id, customer_id, egress-class).

5. Fases (del ADR-pack, corregidas)

  1. F-0 inventario — documentar estado actual (feeds, ipset semantics, Suricata custom rules, SMTP patterns) → security-current-state.md.
  2. Cilium foundation — Hubble + labels; sin enforcement disruptivo.
  3. Wepod de prueba desechable (WordPress hello-world, no es el diseño de producción): labels + default-deny ingress (por label-selectors, NUNCA ipBlock 0.0.0.0/0 — bug Cilium 1.20.1 verificado 2026-09-06) + egress DNS/80/443 + EG path natgw.
  4. SMTP detect→deny.
  5. fw-edge controller + DaemonSet: test node → 1 WAN → todos; comparar counters con legacy ipset.
  6. Suricata chokepoints (según Q1).
  7. Tetragon (observe-only) — posterior.
  8. WAF (según Q2) — detect en test WP.
  9. Correlación + quarantine profile.

6. Acceptance (resumen; full list en el ADR-pack anexo)

  • wepod A no alcanza wepod B; wepod no alcanza API/Ceph/management.
  • site→egress IP observable y estable ante reschedules.
  • feed roto ≠ ruleset vacío (last-known-good + alerta); revisions sincronizadas en todos los nodos del borde.
  • WAF off/detect/block por sitio; detect no bloquea; block rechaza un caso CRS de test.
  • IDS/Tetragon event → site_id atribuible.
  • edge_lb intacto tras cada apply del fw-edge (contrato coexistencia).

7. Consequences

Positivos: aislamiento multi-tenant real, egress determinístico y auditable, reputación protegida (SMTP + pools), feeds escalables (47k IPs de incidente sin GitOps), un solo mecanismo para feeds+egress-classes, atribución site-level end-to-end. Negativos/riesgos: Cilium se vuelve crítico también en el egress (blast radius); kubeProxyReplacement = cambio de datapath cluster-wide (POC+ventana obligatorias); WAF requiere tuning WordPress continuo; volumen de telemetría de seguridad a dimensionar.

8. Referencias

  • Estudio completo: edge-traffic-security-study-v1.md (local; contenido canónico absorbido aquí)
  • ADR-pack del operador (9 sub-decisiones, backlog SEC-01..14, acceptance criteria §50): anexo pendiente de publicar como REC
  • Research upstream 2026-09-09: pins y fuentes en el estudio §5 (Traefik v3.7.13, cilium-1.20.1, CRS v4.29.0, CrowdSec v1.7.1, Coraza v0.3.0/v3.7.0, BunkerWeb v1.6.14)