Plan2026/07-decision-red.md

1148 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:1234: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)
```bash
# 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)
```yaml
# /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:
```yaml
# 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)
```nft
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
```nft
# 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:
```nft
# 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
```