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íaCiliumEgressGatewayPolicy; HA futura vía listaegressGateways. - Requisito CNI aceptado:
kubeProxyReplacement=true+bpf.masqueradecluster-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 rulesetPROHIBIDO, 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)¶
- F-0 inventario — documentar estado actual (feeds, ipset semantics, Suricata custom rules, SMTP patterns) →
security-current-state.md. - Cilium foundation — Hubble + labels; sin enforcement disruptivo.
- 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.
- SMTP detect→deny.
- fw-edge controller + DaemonSet: test node → 1 WAN → todos; comparar counters con legacy ipset.
- Suricata chokepoints (según Q1).
- Tetragon (observe-only) — posterior.
- WAF (según Q2) — detect en test WP.
- 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_lbintacto 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)