Plan2026/07-decision-red.md

48 KiB
Raw Blame History

Decision: Capa de red y conectividad (punto 4)

Contexto

El PBS actual tiene el puerto 8007 abierto a internet para recibir backups. A 100+ servidores, cada servicio de backup deberia ir por VPN privada. Ademas, ya existe PowerDNS y Traefik en uso. La capa de red organiza como se comunican los servidores entre si y con el exterior de forma segura.

Infraestructura fisica: servidores con 2 interfaces de red (IP publica + vRack OVH 25Gbps). Todo el direccionamiento interno sera IPv6 (ULA).

3 subcapas:

  • 4.1 VPN y acceso seguro entre servidores
  • 4.2 DNS y service discovery
  • 4.3 Proxy inverso y balanceo

4.1 VPN y acceso seguro entre servidores

El problema

  • PBS:8007 expuesto a internet para recibir backups de PVE remotos
  • Trafico de backup viaja por internet sin cifrado adicional (solo TLS de PBS)
  • vRack de OVH conecta servidores pero sin cifrado ni autenticacion (L2 privada, no segura)
  • Sin VPN, cada servicio nuevo expuesto requiere abrir puertos en firewall publico
  • La superficie de ataque crece con cada puerto abierto
  • Modelo actual de gateways: 1 VM gateway por cliente (NAT + WG), no escala

Opciones evaluadas

Herramienta Modelo Recursos Complejidad Estado
WireGuard Punto a punto, hub-spoke Kernel module, ~0 overhead Baja (config manual, Ansible) ELEGIDA
Tailscale Mesh VPN, SaaS Agente ligero Muy baja Descartada (SaaS, dependencia externa)
Headscale Tailscale self-hosted ~200MB RAM Baja-media Posible evolucion futura si escala
Nebula Overlay con certificados Agente ligero Media Descartada (menor comunidad)
Netbird Mesh con SSO ~300MB RAM Media Descartada (muy joven)
BGP/OSPF entre hubs Routing dinamico FRRouting Alta Descartado (overengineering a esta escala)

DECISION 4.1 (confirmada)

WireGuard + wgdashboard + IPv6 ULA

Todo el direccionamiento WireGuard en IPv6 ULA (fd00::/8). No ruteable a internet, privado por diseño.

Tres roles de WireGuard

Rol 1: PBS como servidor WG (backups)

Cada PBS fisico es un servidor WireGuard cuyos unicos peers son los PVE que le envian backups.

PBS-1 (wg server)                    PBS-2 (wg server)
├── peer: PVE-A                      ├── peer: PVE-D
├── peer: PVE-B                      └── peer: PVE-E
└── peer: PVE-C
  • PVE apunta su PBS storage a [fdab🔢bkp::1]:8007 (IP WG del PBS)
  • PBS:8007 deja de escuchar en IP publica
  • Los peers PVE NO se ven entre ellos (ip forwarding desactivado, AllowedIPs /128 por peer)
  • Trafico por vRack (25Gbps) cuando estan en el mismo DC

Rol 2: WG Hub (acceso clientes + tecnicos + VMs)

Hub centralizado donde se conectan:

  • Usuarios remotos (tecnicos, clientes)
  • Mikrotiks de oficinas de clientes
  • VMs de clientes que necesitan red privada
WG Hub (par HA con keepalived)
├── peer: tecnico David
├── peer: tecnico Juan
├── peer: cliente ACME usuario remoto
├── peer: cliente ACME mikrotik oficina
├── peer: VM-web ACME (en PVE-1)
├── peer: VM-db ACME (en PVE-2)
├── peer: cliente BETA usuario remoto
├── peer: VM-app BETA (en PVE-1)
└── ...
  • ip forwarding ACTIVADO (los peers deben verse entre ellos)
  • Aislamiento por cliente con nftables: peers de ACME solo ven peers de ACME
  • VMs en PVEs distintos se ven via WG Hub (cross-PVE transparente)
  • Panel futuro para que el cliente defina granularidad peer-a-peer
  • Reemplaza el modelo de 1 gateway VM por cliente: gateways compartidos con reglas granulares

Rol 3: Mesh de gestion entre fisicos (sobre vRack)

WireGuard sobre vRack para cifrar y autenticar el trafico de gestion entre servidores fisicos.

Fisico 1 (vRack)  ◄── WG tunnel cifrado ──►  Fisico 2 (vRack)
fdab:1234:mgmt::1                              fdab:1234:mgmt::2
  • Servicios internos (Grafana, Netbox, Wazuh, Semaphore) escuchan en IP WG mesh
  • Ansible, exporters, logs viajan por WG mesh
  • Hub-spoke: todos los fisicos conectan a un hub de gestion (o al par HA)

Direccionamiento IPv6 ULA

fdab:1234:bkp::/48    → Red de backups (PBS ↔ PVE)
  fdab:1234:bkp:1::/64   PBS-1 y sus PVEs
  fdab:1234:bkp:2::/64   PBS-2 y sus PVEs

fdab:1234:vpn::/48    → Red de clientes/tecnicos (WG Hub)
  fdab:1234:vpn:acme::/64   Cliente ACME (VMs + usuarios)
  fdab:1234:vpn:beta::/64   Cliente BETA

fdab:1234:mgmt::/48   → Red de gestion (mesh entre fisicos)
  fdab:1234:mgmt::1     Hub/Fisico 1
  fdab:1234:mgmt::2     Fisico 2

Arquitectura del WG Hub

Debian minimal dedicado exclusivamente a WireGuard:

┌──────────────────────────────────┐
│  Debian minimal                   │
│                                   │
│  Kernel: WireGuard module         │
│  Host:   nftables (aislamiento   │
│          por cliente)             │
│  Host:   /etc/wireguard/*.conf   │
│                                   │
│  Docker: wgdashboard              │
│  └─ UI web (gestion de peers)    │
│  └─ API REST (→ ICSManager)      │
│  └─ Escribe /etc/wireguard/      │
│  └─ Recarga WG (wg syncconf)    │
│                                   │
│  Nada mas. Sin PBS, sin VMs,     │
│  sin otros servicios.            │
└──────────────────────────────────┘

Alta disponibilidad: par HA con keepalived

┌─────────────┐    ┌─────────────┐
│ WG Hub A    │    │ WG Hub B    │
│ (MASTER)    │    │ (BACKUP)    │
│ fdab::1     │    │ fdab::2     │
└──────┬──────┘    └──────┬──────┘
       │                   │
       └───── VIP ─────────┘
         fdab:1234:vpn::1
         (IP flotante)
  • Misma config WG en ambos (mismas claves, mismos peers)
  • Keepalived gestiona VIP sobre vRack
  • Failover en <2 segundos
  • Los peers no se enteran: su endpoint es la VIP, no cambia
  • Para endpoint publico (usuarios remotos): VIP publica o DNS con TTL bajo

Aislamiento entre clientes (nftables en el hub)

# nftables en WG Hub
# Cliente ACME: sus peers se ven entre ellos
nft add rule inet filter forward \
  iifname "wg-hub" ip6 saddr fdab:1234:vpn:acme::/64 \
  ip6 daddr fdab:1234:vpn:acme::/64 accept

# Cliente BETA: sus peers se ven entre ellos
nft add rule inet filter forward \
  iifname "wg-hub" ip6 saddr fdab:1234:vpn:beta::/64 \
  ip6 daddr fdab:1234:vpn:beta::/64 accept

# DEFAULT: nadie ve a nadie de otro cliente
nft add rule inet filter forward iifname "wg-hub" drop

Cada cliente tiene su /64. Las reglas son por subnet, no por peer individual (mas simple). Panel futuro para granularidad peer-a-peer dentro del /64.

Cambio de modelo: gateways compartidos

Modelo actual Modelo nuevo
1 VM gateway por cliente 2 VM WG Hub (par HA) compartidos
50 clientes = 50 VMs gateway 50 clientes = 2 VMs hub
NAT por cliente Sin NAT (peers directos IPv6)
Cada GW consume RAM/CPU Recursos compartidos
Aislamiento por VM Aislamiento por nftables
Sin HA (si el GW cae, el cliente cae) HA con keepalived (<2 seg failover)

Mitigacion de riesgo: tests automatizados de aislamiento (playbook que verifica que cada cliente solo alcanza sus recursos).

Exposicion resultante del servidor fisico

eth0 (IP publica):
  - Firewall: solo WG UDP (1 puerto) + SSH bastion
  - PBS:8007 CERRADO desde internet
  - PVE:8006 CERRADO desde internet
  - Todo lo demas: DROP

eth1 (vRack 25Gbps):
  - WG mesh cifrado (gestion + backups)
  - Servicios internos solo escuchan en IP WG

wg-hub (overlay IPv6):
  - Clientes y tecnicos acceden a paneles por aqui
  - :8007, :8006, Grafana, etc. solo via WG

Fase de arranque

Fase inicial con solo tecnicos internos para pulir el modelo:

  1. Montar par HA de WG Hub (2 VMs Debian minimal)
  2. Peers: solo tecnicos del equipo
  3. Validar: acceso a paneles, grabacion bastion, aislamiento nftables
  4. Cuando funcione estable: abrir a clientes y VMs progresivamente

Recursos

Componente CPU RAM Disco
WG Hub A (master) 1 vCPU 512 MB 10 GB
WG Hub B (backup) 1 vCPU 512 MB 10 GB
wgdashboard (Docker en Hub A) incluido ~200 MB incluido
WG en cada PBS (backup role) kernel module ~0 ~0
WG en cada fisico (mesh role) kernel module ~0 ~0

Escalabilidad futura

  • Si un par HA no es suficiente (>200 peers): asignar clientes a hubs distintos (Hub par 1: clientes A-M, Hub par 2: clientes N-Z), con tunel WG entre hubs para cross-routing
  • Si se necesita mesh automatico: evaluar Headscale (compatible con clientes WireGuard existentes)
  • Si se necesita routing dinamico entre DCs: FRRouting con OSPF sobre tuneles WG entre hubs
  • Nada de esto es necesario para arrancar

4.2 DNS y service discovery

Las 4 problematicas

  1. Autoritativo de clientes: zonas de dominios de clientes (primario o secundario)
  2. Resolver para clientes: navegacion de VMs y usuarios WG, con bloqueo configurable
  3. Naming automatico: toda IP asignada tiene AAAA + PTR operativos desde el momento 0
  4. Diagnostico self-service: pagina donde el cliente verifica su conectividad antes de abrir ticket

Opciones evaluadas para resolver

Herramienta Multi-tenant Panel cliente Bloqueo config. API Estado
Technitium DNS Si (por cliente/zona) Panel web multi-usuario nativo Si (listas por cliente) REST completa ELEGIDA como resolver
AdGuard Home Parcial (por IP) Solo admin Si (listas por IP) REST Descartada (no multi-tenant nativo)
Pi-hole No No Global Limitada Descartada
Blocky Si (grupos) No tiene UI Si (por grupo) Si Descartada (sin panel)
Unbound No No No No Descartada (solo resuelve)

DECISION 4.2 (confirmada)

Componente 1: PowerDNS Authoritative (ya existente)

Se mantiene y amplia. No se reemplaza.

Funcion Estado
Zonas autoritativas de clientes (primario/secundario) Ya funciona, mantener
Zona vpn6.com.es (nombres WG peers) Crear, automatizar
Zonas PTR IPv6 (reverse DNS) Crear, automatizar
API REST para creacion automatica de registros Activar/usar

Alimentado por:

  • wgdashboard: al crear peer WG → API PowerDNS → AAAA + PTR en vpn6.com.es
  • Netbox (netbox-dns plugin): VMs sincronizadas desde PVE → registros DNS automaticos
  • Manual: zonas de clientes como hasta ahora, evolucionando dominio a dominio

Componente 2: Technitium DNS (resolver con bloqueo)

Solo como resolver. No toca las zonas autoritativas (eso es PowerDNS).

Technitium resuelve consultas DNS de:

  • VMs de clientes
  • Usuarios WG (tecnicos, clientes remotos)
  • Mikrotiks de oficinas conectados por WG

Funcionalidades clave:

  • Bloqueo multi-tenant: cada cliente (identificado por rango IP WG) tiene su perfil de bloqueo
  • Panel web nativo multi-usuario: cada cliente puede entrar y ver sus queries, ajustar bloqueo
  • Categorias de bloqueo: malware, phishing, ads, adult, custom por cliente
  • Dashboard de trafico DNS: top dominios, queries bloqueadas, estadisticas
  • API REST completa: automatizable desde ICSManager/n8n
  • DNSSEC validation: verifica firmas DNS
  • DoH/DoT upstream: consultas cifradas a upstream (Cloudflare, Google)
VM cliente ──────►┌──────────────────────────┐
                  │  Technitium DNS           │
WG usuario ──────►│  (resolver + bloqueo)     │───► Upstream DoH
                  │                           │     (Cloudflare/Google)
Mikrotik ────────►│  Panel por cliente:       │
                  │  - ACME: malware+ads ✅   │
                  │  - BETA: solo malware ✅  │
                  │  - GAMMA: todo ✅         │
                  │                           │
                  │  Zonas internas:          │
                  │  → reenvio a PowerDNS     │
                  │    para *.vpn6.com.es     │
                  │    y zonas de clientes    │
                  └──────────────────────────┘

Technitium reenvia a PowerDNS las consultas de zonas internas:

  • *.vpn6.com.es → PowerDNS (nombres WG)
  • *.cliente.com → PowerDNS (si somos autoritativo)
  • Todo lo demas → upstream DoH (navegacion)

Recursos: 1-2 vCPU, 512MB-1GB RAM, contenedor Docker o VM ligera.

Componente 3: Naming automatico (toda IP tiene nombre)

Esquema de nombres sobre vpn6.com.es (dominio propio):

Tecnicos internos:
  laptop.david.vpn6.com.es     → fdab:1234:vpn::42
  movil.david.vpn6.com.es      → fdab:1234:vpn::43

Clientes (subdominio por cliente):
  oficina.acme.vpn6.com.es     → fdab:1234:vpn:acme::1   (mikrotik)
  portatil.juan.acme.vpn6.com.es → fdab:1234:vpn:acme::10 (usuario)
  vm-web.acme.vpn6.com.es      → fdab:1234:vpn:acme::100 (VM)

Infraestructura:
  pbs-1.mgmt.vpn6.com.es       → fdab:1234:mgmt::10
  pve-2.mgmt.vpn6.com.es       → fdab:1234:mgmt::20
  grafana.mgmt.vpn6.com.es     → fdab:1234:mgmt::30

Con dominio del cliente (si lo pide):
  vpn.acme-empresa.com  → CNAME a acme.vpn6.com.es

Flujos de creacion automatica:

wgdashboard crea peer
  → nombre definido en el formulario
  → API PowerDNS: crea AAAA + PTR
  → El peer ya tiene rDNS + forward desde el momento 0

PVE crea VM
  → netbox-proxmox sincroniza VM a Netbox
  → netbox-dns genera registro en PowerDNS
  → La VM ya tiene nombre DNS automatico

Ansible despliega servidor nuevo
  → Playbook incluye task: registrar en PowerDNS via API
  → Servidor tiene nombre desde el primer boot

Componente 4: Pagina de diagnostico self-service

Panel web donde el cliente entra (SSO Authentik) y ve su estado:

┌─────────────────────────────────────────┐
│  Diagnostico de conectividad            │
│  Cliente: ACME                          │
│                                         │
│  ✅ Tu IP: fdab:1234:vpn:acme::10      │
│  ✅ WireGuard: conectado (handshake 3s) │
│  ✅ DNS: resolviendo correctamente      │
│  ❌ Certificado PBS: caduca en 3 dias   │
│  ✅ PBS:8007: accesible (120ms)         │
│  ✅ Latencia WG: 12ms                   │
│  ⚠️  Tunel IPsec oficina: no detectado  │
│                                         │
│  [Copiar informe] [Abrir ticket]        │
└─────────────────────────────────────────┘

Implementacion: app web ligera (Flask/FastAPI, un contenedor Docker) que:

  1. Detecta IP origen → identifica cliente (via Netbox API o tabla local)
  2. Ejecuta checks en tiempo real contra los servicios de ese cliente
  3. Genera informe copiable para pegar en ticket
  4. Login via SSO Authentik

Prioridad: mes 3+, desarrollo propio. No es critico para arrancar.

Integracion PVE → DNS via Netbox

PVE API ──netbox-proxmox──► Netbox
                               │
                               │ VM con IP asignada
                               ▼
                            netbox-dns plugin
                               │
                               │ Sincroniza zona en PowerDNS
                               ▼
                            PowerDNS API
                               │
                               ▼
                            AAAA: vm-web.acme.vpn6.com.es
                            PTR:  fdab:...::100 → vm-web.acme.vpn6.com.es

No hay hookscripts fragiles en PVE. Netbox es el intermediario fiable. Si la VM existe en Netbox con IP, tiene DNS.

Arquitectura DNS completa

┌───────────────────────────────────────────────────────┐
│                                                        │
│  AUTORITATIVO                     RESOLVER             │
│  ┌──────────────────┐            ┌──────────────────┐ │
│  │  PowerDNS Auth   │            │  Technitium DNS  │ │
│  │  (ya existente)  │◄─reenvio──│  (nuevo)         │ │
│  │                  │  zonas     │                  │ │
│  │  Zonas clientes  │  internas  │  Resolver +      │ │
│  │  vpn6.com.es     │            │  bloqueo multi-  │ │
│  │  PTR IPv6        │            │  tenant          │ │
│  │                  │            │                  │ │
│  │  ← Netbox API    │            │  Panel por       │ │
│  │  ← wgdashboard   │            │  cliente nativo  │ │
│  │  ← Ansible       │            │                  │ │
│  └──────────────────┘            │  Upstream: DoH   │ │
│                                  └──────────────────┘ │
│                                                        │
│  NAMING AUTOMATICO               DIAGNOSTICO           │
│  ┌──────────────────┐           ┌──────────────────┐  │
│  │  Netbox (SSOT)   │           │  Panel web ligero│  │
│  │  + netbox-dns    │           │  (Flask/FastAPI)  │  │
│  │  + netbox-proxmox│           │  SSO Authentik    │  │
│  │  → PowerDNS API  │           │  Checks por      │  │
│  │                  │           │  cliente          │  │
│  │  wgdashboard     │           │  Mes 3+           │  │
│  │  → PowerDNS API  │           └──────────────────┘  │
│  └──────────────────┘                                  │
│                                                        │
└───────────────────────────────────────────────────────┘

Recursos DNS

Componente CPU RAM Disco Estado
PowerDNS Authoritative existente existente existente Ampliar zonas
Technitium DNS (resolver) 1 vCPU 512 MB - 1 GB 10 GB Nuevo
netbox-dns plugin 0 (dentro de Netbox) 0 0 Configurar
Panel diagnostico incluido en VM servicios ~100 MB incluido Mes 3+

Prioridades despliegue DNS

Prioridad Que Esfuerzo
Semana 1 Technitium DNS como resolver basico (sin bloqueo aun) 2h
Semana 1 Crear zona vpn6.com.es en PowerDNS 1h
Semana 2 Automatizar AAAA+PTR desde wgdashboard → PowerDNS API 4h
Semana 2 Configurar Technitium: reenvio zonas internas a PowerDNS 1h
Mes 1 Configurar perfiles de bloqueo por cliente en Technitium 2h
Mes 1 netbox-proxmox + netbox-dns: auto-registro VMs 1 dia
Mes 2 Abrir panel Technitium a clientes (con SSO Authentik) 2h
Mes 3+ Panel de diagnostico self-service 3-5 dias desarrollo

4.3 Proxy inverso y publicacion de servicios

El problema

  • Tendencia a eliminar IPv4: los clientes deben poder publicar servicios sin necesitar IP publica propia
  • Cada cliente que quiere exponer un servicio web necesita SSL, reverse proxy, y DNS
  • Hoy se hace con Traefik (ya en uso) pero sin un modelo sistematico
  • Los clientes deberian poder declarar "quiero publicar este servicio" y que se configure automaticamente

Opciones evaluadas

Herramienta Autodiscovery SSL auto API Estado
Traefik Docker labels, ficheros, API Let's Encrypt nativo REST ELEGIDA (ya en uso)
Nginx Proxy Manager Manual (UI web) Let's Encrypt No Descartada (manual)
HAProxy Config estatica Manual o con ACME Stats Descartada (sin auto-SSL)
Caddy Caddyfile, API Automatico por defecto REST Alternativa viable

DECISION 4.3 (confirmada)

Traefik como proxy inverso centralizado

Ya se usa. Se amplia como punto de publicacion de servicios para clientes.

Modelo: cliente declara servicio → Traefik lo expone

Cliente ACME quiere publicar:
  - Portal web: portal.acme.com
  - API: api.acme.com

Flujo:
1. Cliente declara en panel (o ICSManager): "quiero publicar portal.acme.com"
2. DNS: AAAA de portal.acme.com → IPv6 del Traefik
3. Traefik: route portal.acme.com → VM del cliente (por WG IPv6)
4. SSL: Let's Encrypt automatico
5. Cliente solo tiene IPv6 WG, Traefik pone la cara publica

Arquitectura

Internet (IPv4+IPv6)
       │
       ▼
┌──────────────────────────────────────┐
│  Traefik (proxy inverso)             │
│                                       │
│  Escucha:                            │
│  - IPv4 publica :443 (legacy)        │
│  - IPv6 publica :443 (preferido)     │
│                                       │
│  Routes:                             │
│  portal.acme.com → [fdab:..acme::100]:8080  │
│  api.acme.com    → [fdab:..acme::101]:3000  │
│  grafana.mgmt.vpn6.com.es → [fdab:..mgmt::30]:3000 │
│                                       │
│  SSL: Let's Encrypt (automatico)     │
│  Middleware: rate limiting, headers   │
│                                       │
│  Config: ficheros dinamicos          │
│  (generados por Ansible/API)         │
└──────────────────────────────────────┘
       │
       │ WG IPv6
       ▼
┌──────────────┐  ┌──────────────┐
│ VM cliente   │  │ VM cliente   │
│ portal ACME  │  │ API ACME     │
│ (solo IPv6   │  │ (solo IPv6   │
│  WG, sin IP  │  │  WG, sin IP  │
│  publica)    │  │  publica)    │
└──────────────┘  └──────────────┘

Eliminacion progresiva de IPv4

La tendencia es que las VMs de clientes NO tengan IPv4 publica:

Antes Despues
Cada VM con IP publica VM solo tiene IPv6 WG
SSL configurado en cada VM SSL centralizado en Traefik
Firewall por VM Firewall centralizado
El cliente gestiona su SSL Traefik gestiona Let's Encrypt

Para clientes que aun necesiten IPv4 entrante (legacy):

  • Traefik escucha en IPv4 publica y reenvia a IPv6 WG del cliente
  • El cliente no necesita IPv4 propia
  • Transicion transparente

Configuracion dinamica

Traefik puede leer rutas de:

  • Ficheros YAML (generados por Ansible al dar de alta un servicio)
  • Docker labels (para servicios en Docker)
  • API (para automatizacion)
# /etc/traefik/dynamic/clientes/acme.yml
# Generado automaticamente al dar de alta el servicio
http:
  routers:
    portal-acme:
      rule: "Host(`portal.acme.com`)"
      service: portal-acme
      tls:
        certResolver: letsencrypt
  services:
    portal-acme:
      loadBalancer:
        servers:
          - url: "http://[fdab:1234:vpn:acme::100]:8080"

Flujo automatizado:

ICSManager/Panel → API → Ansible playbook
  → Crea fichero YAML en Traefik
  → Crea AAAA en PowerDNS
  → Traefik recarga automaticamente (file watcher)
  → Servicio publicado con SSL en segundos

Servicios internos via Traefik

Los paneles internos (Grafana, Netbox, Wazuh, etc.) tambien pasan por Traefik pero solo accesibles via WG:

# Servicios internos - solo escuchan en IPv6 WG
http:
  routers:
    grafana:
      rule: "Host(`grafana.mgmt.vpn6.com.es`)"
      service: grafana
      tls:
        certResolver: letsencrypt
    netbox:
      rule: "Host(`netbox.mgmt.vpn6.com.es`)"
      service: netbox
      tls:
        certResolver: letsencrypt

Accesibles solo desde la red WG de gestion. Desde internet: no alcanzable.

Recursos

Componente CPU RAM Disco
Traefik 1 vCPU 256-512 MB 5 GB

Puede correr en la VM de servicios (como contenedor Docker) o en los WG Hubs. No necesita VM dedicada.

Prioridades despliegue proxy

Prioridad Que Esfuerzo
Semana 1 Traefik para servicios internos (Grafana, Netbox, etc.) 2h
Semana 2 SSL automatico para servicios internos 1h
Mes 1 Primer servicio de cliente publicado via Traefik 2h
Mes 2 Template Ansible para alta automatica de servicios 4h
Mes 3+ Panel para que clientes declaren servicios Desarrollo

4.4 Salida a internet: NAT CGNAT + política restrictiva

El problema

Las VMs de clientes son IPv6-only (WG). Necesitan salida a internet para:

  • apt update / Docker pull
  • APIs externas
  • Navegación (si aplica)

Sin un mecanismo de salida, las VMs están aisladas. Pero dar salida sin control abre riesgos: un cliente puede enviar spam, hacer scraping, o abusar y provocar que la IP compartida se blacklistee.

DECISION 4.4 (confirmada)

Dual-stack con IPv4 CGNAT para salida

Cada VM de cliente tiene dual-stack:

VM de cliente:
  - IPv6 WG: fdab:1234:vpn:acme::10  → acceso entrante (RDP, SSH, servicios)
  - IPv4 CGNAT: 100.64.1.10/24       → solo salida NAT (navegar, apt, APIs)

Regla de oro:
  IPv4 = solo SALE, nunca ENTRA (outbound NAT)
  IPv6 = ENTRA controlado (el cliente/técnico accede vía WG)
  Web  = publicado por Traefik (HTTP/HTTPS al mundo)

Rango CGNAT (100.64.0.0/10)

Se usa el rango CGNAT (RFC 6598) en vez de RFC1918 para evitar colisiones con redes de oficina de clientes:

100.64.0.0/10 total (~4 millones de IPs)

Asignación: /24 por cliente
  100.64.1.0/24  → Cliente ACME
  100.64.2.0/24  → Cliente BETA
  100.64.3.0/24  → Cliente GAMMA
  ...
  100.64.254.0/24 → Cliente 254
  100.65.x.0/24   → Overflow si hace falta

Ventaja sobre 10.x o 192.168.x: un cliente con oficina en 192.168.1.0/24 conectada por WG no tiene conflicto con la IP CGNAT de su VM.

Política de salida restrictiva (nftables en WG Hub)

table ip filter {
    chain forward {
        type filter hook forward priority 0; policy drop;

        # Tráfico establecido/relacionado
        ct state established,related accept

        # --- PERMITIDO PARA TODOS ---
        ip saddr 100.64.0.0/10 tcp dport { 80, 443 } accept   # Web
        ip saddr 100.64.0.0/10 udp dport { 53, 123 } accept   # DNS, NTP
        ip saddr 100.64.0.0/10 tcp dport 53 accept             # DNS TCP

        # --- SMTP: BLOQUEADO POR DEFECTO ---
        ip saddr 100.64.0.0/10 tcp dport 25 drop               # NUNCA

        # Puerto 465/587: solo whitelist
        ip saddr 100.64.0.0/10 tcp dport { 465, 587 } ip daddr @smtp_whitelist accept

        # --- RESTO: DENEGADO ---
        ip saddr 100.64.0.0/10 log prefix "CGNAT-BLOCKED: " drop
    }
}

table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100;
        # NAT compartido (base)
        ip saddr 100.64.0.0/10 oifname "eth0" masquerade
    }
}

# Whitelist SMTP
set smtp_whitelist {
    type ipv4_addr
    flags interval
    elements = {
        # WildDuck propio (IP interna)
        100.64.0.0/10,
        # Añadir IPs de servicios de correo autorizados según necesidad
    }
}

Puertos permitidos por defecto

Puerto Política Razón
80, 443 Permitido Navegación, APIs, apt, Docker pulls
53, 123 Permitido DNS, NTP
25 ✗ Bloqueado siempre Prevenir spam. Sin excepciones.
465, 587 Solo whitelist SMTP autenticado a servidores conocidos
Todo lo demás ✗ Bloqueado El cliente pide, se evalúa, se abre específicamente

Logs de intentos bloqueados → Alloy → Loki. Visibles en Grafana.

Rate limiting por cliente

# Evitar abuso incluso en puertos permitidos
ip saddr 100.64.1.0/24 tcp dport { 80, 443 } limit rate 200/second burst 50 accept
ip saddr 100.64.1.0/24 tcp dport { 80, 443 } drop

Definición de producto: base vs premium

SALIDA INTERNET (NAT) - Definición de producto
─────────────────────────────────────────────────
INCLUIDO (base):
  - Salida compartida: HTTP/HTTPS, DNS, NTP
  - SMTP solo a servidores autorizados (465/587 whitelist)
  - Puerto 25 bloqueado sin excepciones
  - IP pública compartida
  - IP puede cambiar sin preaviso
  - Sin garantía de reputación de IP
  - Política restrictiva no negociable

PREMIUM (contratación adicional):
  - IP dedicada fija con PTR personalizable
  - Si se blacklistea, solo afecta al cliente que la contrató
  - Apertura de puertos adicionales (previa evaluación)
  - Reglas firewall personalizadas
  - Rate limits ajustados
─────────────────────────────────────────────────

Implementación del premium:

# Cliente ACME tiene IP dedicada (premium)
ip saddr 100.64.1.0/24 oifname "eth0" snat to 1.2.3.101

# Resto de clientes: IP compartida
ip saddr 100.64.0.0/10 oifname "eth0" snat to 1.2.3.100

Gestión

  • Reglas nftables gestionadas con Ansible (un fichero por cliente, versionado en Forgejo)
  • Aperturas de puertos: el cliente solicita → se evalúa → se aplica con playbook → queda en git
  • Asignación de rangos CGNAT: en Netbox (IPAM), prefijo 100.64.X.0/24 asignado al tenant
  • Alertas: logs de tráfico bloqueado correlacionados con cliente en Grafana

4.5 Modelo de VMs: una VM = un servicio (caja negra)

El problema

Si varios servicios con BBDD comparten una VM:

  • Un técnico trabajando en Zammad puede afectar a Netbox
  • Diagnosticar "qué consume IO" es complejo cuando hay 5 servicios mezclados
  • Migrar un servicio a otro servidor requiere separarlo primero
  • El backup de la VM incluye todo, no puedes restaurar solo un servicio

DECISION 4.5 (confirmada)

Cada servicio con estado (BBDD) tiene su propia VM. Es una caja negra: todo lo que necesita está dentro (app + BBDD + Redis). Se backupea entera, se migra entera, se diagnostica de forma aislada.

Mapa de VMs por servicio

VM Contenido (todo Docker dentro) BBDD Stateful
VM Zammad Zammad + PostgreSQL + Redis + Elasticsearch PostgreSQL Sí, VM dedicada
VM Authentik Authentik + PostgreSQL + Redis PostgreSQL Sí, VM dedicada
VM Netbox Netbox + PostgreSQL + Redis PostgreSQL Sí, VM dedicada
VM Outline Outline + PostgreSQL + Redis PostgreSQL Sí, VM dedicada
VM ICSManager ICSManager + PostgreSQL (todo Docker) PostgreSQL Sí, VM dedicada
VM WildDuck WildDuck + MongoDB + Redis (todo Docker) MongoDB Sí, VM dedicada
VM Servicios Semaphore, Uptime Kuma, Technitium, Traefik Ninguna pesada Stateless/ligeros
VM Monitoring VictoriaMetrics, Loki, Grafana, Alertmanager Time-series Ya dedicada
VM Wazuh Wazuh Manager + Dashboard (OpenSearch) OpenSearch Ya dedicada

Ventajas operativas

Aspecto Servicios mezclados Caja negra por servicio
Diagnosticar "¿Qué de los 5 servicios consume IO?" "La VM Zammad consume IO → es Zammad"
Migrar Separar primero, luego mover Mover la VM entera
Backup/Restore Todo o nada Restauras solo Zammad sin tocar nada
Un técnico trabaja Con miedo de romper otros servicios Libertad total dentro de su VM
Actualizar Coordinar 5 servicios Actualizar uno, si falla, rollback VM
Escalar Dar más RAM a la VM afecta a todos Dar más RAM solo a Zammad

WildDuck y MongoDB

MongoDB dockerizado dentro de la VM WildDuck. De momento es correo interno del equipo técnico, volumen bajo. Si el correo se abre a más usuarios o el volumen crece significativamente, se revalúa:

  • MongoDB en VM dedicada (si el tamaño lo justifica)
  • O migración a servicio gestionado

Los tokens SMTP de WildDuck para avisos de PVE son operativamente críticos. El backup de la VM WildDuck los incluye automáticamente.

Impacto en dimensionamiento

La tabla actualizada de VMs:

VM vCPU RAM Disco
Monitoring 4 8-12 GB 200-500 GB
Wazuh 4 12 GB 100 GB
Zammad 2-3 4-6 GB 50 GB
Authentik 1-2 2-3 GB 20 GB
Netbox 1-2 2-3 GB 20 GB
ICSManager 1-2 2-3 GB 20 GB
WildDuck 1-2 2-3 GB 30-50 GB
PMG 1-2 2-3 GB 20 GB
PDM 1-2 1-2 GB 20 GB
Outline 1 1-2 GB 20 GB
Servicios (stateless) 1-2 2-3 GB 20 GB
Bastion 1 1 GB 20 GB
WG Hub A 1 512 MB 10 GB
WG Hub B 1 512 MB 10 GB
Forgejo exist. exist. exist.
Total ~24-32 vCPU ~44-58 GB ~580-910 GB

4.6 Proxmox Datacenter Manager (PDM)

El problema

Con múltiples PVE en distintos DCs (Francia, potencialmente otros), la gestión es por nodo o por cluster. Si un cliente necesita mover una VM de un PVE a otro (por mantenimiento, por rendimiento, o por fallo), hay que hacerlo manualmente conectándose a cada PVE.

DECISION 4.6 (confirmada)

PDM como panel único de gestión multi-cluster.

Proxmox Datacenter Manager permite:

  • Vista global de todos los nodos PVE y clusters en un solo panel
  • Migración de VMs entre clusters/nodos (lo que los clientes necesitan)
  • Gestión centralizada de recursos, storage, red
  • Planificación de capacidad a nivel de datacenter
                    ┌─────────────────────┐
                    │  PDM                 │
                    │  (panel único)       │
                    └──────┬──────────────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
        ┌─────▼─────┐ ┌───▼──────┐ ┌──▼────────┐
        │ PVE-1     │ │ PVE-2    │ │ PVE-3     │
        │ Francia   │ │ Francia  │ │ Alemania  │
        │ 20 VMs    │ │ 15 VMs   │ │ 10 VMs    │
        └───────────┘ └──────────┘ └───────────┘

El técnico ve todo desde PDM. Puede migrar VM-web
de PVE-1 a PVE-2 desde la misma interfaz.

Recursos

Componente CPU RAM Disco
PDM (VM dedicada) 1-2 vCPU 1-2 GB 20 GB

VM dedicada (patrón caja negra). Ligero: es solo plano de gestión, no compute.

SSO con Authentik. Accesible solo vía WG (red de gestión).


4.7 Proxmox Mail Gateway (PMG)

El problema

WildDuck gestiona el correo (buzones, IMAP, SMTP, tokens). Pero no tiene filtrado avanzado de spam ni antivirus. Si se abre el correo a más usuarios (más allá del equipo técnico), el volumen de spam y malware entrante será un problema.

DECISION 4.7 (confirmada)

PMG como gateway de filtrado delante de WildDuck.

Internet (puerto 25)
       │
       ▼
┌──────────────────────┐
│  PMG                  │
│  Proxmox Mail Gateway │
│                       │
│  - Anti-spam          │
│    (SpamAssassin)     │
│  - Anti-virus         │
│    (ClamAV)           │
│  - Greylisting        │
│  - SPF/DKIM/DMARC     │
│  - Cuarentena con UI  │
│  - Reglas por dominio │
│  - Reglas por usuario │
│                       │
│  Puerto 25 entrante   │
│  → filtra             │
│  → reenvía limpio a   │
│    WildDuck            │
└──────────┬───────────┘
           │ (email limpio)
           ▼
┌──────────────────────┐
│  WildDuck             │
│  (buzones, IMAP,      │
│   tokens SMTP PVE)    │
└──────────────────────┘

Funcionalidades clave

Función Qué hace
Anti-spam SpamAssassin + reglas custom, scoring por mensaje
Anti-virus ClamAV, escanea adjuntos entrantes y salientes
Greylisting Retrasa primer intento desde IP desconocida (mata el 80% del spam)
SPF/DKIM/DMARC Verifica autenticidad del remitente
Cuarentena UI web donde el usuario ve mensajes filtrados y puede liberar falsos positivos
Reglas por dominio Políticas diferentes por dominio gestionado
Reporting Estadísticas: spam filtrado, virus detectados, volumen

Email saliente

PMG también filtra salida: si un usuario comprometido envía spam, PMG lo detecta y bloquea antes de que salga y blacklistee la IP.

WildDuck (usuario envía)
       │
       ▼
┌──────────────────────┐
│  PMG                  │
│  - Verifica que no    │
│    sea spam saliente  │
│  - Firma DKIM         │
│  - Rate limiting      │
└──────────┬───────────┘
           │
           ▼
      Internet (destino)

Integración con el stack

Integración Qué aporta
PMG → Loki (logs) Logs de spam filtrado, virus detectados → visibles en Grafana
PMG → Alertmanager Alerta si el volumen de spam sube anómalamente
PMG → Wazuh Eventos de seguridad (virus detectados) correlacionados
PMG SSO Authentik Panel de administración con cuenta unificada
PMG cuarentena → usuario El usuario accede vía web a su cuarentena

Recursos

Componente CPU RAM Disco
PMG (VM dedicada) 1-2 vCPU 2-3 GB 20 GB

ClamAV consume la mayor parte de la RAM (~1-1.5 GB para las firmas). SpamAssassin ligero. VM dedicada siguiendo patrón caja negra.

Prioridades

Paso Qué Cuándo
Desplegar PMG con reglas básicas Cuando WildDuck se abra a más usuarios
Configurar DKIM signing Junto con el despliegue
Integrar logs PMG → Loki Semana 1 post-despliegue
Cuarentena accesible a usuarios Junto con el despliegue

No es urgente mientras WildDuck solo sirva al equipo técnico. Se despliega cuando se amplíe el correo.


Resumen Capa 4 completa (DECISIONES FINALES)

┌─────────────────────────────────────────────────────┐
│              CAPA 4: RED Y CONECTIVIDAD              │
├─────────────────────────────────────────────────────┤
│                                                      │
│  4.1 VPN (confirmada)                                │
│  ┌───────────────────────────────────┐               │
│  │  WireGuard + wgdashboard + IPv6   │               │
│  │                                    │               │
│  │  Rol 1: PBS = WG server (backups) │               │
│  │  - Peers: solo PVEs con storage   │               │
│  │  - Sin forwarding (aislados)      │               │
│  │                                    │               │
│  │  Rol 2: WG Hub HA (clientes)     │               │
│  │  - Par HA keepalived              │               │
│  │  - Tecnicos + clientes + VMs     │               │
│  │  - nftables aislamiento/cliente   │               │
│  │  - wgdashboard (UI + API)        │               │
│  │  - Reemplaza 1-GW-por-cliente    │               │
│  │                                    │               │
│  │  Rol 3: Mesh gestion (vRack)     │               │
│  │  - WG cifrado sobre vRack 25Gbps │               │
│  │  - Servicios internos solo aqui   │               │
│  │                                    │               │
│  │  Direccionamiento: IPv6 ULA       │               │
│  │  fdab:1234:bkp::/48  (backups)   │               │
│  │  fdab:1234:vpn::/48  (clientes)  │               │
│  │  fdab:1234:mgmt::/48 (gestion)   │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.2 DNS (confirmada)                                │
│  ┌───────────────────────────────────┐               │
│  │  PowerDNS Auth (ya existente)     │               │
│  │  - Zonas clientes (pri/sec)       │               │
│  │  - vpn6.com.es (nombres WG)      │               │
│  │  - PTR IPv6                       │               │
│  │  - ← Netbox + wgdashboard API    │               │
│  │                                    │               │
│  │  Technitium DNS (resolver nuevo)  │               │
│  │  - Resolver + bloqueo multi-tenant│               │
│  │  - Panel web por cliente          │               │
│  │  - Perfiles: malware/ads/adult    │               │
│  │  - Reenvio zonas internas → PDNS  │               │
│  │                                    │               │
│  │  Naming automatico:               │               │
│  │  - wgdashboard → PDNS API         │               │
│  │  - Netbox → netbox-dns → PDNS     │               │
│  │  - PVE → Netbox → DNS automatico │               │
│  │                                    │               │
│  │  Panel diagnostico (mes 3+)       │               │
│  │  - Self-service checks cliente    │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.3 PROXY INVERSO (confirmada)                      │
│  ┌───────────────────────────────────┐               │
│  │  Traefik (ya en uso, ampliar)     │               │
│  │  - Proxy inverso centralizado     │               │
│  │  - SSL Let's Encrypt automatico   │               │
│  │  - Clientes declaran servicio →   │               │
│  │    Traefik lo publica             │               │
│  │  - VMs sin IPv4 publica           │               │
│  │    (Traefik pone la cara publica) │               │
│  │  - Config YAML dinamica via       │               │
│  │    Ansible/API                     │               │
│  │  - Servicios internos solo via WG │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.4 SALIDA INTERNET NAT (confirmada)                │
│  ┌───────────────────────────────────┐               │
│  │  Dual-stack por VM:              │               │
│  │  - IPv4 CGNAT (100.64.x.x/24)   │               │
│  │    solo salida NAT, nunca entra  │               │
│  │  - IPv6 WG: acceso entrante     │               │
│  │                                    │               │
│  │  Política restrictiva:           │               │
│  │  - 80,443,53,123: permitido     │               │
│  │  - Puerto 25: BLOQUEADO siempre │               │
│  │  - 465,587: solo whitelist SMTP │               │
│  │  - Resto: bloqueado por defecto │               │
│  │                                    │               │
│  │  Producto base: IP compartida    │               │
│  │  (puede cambiar sin preaviso)   │               │
│  │  Premium: IP dedicada fija      │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.5 MODELO VMs (confirmada)                         │
│  ┌───────────────────────────────────┐               │
│  │  Una VM = un servicio (caja negra)│              │
│  │  App + BBDD + Redis todo dentro  │               │
│  │  - Zammad, Authentik, Netbox,    │               │
│  │    ICSManager, WildDuck, Outline │               │
│  │    cada uno en su VM dedicada    │               │
│  │  - Backup/migración: VM entera   │               │
│  │  - Diagnóstico aislado           │               │
│  │  - Total: ~24-32 vCPU, 44-58 GB │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.6 PDM (confirmada)                                │
│  ┌───────────────────────────────────┐               │
│  │  Proxmox Datacenter Manager      │               │
│  │  - Gestión multi-cluster PVE     │               │
│  │  - Migración VMs entre PVEs     │               │
│  │  - Vista global de recursos      │               │
│  │  - SSO Authentik, solo via WG    │               │
│  │  - VM dedicada: 1-2 vCPU, 1-2GB │               │
│  └───────────────────────────────────┘               │
│                                                      │
│  4.7 PMG (confirmada, desplegar al ampliar correo)   │
│  ┌───────────────────────────────────┐               │
│  │  Proxmox Mail Gateway            │               │
│  │  - Anti-spam (SpamAssassin)      │               │
│  │  - Anti-virus (ClamAV)           │               │
│  │  - SPF/DKIM/DMARC               │               │
│  │  - Greylisting                   │               │
│  │  - Cuarentena con UI web        │               │
│  │  - Filtro saliente (anti-spam)  │               │
│  │  - Delante de WildDuck          │               │
│  │  - VM dedicada: 1-2 vCPU, 2-3GB │               │
│  └───────────────────────────────────┘               │
│                                                      │
└─────────────────────────────────────────────────────┘

Recursos Capa 4:
- WG Hub A: 1 vCPU, 512 MB RAM, 10 GB disco
- WG Hub B: 1 vCPU, 512 MB RAM, 10 GB disco
- Technitium DNS: 1 vCPU, 512 MB - 1 GB RAM (en VM Servicios)
- Traefik: contenedor Docker, 256-512 MB RAM (en VM Servicios)
- PDM: 1-2 vCPU, 1-2 GB RAM, 20 GB disco
- PMG: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
- PowerDNS: ya existente
- WG en servidores: kernel module, 0 extra
- NAT CGNAT: en WG Hub (nftables, 0 extra)

Dimensionamiento revisado (todas las VMs de gestión):
  ~24-32 vCPU, ~44-58 GB RAM, ~580-910 GB disco
  Recomendación servidor: 128 GB RAM, 8-12c/16-24t, 2×2TB NVMe