Redes, seguridad, virtualización y frontend
Periodo: 02 mar → 28 abr 2026
Definición del proyecto
Arrancamos con la planificación conjunta del proyecto. El objetivo es diseñar e implantar desde cero la infraestructura tecnológica de Zitadel Inmobiliaria, una agencia ficticia ubicada en Vigo. En esta primera semana definimos juntas la arquitectura general, el reparto de áreas de trabajo y el contexto empresarial que justifica todas las decisiones técnicas.
Mi área principal cubre la capa de red y seguridad: diseñar la topología, segmentar las redes mediante VLANs, configurar el firewall y garantizar que todo el tráfico esté controlado y cifrado. Además me encargo de la instalación y configuración de Proxmox como base de toda la infraestructura, y del frontend de WordPress.
Diseño de la arquitectura de red: VLANs y segmentación
En esta fase diseño la red de Zitadel. El principio guía es que no todo el mundo puede hablar con todo el mundo: cada zona de la infraestructura está aislada de las demás, y el tráfico entre ellas pasa obligatoriamente por el firewall.
Segmentación por VLANs
Definimos cuatro zonas de confianza diferenciada. Todo el tráfico entre ellas pasa por OPNsense a través del bridge vmbr1 de Proxmox, configurado en modo VLAN-aware:
| VLAN | Nombre | Rango | Uso |
|---|---|---|---|
| 10 | Management | 10.0.10.0/24 | Central-Node, Teampass |
| 20 | Servers | 10.0.20.0/24 | Zabbix |
| 30 | DMZ | 10.0.30.0/24 | WordPress, Traefik |
| 40 | Database | 10.0.40.0/24 | MariaDB |
| 50 | Usuarios | 10.0.50.0/24 | Equipos de empleados |
Configuración del bridge en Proxmox
El bridge vmbr1 no tiene puerto físico asignado, lo que obliga a que todo el tráfico entre VLANs pase por OPNsense sin posibilidad de cortocircuito:
# /etc/network/interfaces — host Proxmox
auto vmbr1
iface vmbr1 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
bridge-vlan-aware yes
bridge-vids 2-4094
Instalación de Proxmox VE y optimización del hardware
Toda la infraestructura corre sobre un único servidor físico: un Dell OptiPlex 3050 SFF con Intel Core i5-7400, 8 GB de RAM DDR4 y un HDD de 4 TB. Proxmox VE es el hipervisor que permite crear y gestionar todos los contenedores y máquinas virtuales dentro de ese hardware, cada uno con sus propios recursos y completamente aislado del resto.
Optimización de memoria: KSM
Al contar con solo 8 GB de RAM para todos los servicios, la primera medida fue activar KSM (Kernel Samepage Merging). Esta tecnología detecta páginas de memoria idénticas entre máquinas (por ejemplo, varias instancias de Debian con el mismo sistema base) y las fusiona en una sola página física. En la práctica, conseguimos liberar 1,78 GiB de RAM adicionales que de otro modo habrían estado duplicados.
Gestión del almacenamiento
Configuramos tres tipos de almacenamiento diferenciados en Proxmox:
- LVM-Thin (local-lvm, HDD 250 GB): almacén de contenedores y VMs. Solo ocupa espacio físico a medida que se llenan, lo que evita desperdiciar disco reservando capacidad que no se usa.
- Directorio (HDD 4 TB): almacén de ISOs, plantillas de contenedor y backups del proyecto, fuera del disco del sistema.
- Google Drive (15 GB): copia externa en la nube, cumpliendo la tercera pata de la estrategia 3-2-1.
Segregación de acceso con RBAC
Como el servidor físico era de uso personal previo al proyecto, aplicamos control de acceso por roles para que Joseanía solo pudiera ver y gestionar las máquinas de Zitadel: creamos un pool llamado Zitadel, movimos todas las VMs del proyecto a ese pool, y asignamos el usuario josi@pve con rol de administrador únicamente sobre ese pool. Al entrar, solo ve las máquinas del proyecto sin acceso a la configuración del nodo ni posibilidad de apagarlo.
Firewall con OPNsense
OPNsense es el núcleo de la seguridad de red de Zitadel. Es una máquina virtual con dos interfaces: una conectada al exterior (WAN → vmbr0) y otra al bridge interno VLAN-aware (LAN trunk → vmbr1). Todo el tráfico entre zonas pasa por aquí sin excepción.
VLANs configuradas en OPNsense
Creo una subinterfaz por cada VLAN sobre vtnet1 y le asigno su IP de gateway. OPNsense actúa también como servidor DHCP para todas las zonas, con rangos diferenciados. Los servidores tienen IPs estáticas asignadas manualmente.
Reglas de firewall más importantes
- VLAN 10 (Management): acceso libre hacia cualquier zona, ya que el Central-Node necesita llegar a todas las máquinas para administrarlas y desplegarlas.
- VLAN 30 (DMZ): puertos 80 y 443 abiertos al exterior. Permite conexión al puerto 3306 de la VLAN 40.
- VLAN 40 (Database): solo acepta el puerto 3306 desde la DMZ. Bloqueada para todo lo demás.
- Regla de oro: ninguna zona puede llegar a la interfaz de administración del firewall sin permiso explícito. Si un servidor es comprometido, el atacante no puede saltar al panel de control.
DNS interno con dominio zitadel.local
OPNsense resuelve el dominio interno zitadel.local para todos los sistemas. Esto permite que las máquinas se comuniquen entre sí por nombre en lugar de por dirección IP: si una máquina cambia de IP, solo hay que actualizar el DNS y el resto sigue funcionando sin tocar nada más.
Acceso remoto seguro con WireGuard en contenedor LXC
Para poder administrar la infraestructura desde casa sin exponer el puerto 8006 de Proxmox a internet, implementé un servidor WireGuard dentro de un contenedor LXC ligero. La elección de LXC en lugar de una VM completa responde a los recursos limitados del servidor: un contenedor consume mucha menos RAM y CPU para un servicio tan concreto como una VPN.
¿Por qué WireGuard?
WireGuard tiene una base de código de aproximadamente 4.000 líneas frente a las más de 100.000 de OpenVPN. Menos código significa menos superficie de ataque. El cifrado moderno basado en ChaCha20 y Curve25519 ofrece mejor rendimiento y seguridad que las soluciones clásicas, y la configuración es mucho más sencilla de mantener.
Configuración del servidor (wg0.conf)
El servidor escucha en el puerto 51820 y asigna IPs en la subred 10.139.65.0/24. Cada cliente (mi equipo y el de Joseanía) tiene su propio par de clave pública/privada y una IP fija dentro del túnel:
[Interface]
PrivateKey = ***
Address = 10.139.65.1/24
MTU = 1420
ListenPort = 51820
### begin VPN-Wireguard ###
[Peer] # Mi equipo
PublicKey = fPi04VW+fcRBFeWMj+2u2p1Hlok...
PresharedKey = ***
AllowedIPs = 10.139.65.2/32
### end VPN-Wireguard ###
### begin josi ###
[Peer] # Equipo de Joseanía
PublicKey = fcRBFeWMj1Hlok+6zng+2u2p82e...
PresharedKey = ***
AllowedIPs = 10.139.65.3/32
### end josi ###
Qué conseguimos
- El puerto 8006 de Proxmox nunca queda expuesto a internet.
- Joseanía puede acceder a toda la infraestructura desde su casa como si estuviera en la misma red local.
- Todo el tráfico de administración viaja cifrado extremo a extremo, incluso desde redes públicas.
- Las claves privadas están anonimizadas en la documentación por seguridad.
De VMs a contenedores LXC: optimizando el Terraform
Una decisión importante que tomamos durante el proyecto fue migrar el despliegue de máquinas virtuales completas a contenedores LXC. La razón principal es el hardware: con un i5-7400 y 8 GB de RAM, ejecutar siete VMs con sistemas operativos completos era inasumible. Los contenedores LXC comparten el kernel del host de Proxmox, por lo que consumen una fracción de los recursos de una VM equivalente.
Diferencia en el código Terraform
El cambio en Terraform es directo: en lugar de proxmox_virtual_environment_vm, usamos proxmox_virtual_environment_container, con un bloque operating_system que apunta a la plantilla de Debian 13 descargada en Proxmox:
# Contenedor LXC — ejemplo con Zabbix
resource "proxmox_virtual_environment_container" "zabbix" {
name = "Zabbix"
node_name = "pve"
vm_id = 202
unprivileged = true
cpu { cores = 1 }
memory { dedicated = 512 }
disk {
datastore_id = "local-lvm"
size = 8
}
network_interface {
name = "eth0"
bridge = "vmbr1"
vlan_id = 20
}
operating_system {
template_file_id = "HDD-4TB:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst"
type = "debian"
}
initialization {
hostname = "zabbix"
ip_config {
ipv4 {
address = "10.0.20.202/24"
gateway = "10.0.20.1"
}
}
user_account {
password = var.ad_password
keys = [file("~/.ssh/id_rsa.pub")]
}
}
features { nesting = true }
start_on_boot = true
}
Contenedores desplegados
| ID | Nombre | RAM | Disco | VLAN | IP |
|---|---|---|---|---|---|
| 202 | Zabbix | 512 MB | 8 GB | 20 | 10.0.20.202 |
| 203 | WordPress | 768 MB | 10 GB | 30 | 10.0.30.203 |
| 204 | MariaDB | 512 MB | 10 GB | 40 | 10.0.40.204 |
| 205 | Teampass | 512 MB | 8 GB | 10 | 10.0.10.205 |
| 206 | Traefik | 256 MB | 4 GB | 30 | 10.0.30.206 |
nesting = true en Terraform. Sin él, Docker no puede crear sus propias redes internas dentro del contenedor LXC y falla al arrancar.Frontend WordPress: tema, contenidos y personalización visual
Con el backend de WordPress montado por Joseanía, me encargo de la parte visible: el portal que ven los clientes de Zitadel. El objetivo es que sea profesional, fácil de navegar y que refleje la identidad de la empresa.
Tema instalado
Se instaló el tema Residential Real Estate, elegido por su diseño específico para catálogos inmobiliarios. Incluye sistema de listado de propiedades, filtros de búsqueda por tipo, precio y ubicación, y galerías fotográficas por inmueble.
Adaptaciones realizadas
- Logotipo: integración del logo de Zitadel en la cabecera.
- Colores corporativos: ajuste en botones, menús y llamadas a la acción para coherencia visual.
- Páginas principales: Inicio, Sobre Nosotros, Catálogo de inmuebles, Galería y Contacto.
- URLs amigables: configuración de enlaces permanentes (
/quienes-somos/en lugar de/?p=15) para mejorar el SEO y la legibilidad. - Feed RSS: habilitado en
/feed/para que los clientes reciban novedades automáticamente cuando se publica un inmueble nuevo.
Usuarios y roles en WordPress
| Rol | Responsabilidades |
|---|---|
| Administrador | Gestión total: plugins, configuración, temas y backups |
| Editor | Puede publicar, editar y borrar cualquier inmueble |
| Autor | Solo gestiona sus propios inmuebles; no toca el contenido de otros |
Dificultad principal: error 404 en URLs amigables
Al activar los enlaces permanentes, Apache no reconocía las rutas y devolvía error 404. Para resolverlo fue necesario entrar en el contenedor, activar AllowOverride All en la configuración de Apache y generar manualmente el .htaccess con las reglas de reescritura de WordPress. El bloqueo de instalación de plugins por FTP se resolvió añadiendo define('FS_METHOD', 'direct') en el wp-config.php.
Mi parte en Ansible: playbooks e integración con la infraestructura
Ansible es la herramienta que configura todas las máquinas una vez que Terraform las ha creado. El trabajo en Ansible fue conjunto con Joseanía, pero yo me encargué específicamente de que los playbooks funcionasen correctamente contra los contenedores LXC (que tienen algunas diferencias respecto a VMs completas) y de validar que la conectividad entre zonas funcionase antes de ejecutarlos.
Gestión de credenciales con Ansible Vault
Las contraseñas de los playbooks se guardan cifradas con Ansible Vault en secrets.yml. Teampass actúa como repositorio centralizado para que el equipo consulte esas credenciales desde su interfaz web, evitando que aparezcan en texto plano en el repositorio:
# Crear el archivo cifrado
ansible-vault create ~/infraestructura/ansible/vars/secrets.yml
# Editarlo en cualquier momento
ansible-vault edit ~/infraestructura/ansible/vars/secrets.yml
# Ejecutar el playbook descifrando al vuelo
ansible-playbook playbook.yml --ask-vault-pass
Ajustes específicos para contenedores LXC
Los contenedores LXC no tienen el agente QEMU, así que la conexión SSH de Ansible se establece directamente por IP. El inventario refleja esto y desactiva la verificación de host para el primer contacto:
[all:vars]
ansible_user = admin
ansible_ssh_private_key_file = ~/.ssh/id_rsa
ansible_ssh_extra_args = '-o StrictHostKeyChecking=no'
[wordpress]
wordpress ansible_host=10.0.30.203
[mariadb]
mariadb ansible_host=10.0.40.204
[zabbix]
zabbix ansible_host=10.0.20.202
[teampass]
teampass ansible_host=10.0.10.205
nesting no estaba activado en Proxmox. Esto se soluciona en el propio Terraform (como se ve en la entrada anterior), pero fue necesario detectarlo durante las pruebas de Ansible para volver atrás y corregirlo en la definición de los contenedores.Traefik como reverse proxy y certificado Let’s Encrypt real
Hoy hemos conseguido que todo el tráfico público entre por Traefik y que el certificado HTTPS deje de aparecer como no seguro. Era uno de los flecos más visibles del proyecto y llevaba un rato dando guerra.
El problema de partida
El NAT de OPNsense estaba apuntando los puertos 80 y 443 directamente a la VM de WordPress en lugar de a Traefik. Eso hacía que el tráfico llegara a Apache saltándose el reverse proxy completamente. Una vez corregido el Destination NAT para que apuntara a 10.0.30.206 (Traefik), el flujo quedó como tiene que estar:
- Internet → OPNsense → Traefik (10.0.30.206) → WordPress (10.0.30.203)
Configuración de Traefik
Traefik corre en su propio contenedor LXC en la DMZ y usa un archivo de configuración estático (traefik.yml) más uno dinámico (dynamic.yml) donde se define el router hacia WordPress. Let’s Encrypt obtiene el certificado automáticamente mediante HTTP challenge y lo renueva solo:
# traefik.yml — configuración estática
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: admin@zitadel.a24iriabc.iesteis.gal
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
file:
filename: /etc/traefik/dynamic.yml
# dynamic.yml — router y servicio
http:
routers:
wordpress:
rule: "Host(`zitadel.a24iriabc.iesteis.gal`)"
entryPoints: [ websecure ]
service: wordpress
middlewares: [ https-forward ]
tls:
certResolver: letsencrypt
middlewares:
https-forward:
headers:
customRequestHeaders:
X-Forwarded-Proto: https
services:
wordpress:
loadBalancer:
servers:
- url: "http://10.0.30.203:80"
Ajuste en WordPress
Al poner Traefik por delante, Apache ya no tiene que gestionar el HTTPS. Se desactivó el sitio SSL y se dejó que Apache solo escuche en el puerto 80, respondiendo HTTP plano internamente. Traefik termina el TLS y pasa el tráfico ya descifrado. Para que WordPress supiera que la conexión original era HTTPS, se añadió el middleware X-Forwarded-Proto: https y se reactivó el módulo rewrite de Apache, que es el que permite las URLs amigables.
Resultado
- Certificado Let’s Encrypt válido, emitido por R13 de Let’s Encrypt, sin advertencias en el navegador.
- Redirección HTTP → HTTPS gestionada por Traefik, no por Apache.
- WordPress solo accesible a través del reverse proxy, nunca directamente desde internet.
- URLs amigables funcionando en todas las páginas del portal.
curl al dominio aparecía Server: Apache/2.4.66 en la cabecera de respuesta. Eso probaba que Traefik no estaba en la ruta, porque si lo estuviera no se vería la cabecera de Apache directamente.
