Firewall, usuarios, permisos y acceso seguro
Firewall, usuarios, permisos y acceso seguro
En el capítulo 9 dejaste el nodo con bridges, VLANs, bonding y quizá una zona SDN funcionando. Todo el tráfico circula. Ese es exactamente el problema: circula todo, desde cualquier sitio, hacia cualquier sitio, y el puerto 8006 con la API completa del hipervisor está escuchando en el wildcard.
Este capítulo cierra las dos puertas que quedan abiertas. La primera es de red: el firewall distribuido de Proxmox VE, que corre en cada nodo pero se configura desde el sistema de ficheros del clúster, y que tiene un mecanismo de activación en cascada que hace tropezar a todo el mundo la primera vez. La segunda es de identidad: quién es cada usuario, de dónde viene su autenticación, qué puede tocar exactamente y con qué segundo factor.
Al final tendrás un nodo donde el acceso de administración solo llega desde las IPs que tú decides, con un certificado válido, con doble factor obligatorio, con un token de API para automatización que no puede hacer más de lo que necesita, y con la capacidad de depurar por qué un paquete concreto se está cayendo.
La referencia es Proxmox VE 9.2 sobre Debian 13 Trixie. Todos los defaults y rutas de este capítulo salen de la documentación de la serie 9.2.
Advertencia antes de empezar: la propia documentación de Proxmox lo dice con todas sus letras: “If you enable the firewall, traffic to all hosts will be blocked by default”. Y añade el consejo operativo: “Please open a SSH connection to one of your Proxmox VE hosts before enabling the firewall”. Abre esa sesión SSH ahora y no la cierres hasta terminar el capítulo. Si tienes IPMI o iKVM, tenlo a mano también.
Arquitectura: un firewall distribuido, no un appliance
El firewall de Proxmox VE no es una caja central por la que pasa el tráfico. La documentación lo describe así:
“While all configuration is stored on the cluster file system, the iptables-based firewall service runs on each cluster node, and thus provides full isolation between virtual machines. The distributed nature of this system also provides much higher bandwidth than a central firewall solution.”
Dos consecuencias prácticas. Uno. La configuración vive en /etc/pve, que es pmxcfs y por tanto se replica a todos los nodos del clúster automáticamente: escribes una regla en un nodo y aparece en los demás. Dos. La aplicación es local: cada nodo compila esa configuración a su propio ruleset de iptables. No hay cuello de botella ni punto único de fallo en el plano de datos.
IPv6 no necesita un juego de reglas aparte:
“The firewall has full support for IPv4 and IPv6. IPv6 support is fully transparent, and we filter traffic for both protocols by default. So there is no need to maintain a different set of rules for IPv6.”
Cada nodo corre dos daemons: pve-firewall, que compila y aplica las reglas, y pvefw-logger, un daemon NFLOG que sustituye a ulogd y escribe el log de paquetes.
Direcciones y zonas
Antes de escribir una regla hay que saber dónde se aplica. Las direcciones son tres: In es el tráfico que llega a una zona, Out el que sale de ella, y Forward el que la atraviesa (en la zona host puede ser tráfico enrutado como gateway o NAT; a nivel VNet afecta a todo lo que pasa por la VNet, incluidas las NICs bridgeadas).
Gotcha inmediato con Forward: la documentación es tajante: “Creating rules for forwarded traffic is currently only possible when using the new nftables-based proxmox-firewall. Any forward rules will be ignored by the stock pve-firewall and have no effect!”. Si escribes reglas FORWARD con el backend por defecto no fallan, simplemente no hacen nada. Es un silencio caro.
Las zonas también son tres:
| Zona | Detalle |
|---|---|
Host | Tráfico desde, hacia o reenviado por un host. Las reglas se definen a nivel datacenter o a nivel host, y las de nivel host tienen precedencia sobre las de datacenter |
VM | Tráfico desde o hacia una VM o CT. No se pueden definir reglas de tráfico reenviado, solo In y Out |
VNet | Tráfico que pasa por una VNet del SDN, tanto guest a guest como host a guest. Como siempre es forwarded, solo admite reglas con dirección forward, y requiere proxmox-firewall |
Los tres niveles, sus ficheros y el gotcha de los tres flags
Aquí es donde se pierde la mayoría de la gente. El firewall se habilita en cascada, y cada nivel tiene un default distinto.
| Nivel | Fichero exacto | enable por defecto | Secciones válidas |
|---|---|---|---|
| Datacenter | /etc/pve/firewall/cluster.fw | 0 | [OPTIONS], [RULES], [IPSET <name>], [GROUP <name>], [ALIASES] |
| Nodo | /etc/pve/nodes/<nodename>/host.fw | 1 | [OPTIONS], [RULES] |
| VM o CT | /etc/pve/firewall/<VMID>.fw | 0 | [OPTIONS], [RULES], [IPSET <name>], [ALIASES] |
| VNet | /etc/pve/sdn/firewall/<vnet_name>.fw | 0 | [OPTIONS], [RULES] solo con FORWARD |
El formato es de pares clave-valor agrupados por secciones. Las líneas que empiezan por # y las líneas en blanco son comentarios; las cabeceras de sección van entre corchetes.
El nivel de nodo viene activo por defecto, pero está subordinado a que el datacenter esté activo. Y el nivel VNet solo existe con el backend nftables.
flowchart TD
DC["/etc/pve/firewall/cluster.fw<br/>Datacenter - enable 0 por defecto"] --> NODE["/etc/pve/nodes/nodo/host.fw<br/>Nodo - enable 1 por defecto"]
NODE --> GUEST["/etc/pve/firewall/VMID.fw<br/>VM o CT - enable 0 por defecto"]
GUEST --> NIC["netN con firewall=1<br/>Flag por interfaz del guest"]
NIC --> EFECTO["La regla del guest surte efecto"]
VNET["/etc/pve/sdn/firewall/vnet.fw<br/>Solo con backend nftables"] -.-> EFECTO
Los tres flags
Para que una regla de VM tenga efecto hacen falta tres cosas a 1, no una:
Uno. enable: 1 en /etc/pve/firewall/cluster.fw. Dos. enable: 1 en /etc/pve/firewall/<VMID>.fw.
# /etc/pve/firewall/cluster.fw
[OPTIONS]
# enable firewall (cluster-wide setting, default is disabled)
enable: 1
# /etc/pve/firewall/100.fw
[OPTIONS]
enable: 1
policy_in: DROP
[RULES]
IN SSH(ACCEPT) -i net0 -source +management
IN HTTP(ACCEPT) -i net0
Tres. firewall=1 en la NIC correspondiente del guest. La documentación lo dice literalmente: “Each virtual network device has its own firewall enable flag. So you can selectively enable the firewall for each interface. This is required in addition to the general firewall enable option.”
qm set 100 --net0 virtio,bridge=vmbr0,firewall=1
pct set 200 --net0 name=eth0,bridge=vmbr0,ip=10.50.0.10/24,gw=10.50.0.1,firewall=1
El síntoma clásico del error es: “creé reglas DROP en la VM y no bloquean nada”. Casi siempre falta el flag de la NIC o el enable de datacenter. Verificación rápida:
grep -A2 '\[OPTIONS\]' /etc/pve/firewall/cluster.fw
grep -A5 '\[OPTIONS\]' /etc/pve/firewall/100.fw
grep '^net' /etc/pve/qemu-server/100.conf
pve-firewall status ; iptables-save | grep -c 'PVEFW'
Opciones completas por nivel y protecciones
Las opciones que puedes poner en [OPTIONS] cambian según el nivel. Estas son las que importan.
En cluster.fw: enable (entero 0-N, default 0), ebtables (booleano, default 1, habilita reglas ebtables en todo el clúster), policy_in y policy_out (ACCEPT, DROP o REJECT), policy_forward (ACCEPT o DROP) y log_ratelimit, con la forma [enable=]<1|0> [,burst=<integer>] [,rate=<rate>], donde enable es 1 por defecto, burst es 5 y rate es 1/second. El burst es el número inicial de paquetes que siempre se loguean antes de aplicar el rate limit.
En host.fw:
| Opción | Valores y default |
|---|---|
enable | booleano, default 1 |
nftables | booleano, default 0. Habilita el firewall basado en nftables, marcado como tech preview |
ndp | booleano, default 1 |
nf_conntrack_max | entero >= 32768, default 262144 |
nf_conntrack_tcp_timeout_established | entero >= 7875, default 432000 |
nf_conntrack_tcp_timeout_syn_recv | entero 30-60, default 60 |
nf_conntrack_allow_invalid | booleano, default 0 |
nf_conntrack_helpers | cadena, vacío por defecto. Soporta amanda, ftp, irc, netbios-ns, pptp, sane, sip, snmp, tftp |
log_nf_conntrack | booleano, default 0 |
protection_synflood | booleano, default 0 |
protection_synflood_burst | entero, default 1000 |
protection_synflood_rate | entero, default 200 SYN por segundo y por IP origen |
tcpflags | booleano, default 0. Filtra combinaciones ilegales de flags TCP |
nosmurfs | booleano. Habilita el filtro SMURFS |
smurf_log_level / tcp_flags_log_level | nivel de log de esos filtros |
log_level_in / log_level_out / log_level_forward | alert, crit, debug, emerg, err, info, nolog, notice, warning |
Las tres protecciones que valen la pena activar en un nodo expuesto son protection_synflood, tcpflags y nosmurfs. Ninguna viene activa.
# /etc/pve/nodes/pve1/host.fw
[OPTIONS]
enable: 1
protection_synflood: 1
protection_synflood_rate: 200
protection_synflood_burst: 1000
tcpflags: 1
nosmurfs: 1
log_level_in: info
Nota sobre conntrack: nf_conntrack_max a 262144 es un default generoso para un hipervisor, pero si corres cientos de contenedores con mucho tráfico corto lo vas a agotar. nf_conntrack_tcp_timeout_established a 432000 segundos son cinco días: cada conexión TCP establecida ocupa una entrada durante ese tiempo aunque esté inactiva.
En <VMID>.fw los defaults son enable: 0, macfilter: 1, ndp: 1 y dhcp: 0, más radv (permite al guest enviar Router Advertisement), ipfilter (equivale a añadir un ipset ipfilter-net<id> vacío por cada interfaz), policy_in/policy_out y log_level_in/log_level_out. Que macfilter venga a 1 significa que el guest no puede falsificar su MAC; ipfilter es el equivalente a nivel IP, y si existe el set para una interfaz, todo tráfico saliente con IP origen distinta se descarta.
En <vnet>.fw solo hay tres opciones: enable (default 0), policy_forward (ACCEPT o DROP) y log_level_forward. Y como el tráfico de la cadena FORWARD es bidireccional, hay que escribir las dos direcciones:
[RULES]
FORWARD ACCEPT -dest 10.0.0.1 -dport 80
FORWARD ACCEPT -source 10.0.0.1 -sport 80
Sintaxis de reglas, macros, grupos, alias e IP sets
La forma general de una regla:
[RULES]
DIRECTION ACTION [OPTIONS]
|DIRECTION ACTION [OPTIONS] # regla deshabilitada
DIRECTION MACRO(ACTION) [OPTIONS]
DIRECTION es IN, OUT o FORWARD. ACTION es ACCEPT, DENY o REJECT. El prefijo | deshabilita la regla sin borrarla, que es mucho mejor que comentarla porque la GUI la sigue mostrando.
Opciones de refinamiento:
| Opción | Detalle |
|---|---|
--source / --dest | IP única, IP set con +nombre, alias, rango 20.34.101.207-201.3.9.99, o lista separada por comas. No mezcles IPv4 e IPv6 en la misma lista |
--sport / --dport | nombres de servicio o números 0-65535 según /etc/services; rangos tipo 80:85; listas separadas por comas |
--proto | nombres como tcp o udp, o números según /etc/protocols |
--icmp-type | solo si proto es icmp, icmpv6 o ipv6-icmp |
--iface | en VMs y CTs hay que usar los nombres de clave de la config, es decir net0, net1. En reglas de host se admite cualquier cadena |
--log | nivel de log de esa regla concreta |
Ejemplos tal cual aparecen en la documentación:
[RULES]
IN SSH(ACCEPT) -i net0 -source 192.168.2.192 # only allow SSH from 192.168.2.192
IN SSH(ACCEPT) -i net0 -source 10.0.0.1-10.0.0.10 # accept SSH for IP range
IN SSH(ACCEPT) -i net0 -source +mynetgroup # accept ssh for ipset mynetgroup
IN SSH(ACCEPT) -i net0 -source myserveralias #accept ssh for alias myserveralias
|IN SSH(ACCEPT) -i net0 # disabled rule
IN DROP # drop all incoming packages
OUT ACCEPT # accept all outgoing packages
Security groups
Un grupo de seguridad es un conjunto de reglas definido a nivel clúster y reutilizable en cualquier guest. Se define en cluster.fw y se invoca desde el guest con una sola línea, así no repites el mismo bloque en cuarenta ficheros:
# /etc/pve/firewall/cluster.fw
[group webserver]
IN ACCEPT -p tcp -dport 80
IN ACCEPT -p tcp -dport 443
# /etc/pve/firewall/<VMID>.fw
[RULES]
GROUP webserver
Alias y local_network
Un alias asocia un nombre a una IP o red, y se puede usar tanto en source/dest como dentro de un IP set. Proxmox define uno automáticamente: local_network. Se inspecciona con pve-firewall localnet, que devuelve algo así:
local hostname: example
local IP address: 192.168.2.100
network auto detect: 192.168.0.0/20
using detected local_network: 192.168.0.0/20
“The firewall automatically sets up rules to allow everything needed for cluster communication (corosync, API, SSH) using this alias.”
Gotcha en hosts con IP pública directa: la autodetección puede acabar considerando “red local” un /20 público entero del datacenter del proveedor. Ahí conviene sobrescribirlo a mano:
# /etc/pve/firewall/cluster.fw
[ALIASES]
local_network 1.2.3.4 # use the single IP address
IP sets
Los IP sets se referencian con +nombre, por ejemplo IN HTTP(ACCEPT) -source +management. Hay tres estándar con semántica especial.
management. Aplica solo a los firewalls de host, no a los de VM. Las IPs que contiene tienen permitidas las tareas normales de administración: GUI, VNC, SPICE y SSH. La red del clúster se añade automáticamente vía el alias cluster_network. Es la forma oficialmente recomendada de dar acceso remoto a la interfaz web: “To simplify that task, you can instead create an IPSet called ‘management’, and add all remote IPs there. This creates all required firewall rules to access the GUI from remote.”
blacklist. El tráfico desde esas IPs se descarta en el firewall de todos los hosts y de todas las VMs.
# /etc/pve/firewall/cluster.fw
[IPSET management]
192.168.2.10
192.168.2.10/24
[IPSET blacklist]
77.240.159.182
213.87.123.0/24
ipfilter-net<n>. Anti IP-spoofing por interfaz de guest, y este vive en el fichero del guest. Si existe el set para una interfaz, cualquier tráfico saliente cuya IP origen no coincida se descarta. Para contenedores con IPs configuradas el set las contiene implícitamente; para VMs y CTs contiene además la IPv6 link-local derivada de la MAC, para que NDP siga funcionando.
# /etc/pve/firewall/<VMID>.fw
[IPSET ipfilter-net0] # only allow specified IPs on net0
192.168.2.10
Si usas SDN, el firewall genera automáticamente IP sets por VNet: <vnet>-all, <vnet>-gateway, <vnet>-no-gateway y <vnet>-dhcp, además de guest-ipam-<VMID> cuando usas el IPAM pve. Los viste en el capítulo 9. Advertencia: si borras todas las entradas IPAM de un guest y quedan reglas que referencian su IP set, el firewall falla al actualizar el ruleset entero, porque apunta a un set inexistente.
Macros y los puertos que usa Proxmox VE
Las macros son atajos con puertos ya rellenados. Se usan como IN MACRO(ACTION) [opciones]. La lista completa está en el apéndice de macros de la documentación; estas son las que vas a usar en un hipervisor:
| Macro | Puertos |
|---|---|
SSH | tcp/22 |
HTTP | tcp/80 |
HTTPS | tcp/443 |
Web | tcp/80 y tcp/443 |
PMG | tcp/8006 |
SPICEproxy | tcp/3128 |
VNC | tcp/5900:5999 |
Ceph | tcp/6789, tcp/3300, tcp/6800:7300 |
DNS | udp/53 y tcp/53 |
NTP | udp/123 |
Ping | icmp echo-request |
NeighborDiscovery | ICMPv6 de router y neighbor solicitation y advertisement |
Trcrt | udp/33434:33524 e icmp echo-request |
SMB | udp/135, udp/445, udp/137:139, tcp/135, tcp/139, tcp/445 |
Gotcha: no existe una macro PVE para el puerto 8006. La que lo cubre es PMG, porque Proxmox Mail Gateway usa el mismo puerto. Es funcionalmente correcta pero el nombre despista al leer las reglas, así que conviene comentarla.
Los puertos que usa Proxmox VE, según la lista oficial: 8006 TCP para la interfaz web (HTTP/1.1 sobre TLS), 5900-5999 TCP para la consola VNC web por WebSocket, 3128 TCP para el SPICE proxy, 22 TCP para el sshd de las acciones de clúster, 111 UDP para rpcbind, 25 TCP saliente para sendmail, 5405-5412 UDP para el tráfico de corosync y 60000-60050 TCP para la migración en vivo de memoria y disco local.
Ese rango 60000-60050 es el que se usa en las migraciones del capítulo 11; si lo bloqueas entre nodos, las migraciones fallan sin un mensaje demasiado claro.
Qué se permite y qué se descarta al activar el firewall
Con el firewall activo y policy_in/policy_out en DROP o REJECT, no todo se cae. Siguen permitidos: el tráfico por loopback, las conexiones ya establecidas, el protocolo IGMP, el multicast UDP de la red del clúster, UDP a los puertos 5405-5412 en la red del clúster para corosync, ICMP tipos 3 (Destination Unreachable), 4 (congestion control) y 11 (Time Exceeded), y desde los management hosts el TCP al 8006 (interfaz web), al rango 5900-5999 (consola VNC web), al 3128 (SPICE proxy), al 22 (SSH) y al rango 60000-60050 (migraciones).
Y hay un conjunto que se descarta sin loguear, aunque tengas el log activado: las conexiones TCP con estado inválido; el broadcast, multicast y anycast no relacionado con corosync, es decir fuera de 5405-5412; el TCP al puerto 43; el UDP a los puertos 135 y 445; el UDP al rango 137-139; el UDP con puerto origen 137 hacia el rango 1024-65535; el UDP al puerto 1900; el TCP a los puertos 135, 139 y 445; y el UDP originado en el puerto origen 53.
Todo lo demás se descarta o rechaza y sí se loguea. Si estás depurando por qué no ves nada en el log de un paquete SMB o de un UDP que sale del 53, ahí tienes la respuesta.
Para guests con política DROP o REJECT el comportamiento cambia en un punto importante: “The same rules for dropping/rejecting packets are inherited from the datacenter, while the exceptions for accepted incoming/outgoing traffic of the host do not apply”. Es decir, la VM no hereda el permiso de 8006 ni de SSH desde los management hosts. Solo se mantienen excepciones para DHCP, NDP, Router Advertisement y los filtros de MAC e IP según lo que hayas configurado.
Los dispositivos fwbr, fwln y fwpr
Cuando pones firewall=1 en net0 de la VM 100, Proxmox no filtra directamente sobre tap100i0. Interpone un mini-bridge y un par veth, porque el filtrado se hace con el match physdev de iptables sobre el extremo fwln+.
| Dispositivo | Rol |
|---|---|
tap100i0 | La NIC de la VM. En LXC sería veth100i0 |
fwbr100i0 | Mini-bridge de firewall entre el tap y el bridge real |
fwln100i0 | Extremo veth del lado del fwbr. Aquí se filtra, con --physdev-in fwln+ y --physdev-out fwln+ |
fwpr100p0 | Extremo veth del lado de vmbr0 |
fwln100o0 | El OVSIntPort equivalente cuando el bridge es Open vSwitch |
flowchart LR
RED["Red fisica"] --> ENO["eno1"]
ENO --> VMBR["vmbr0"]
VMBR --> FWPR["fwpr100p0 - veth lado bridge"]
FWPR --> FWLN["fwln100i0 - veth lado firewall<br/>punto de filtrado con physdev"]
FWLN --> FWBR["fwbr100i0 - mini bridge"]
FWBR --> TAP["tap100i0"]
TAP --> VM["VM 100"]
Las cadenas que genera pve-firewall en iptables llevan todas el prefijo PVEFW-: PVEFW-INPUT, PVEFW-OUTPUT, PVEFW-FORWARD, PVEFW-PREROUTING, PVEFW-HOST-IN, PVEFW-HOST-OUT, PVEFW-FWBR-IN, PVEFW-FWBR-OUT, PVEFW-Drop, PVEFW-Reject, PVEFW-DropBroadcast, PVEFW-blacklist, PVEFW-smurfs, PVEFW-tcpflags, PVEFW-logflags, PVEFW-IPS y PVEFW-SET-ACCEPT-MARK.
Para verlos: ip -br link | grep -E 'fwbr|fwln|fwpr|tap|veth', bridge link show y iptables-save | grep fwln100i0.
Gotcha de NAT: si haces masquerading para una red privada de guests y tienes el firewall activo, las conexiones salientes pueden bloquearse porque prefieren el POSTROUTING del bridge de la VM en vez del MASQUERADE. La solución documentada es añadir zonas de conntrack en /etc/network/interfaces:
post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
Logging: niveles, LOGID y dónde mirar
Por defecto todo el logging está deshabilitado. Se activa poniendo un loglevel de entrada y/o salida en Firewall → Options, a nivel host y a nivel VM/CT por separado.
Lo que confunde: el loglevel no controla cuánto se loguea.
“loglevel does not affect how much of the filtered traffic is logged. It changes a LOGID appended as prefix to the log output for easier filtering and post-processing.”
El LOGID es el número que se antepone a cada línea: nolog no loguea, y emerg, alert, crit, err, warning, notice, info y debug corresponden a los LOGID 0 a 7 en ese orden. El formato de línea es VMID LOGID CHAIN TIMESTAMP POLICY: PACKET_DETAILS, con VMID a 0 para el firewall de host. El fichero es /var/log/pve-firewall.log.
tail -f /var/log/pve-firewall.log
grep '^0 ' /var/log/pve-firewall.log # solo firewall de host
grep '^100 ' /var/log/pve-firewall.log # solo la VM 100
También puedes loguear reglas individuales con el sufijo -log: IN REJECT -p icmp -log nolog es idéntico a IN REJECT -p icmp, mientras que IN REJECT -p icmp -log debug produce salida marcada como debug.
Comandos: compilar, simular y no dispararse en el pie
El CLI es pve-firewall:
| Comando | Qué hace |
|---|---|
pve-firewall status | Muestra el estado y compila todas las reglas, así que saca warnings si hay errores de configuración |
pve-firewall compile | Compila e imprime las reglas sin aplicarlas. Es el modo de prueba |
pve-firewall localnet | Imprime la información de red local y el local_network detectado |
pve-firewall start [--debug 1] | Arranca; con --debug se queda en foreground |
pve-firewall restart | Reinicia el servicio |
pve-firewall stop | Para el servicio |
Advertencia sobre stop: la documentación avisa de que “stopping actively removes all Proxmox VE related iptable rules rendering the host potentially unprotected”. No es un “pausar”, es un “quitar todas las reglas”.
El simulador es la herramienta más útil para depurar sin tocar tráfico real:
pve-firewall simulate --from outside --to vm100 --dport 22 --verbose
pve-firewall simulate --from host --to outside --dport 443
--from y --to aceptan host, outside, vm<N>, ct<N> o una VNet. Los defaults son outside para el origen y host para el destino, y el protocolo por defecto es tcp. La documentación aclara su límite: “This does not simulate the kernel routing table, but simply assumes that routing from source zone to destination zone is possible”.
proxmox-firewall con nftables: qué gana y qué pierde
Existe un segundo backend, proxmox-firewall, que traduce la misma configuración a nftables en vez de iptables. En la documentación de la serie 9.2 sigue con esta advertencia:
“WARNING: proxmox-firewall is currently in tech preview. There might be bugs or incompatibilities with the original firewall. It is currently not suited for production use.”
Y nftables: sigue con default = 0. Está en tech preview desde PVE 8.2.
Qué gana: reglas de dirección FORWARD, que el backend clásico ignora por completo; reglas a nivel VNet en /etc/pve/sdn/firewall/<vnet>.fw; y evaluación de las reglas de guest incluso para conexiones que ya tienen entrada en conntrack.
Qué pierde o cambia: con Linux bridges no crea los bridges de firewall fwbrX (los guests sobre bridges OVS sí los siguen teniendo); REJECT no es posible para tráfico de guest y se hace DROP en su lugar; y usar las opciones NDP, Router Advertisement o DHCP siempre genera reglas, independientemente de la política por defecto.
Activación:
apt install proxmox-firewall
# /etc/pve/nodes/<node_name>/host.fw
[OPTIONS]
nftables: 1
También se puede marcar en Host → Firewall → Options → nftables.
Gotcha grande: “After enabling/disabling proxmox-firewall, all running VMs and containers need to be restarted for the old/new firewall to work properly.” Reiniciar el servicio no basta: hay que reiniciar todos los guests, y eso vale igual al activarlo que al volver atrás.
Crea dos tablas, proxmox-firewall y proxmox-firewall-guests, y solo toca esas, así que puedes crear tus propias tablas nftables para reglas a medida sin que las pise.
Depuración:
systemctl status proxmox-firewall
nft list table inet proxmox-firewall
nft list table bridge proxmox-firewall-guests
# Volcar los comandos generados en JSON, con logs a STDERR
PVE_LOG=trace /usr/libexec/proxmox/proxmox-firewall compile > /tmp/fw.json
# Ruleset base embebido en el binario, y recrear todo desde cero
/usr/libexec/proxmox/proxmox-firewall skeleton | nft -f -
/usr/libexec/proxmox/proxmox-firewall compile | nft -j -f -
Para log verboso permanente, systemctl edit proxmox-firewall y añade Environment="PVE_LOG=trace" bajo [Service]. Los niveles menos ruidosos son info y debug.
Realms de autenticación
Cambiamos de puerta. El control de acceso empieza por el realm, que es el backend contra el que se valida la contraseña. Se configuran en /etc/pve/domains.cfg.
| Realm | Tipo | Notas |
|---|---|---|
| Linux PAM | pam | Usuarios del sistema. No se puede eliminar. Los cambios de contraseña desde la GUI solo aplican al nodo local |
| Proxmox VE authentication server | pve | Almacén interno; hashes SHA-256 en /etc/pve/priv/shadow.cfg. Recomendado para instalaciones pequeñas y medianas |
| LDAP | ldap | base_dn, user_attr, bind opcional; la contraseña de bind va a /etc/pve/priv/realm/<realmname>.pw |
| Active Directory | ad | Como LDAP con ajustes de AD; suele necesitar bind_dn |
| OpenID Connect | openid | Servidor de autorización externo, con autocreate y mapeo de username por claim |
pveum realm list
pveum realm add mi-ldap --type ldap --server1 ldap.example.com \
--base_dn "dc=example,dc=com" --user_attr uid
pveum realm modify mi-ad --case-sensitive 0
pveum realm sync mi-ldap --dry-run 1
pveum realm sync mi-ldap
pveum realm delete mi-ldap
La sincronización tiene tres opciones que conviene entender antes de lanzarla en serio: --scope acepta users, groups o both; --enable-new habilita los usuarios nuevos y tiene default true; y --remove-vanished acepta acl, entry o properties, y decide qué se hace con los usuarios que ya no están en el directorio. El comportamiento por defecto se fija con pveum realm modify mi-ldap --sync-defaults-options "remove-vanished=entry,properties,enable-new=1".
Gotcha de LDAP: los DN hay que escaparlos según RFC 2253. Los caracteres afectados son el espacio inicial, el # inicial, y luego ,, +, ", /, <, >, ; y =. Queda así: bind_dn = "CN=Example\, User,OU=people,DC=example,DC=com".
OpenID Connect se configura igual de rápido:
pveum realm add myrealm1 --type openid \
--issuer-url https://accounts.google.com --client-id XXXX --client-key YYYY \
--username-claim email
En PVE 9.2 los realms OpenID admiten una lista adicional opcional de audiencias aceptadas, y se corrigió el doble encoding de atributos LDAP con caracteres no ASCII.
Usuarios, grupos y roles
Los usuarios se identifican como <userid>@<realm>. Todo, salvo las contraseñas, vive en /etc/pve/user.cfg.
pveum user add ana@pve --password 'S3cr3t!' --email [email protected] \
--firstname Ana --lastname Perez --comment "SRE" --enable 1 --expire 0 --groups sre
pveum user modify ana@pve --groups sre,devops --append 1
pveum user list --full 1 --enabled 1
pveum user permissions ana@pve --path /vms/100
pveum passwd ana@pve ; pveum user delete ana@pve
pveum group add sre --comment "Equipo SRE"
pveum group list
--expire va en epoch UNIX; 0 significa sin expiración. root@pam siempre entra por PAM, no se puede borrar y recibe el correo del sistema.
Los roles predefinidos:
| Rol | Uso |
|---|---|
Administrator | Todos los privilegios |
NoAccess | Ningún privilegio. Anula cualquier otro rol en esa ruta |
PVEAdmin | Casi todo salvo modificar sistema y permisos |
PVEAuditor | Solo lectura |
PVEDatastoreAdmin | Backups y plantillas |
PVEDatastoreUser | Uso limitado de storage |
PVEPoolAdmin | Gestión de pools |
PVESDNAdmin | SDN |
PVESysAdmin | Audit, consola y syslog |
PVETemplateUser | Uso de plantillas |
PVEUserAdmin | Gestión de usuarios |
PVEVMAdmin | Administración completa de VMs |
PVEVMUser | Ver, backup, consola y encendido/apagado |
Cuando ninguno encaja, se hacen roles a medida con la lista exacta de privilegios: pveum role add VM_Power-only --privs "VM.PowerMgmt VM.Console", ampliable después con pveum role modify VM_Power-only --privs "VM.Audit" --append 1. Los privilegios están agrupados por familia: Sys.*, VM.*, Datastore.*, Mapping.*, SDN.*, más Group.Allocate, Permissions.Modify, Pool.Allocate, Pool.Audit, Realm.Allocate, Realm.AllocateUser y User.Modify.
Endurecimiento de privilegios en PVE 9.2. Tres cambios que rompen automatizaciones que funcionaban en PVE 8. Uno. Se exige VM.Config.Cloudinit para volcar la contraseña de cloud-init, relevante si usas las plantillas del capítulo 5. Dos. Se exige VM.PowerMgmt para arrancar VMs después de crearlas, restaurarlas o hacer rollback de un snapshot. Tres. Se exige Sys.Console para añadir VMs o contenedores como recursos HA durante la creación o la restauración.
ACL por path, herencia y pools
Los permisos se conceden asignando un rol a un usuario, grupo o token sobre una ruta. Las rutas son plantillas: /, /access, /access/groups, /access/realms/{realmid}, /mapping/notifications, /nodes/{node}, /pool/{poolname}, /pool/{poolname}/{type}, /sdn/zones/{zone}, /storage/{storeid}, /vms y /vms/{vmid}.
pveum acl list
pveum acl modify /vms/100 --users ana@pve --roles PVEVMUser
pveum acl modify /pool/mipool --groups sre --roles PVEVMAdmin
pveum acl modify /storage/ceph-rbd --users terraform@pve --roles PVEDatastoreUser
pveum acl modify /vms --tokens 'terraform@pve!tf' --roles PVEVMAdmin
pveum acl modify /nodes/pve1 --users ana@pve --roles PVEAuditor --propagate 0
pveum acl delete /vms/100 --users ana@pve --roles PVEVMUser
Las reglas de herencia, en orden:
- Los permisos de usuario reemplazan a los de grupo en la misma ruta.
- Los permisos de grupo aplican si el usuario es miembro.
- Los permisos de nivel más profundo ganan sobre los de nivel superior.
- El rol
NoAccessanula cualquier otro rol en esa ruta. --propagate 0limita el ACL exactamente a esa ruta, sin heredar hacia abajo. El default es1.
Un pool agrupa VMs, CTs y storages para conceder permisos de una sola vez. Una VM solo puede estar en un pool.
pveum pool add produccion --comment "Entorno de produccion"
pveum pool modify produccion --vms 100,101,102 --storage ceph-rbd,backup-nfs
pveum acl modify /pool/produccion --groups sre --roles PVEVMAdmin
flowchart TD
U["Usuario ana@pve<br/>realm pve"] --> G["Pertenencia a grupos"]
U --> T["API token ana@pve!ci"]
G --> ACL["ACL por path<br/>con propagate 0 o 1"]
U --> ACL
ACL --> R["Rol asignado en esa ruta"]
R --> P["Privilegios efectivos del usuario"]
T --> PS{"privsep del token"}
PS -->|"privsep 1 - por defecto"| INTER["Interseccion entre privilegios del usuario<br/>y ACL asignados al token"]
PS -->|"privsep 0"| HER["El token hereda todos los privilegios del usuario"]
P --> INTER
INTER --> EF["Permisos efectivos del token"]
HER --> EF
API tokens y privilege separation
Los tokens son la credencial correcta para automatización: Terraform, Ansible, exporters de métricas, pipelines de CI. Los verás en acción en el capítulo 14.
# Token con privilege separation, que es el default
pveum user token add terraform@pve tf --comment "Terraform CI"
# Token que hereda TODOS los permisos del usuario
pveum user token add terraform@pve tf-full --privsep 0
# Con expiracion en epoch UNIX
pveum user token add ci@pve deploy --expire 1798761600
pveum user token permissions terraform@pve tf
pveum user token modify terraform@pve tf --regenerate 1
pveum user token remove terraform@pve tf
Lo que hay que entender:
Uno. Con --privsep 1, que es el default, los permisos efectivos son la intersección de los del usuario y los ACL asignados al propio token. No basta con dar permisos al usuario: hay que dárselos también al token con pveum acl modify /vms --tokens 'terraform@pve!tf' --roles PVEVMAdmin.
Dos. Con --privsep 0 el token hereda todos los permisos del usuario. Es cómodo y es exactamente lo que quieres evitar en un runner de CI compartido.
Tres. El valor del secreto se muestra una sola vez, en la creación. No es recuperable después.
Cuatro. Los tokens no sirven para la consola de VM ni para acceso al sistema. Son para la API REST.
La cabecera HTTP es Authorization: PVEAPIToken=USER@REALM!TOKENID=UUID:
curl -H "Authorization: PVEAPIToken=terraform@pve!tf=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
https://pve.example.com:8006/api2/json/version
Novedad de PVE 9.2: la regeneración del secreto se puede hacer in situ con --regenerate 1, preservando las entradas ACL. Antes había que borrar el token y volver a crear todos sus permisos.
Two-Factor Authentication
Cuatro métodos soportados: TOTP, con códigos de 30 segundos y sin configuración de servidor; WebAuthn, para llaves hardware y autenticadores de plataforma, que requiere HTTPS con certificado de confianza; Yubico OTP, contra un servidor de validación de Yubico propio o en la nube; y recovery keys, códigos de un solo uso.
WebAuthn necesita configuración explícita en /etc/pve/datacenter.cfg:
webauthn: rp=mypve.example.com,origin=https://mypve.example.com:8006,id=mypve.example.com
La sub-opción allow-subdomains=<0|1> tiene default 1. U2F está deprecado en favor de WebAuthn.
Gotcha de la CLI: pveum user tfa solo permite listar, borrar y desbloquear. El alta de factores se hace desde la GUI, en Datacenter → Permissions → Two Factor, o vía la API /access/tfa/{userid}.
pveum user tfa list ana@pve
pveum user tfa delete ana@pve --id <tfa-entry-id>
pveum user tfa unlock ana@pve
La política de bloqueo: 8 intentos fallidos de TOTP deshabilitan los factores TOTP del usuario, y 100 intentos con WebAuthn o recovery provocan un bloqueo de 1 hora. El desbloqueo se hace entrando con una recovery key o con pveum user tfa unlock desde un administrador.
Para exigir 2FA a todos los usuarios de un realm se define el método TFA al crear o editar el realm; es la forma de no depender de que cada usuario lo active por su cuenta. Y para automatizar un login con 2FA existe pveum ticket ana@pve --otp 123456.
Certificados: la CA del clúster y ACME
Cada clúster crea su propia CA autofirmada y emite un certificado por nodo. Se usan para pveproxy y para la función Shell/Console con SPICE.
| Fichero | Rol |
|---|---|
/etc/pve/pve-root-ca.pem | Certificado de la CA del clúster |
/etc/pve/priv/pve-root-ca.key | Clave privada de la CA |
/etc/pve/nodes/NODENAME/pve-ssl.pem | Certificado del nodo firmado por la CA. Es el default |
/etc/pve/local/pveproxy-ssl.pem | Certificado externo o de ACME, si existe tiene prioridad |
/etc/pve/local/pveproxy-ssl.key | Clave privada, sin passphrase |
/etc/pve/local es un symlink específico del nodo a /etc/pve/nodes/NODENAME.
Para que tus estaciones dejen de avisar sin comprar certificado, se importa la CA en el trust store del sistema. Hay que copiarla fuera de banda con scp root@pve1:/etc/pve/pve-root-ca.pem ., porque “There is no API or web interface endpoint for downloading the file”.
Advertencia: no reemplaces ni edites a mano /etc/pve/local/pve-ssl.pem, /etc/pve/local/pve-ssl.key ni los ficheros de la CA. Si quieres un certificado propio, va en pveproxy-ssl.pem.
ACME
Proxmox trae cliente ACME integrado, con los endpoints de Let’s Encrypt de producción y de staging. Hay una cuenta por clúster.
root@pve1:~# pvenode acme account register default [email protected]
Directory endpoints:
0) Let's Encrypt V2 (https://acme-v02.api.letsencrypt.org/directory)
1) Let's Encrypt V2 Staging (https://acme-staging-v02.api.letsencrypt.org/directory)
2) Custom
Enter selection: 1
El consejo oficial es usar staging para los experimentos, por los rate limits.
HTTP-01 con el plugin standalone. Siempre existe implícitamente. Sus requisitos exactos: aceptar los ToS, que el puerto 80 del nodo sea alcanzable desde internet, que no haya otro listener en el 80, y que el subdominio resuelva a una IP pública del nodo. Con eso, pvenode config set --acme domains=example.invalid y pvenode acme cert order.
DNS-01. Reutiliza los plugins DNS de acme.sh, así que cubre prácticamente cualquier proveedor con API. La configuración de plugins vive en /etc/pve/priv/acme/plugins.cfg y está disponible para todos los nodos del clúster.
pvenode acme plugin add dns example_plugin --api ovh --data /path/to/api_token
pvenode acme plugin config example_plugin
pvenode config set -acmedomain0 example.proxmox.com,plugin=example_plugin
pvenode acme cert order
El validation delay es el tiempo entre poner el registro TXT y pedir la validación; los proveedores necesitan propagar. Si tu DNS no tiene API se puede usar el modo alias: un CNAME permanente de _acme-challenge.domain1.example hacia _acme-challenge.domain2.example, y la propiedad alias en la clave acmedomainX.
Renovación. La hace pve-daily-update.service, e intenta renovar cuando el certificado ya expiró o expira en los próximos 30 días. Si usas un directory que emite certificados de vida corta, conviene desactivar el retardo aleatorio de pve-daily-update.timer.
Gotcha al pasar de staging a producción: cambiar el directory de una cuenta no está soportado. Hay que desactivar la cuenta con pvenode acme account deactivate default y registrar otra; la desactivación renombra /etc/pve/priv/acme/default a algo tipo _deactivated_default_4. Para inspeccionar el estado: pvenode cert info, pvenode acme account list y pvenode acme plugin list.
Endurecer pveproxy y poner un reverse proxy
pveproxy expone toda la API en el 8006 por HTTPS. Corre como www-data con permisos muy limitados y delega en pvedaemon local lo que necesita más privilegios. Además reenvía automáticamente las peticiones dirigidas a otros nodos del clúster.
Su configuración está en /etc/default/pveproxy.
ACL estilo Apache
ALLOW_FROM="10.0.0.1-10.0.0.5,192.168.0.0/22"
DENY_FROM="all"
POLICY="allow"
all es alias de 0/0 y ::/0. La política por defecto es allow, y esa es justamente la que hay que cambiar:
| Coincidencia | POLICY=deny | POLICY=allow |
|---|---|---|
| Solo Allow | allow | allow |
| Solo Deny | deny | deny |
| Sin coincidencia | deny | allow |
| Allow y Deny | deny | allow |
IP de escucha
Por defecto pveproxy y spiceproxy escuchan en wildcard, IPv4 e IPv6.
LISTEN_IP="192.0.2.1"
LISTEN_IP="2001:db8:85a3::1"
LISTEN_IP="fe80::c463:8cff:feb9:6a4e%vmbr0"
“LISTEN_IP can be used to only restrict the socket to an internal interface and thus have less exposure to the public internet.”
Advertencia: “The nodes in a cluster need access to pveproxy for communication, possibly on different sub-nets. It is not recommended to set LISTEN_IP on clustered systems.” En un clúster, LISTEN_IP mal puesto lo rompe. Úsalo solo en nodo standalone.
Los cambios se aplican con systemctl restart pveproxy.service spiceproxy.service. Ojo: a diferencia de un reload, un restart puede interrumpir procesos worker de larga duración, como una consola o una shell abierta contra un guest.
Sobre TLS, los defaults del fichero ya son razonables y están escritos explícitamente en él: CIPHERS con la familia ECDHE de AES-GCM, ChaCha20-Poly1305 y AES-SHA, CIPHERSUITES con TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 y TLS_AES_128_GCM_SHA256, y HONOR_CIPHER_ORDER=0. CIPHERS aplica a TLS 1.2 y anteriores; CIPHERSUITES a TLS 1.3. SSL 2 y 3 están deshabilitados incondicionalmente. Consulta el contenido exacto en tu nodo con cat /etc/default/pveproxy.
Reverse proxy con nginx
La configuración oficial de la wiki, que funciona con la interfaz web y con la consola noVNC:
upstream proxmox {
server "YOUR.FQDN.HOSTNAME.HERE";
}
server {
listen 80 default_server;
listen [::]:80 default_server;
rewrite ^(.*) https://$host$1 permanent;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name _;
ssl_certificate /etc/pve/local/pve-ssl.pem;
ssl_certificate_key /etc/pve/local/pve-ssl.key;
proxy_redirect off;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass https://localhost:8006;
proxy_buffering off;
client_max_body_size 0;
proxy_connect_timeout 3600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
send_timeout 3600s;
}
}
Por qué cada directiva: proxy_http_version 1.1 junto con las cabeceras Upgrade y Connection "upgrade" son imprescindibles para el WebSocket de noVNC y xterm.js, y sin ellas la interfaz carga pero la consola no abre; proxy_buffering off mantiene la consola en tiempo real; client_max_body_size 0 permite subir ISOs sin límite de tamaño; y los timeouts de 3600 segundos sostienen sesiones de consola largas.
Post-setup obligatorio: los certificados viven en /etc/pve, que lo monta pve-cluster.service. Sin esta dependencia nginx arranca antes y falla. Con systemctl edit nginx.service añade bajo [Unit] las líneas Requires=pve-cluster.service y After=pve-cluster.service, y luego valida con nginx -t && systemctl restart nginx.
En un nodo standalone, la combinación potente es reverse proxy en 443 más LISTEN_IP="127.0.0.1" en /etc/default/pveproxy, para que el 8006 no esté expuesto en ninguna interfaz externa.
Por qué no se expone el 8006 y las capas de defensa
El diseño del producto asume acceso restringido: la documentación del firewall recomienda el IPSet management para limitar quién llega a la GUI, y /etc/default/pveproxy ofrece ALLOW_FROM, DENY_FROM y LISTEN_IP explícitamente para tener “less exposure to the public internet”. El staff de Proxmox lo dice en el foro oficial sin rodeos: “You should never expose your management to the world wide web”.
Los argumentos concretos. Uno. pveproxy da acceso a toda la API REST, incluida la creación y destrucción de VMs y la shell del host vía termproxy y vncshell; comprometer el 8006 no es comprometer una aplicación, es comprometer el hipervisor entero y todo lo que corre encima. Dos. La consola noVNC y xterm.js exponen una shell root del host con las credenciales adecuadas. Tres. El certificado por defecto es autofirmado por la CA del clúster, así que los usuarios se acostumbran a aceptar el aviso del navegador y un MITM se vuelve trivial: por eso el certificado ACME no es cosmético. Cuatro. No hay rate limiting ni bloqueo de fuerza bruta integrado en pveproxy; el único mecanismo de lockout documentado es el de 2FA. Cinco. Cambiar el puerto es security by obscurity y no aporta protección real: “as long as it faces to the public, it can be attacked independent of the used port”. Seis. Los nodos del clúster necesitan el 8006 entre sí, así que un LISTEN_IP mal configurado rompe el clúster.
flowchart TD
A["1. VPN o VLAN de gestion separada<br/>recomendacion primaria"] --> B["2. Firewall PVE con IPSet management<br/>y las IPs de administracion"]
B --> C["3. /etc/default/pveproxy<br/>ALLOW_FROM con POLICY=deny"]
C --> D["4. Certificado ACME valido<br/>HTTP-01 o DNS-01"]
D --> E["5. 2FA obligatorio por realm<br/>TOTP WebAuthn Yubico o recovery keys"]
E --> F["6. Reverse proxy en 443 si hace falta acceso web<br/>nunca el 8006 desnudo"]
F --> G["7. fail2ban sobre el log del daemon<br/>patron de comunidad"]
Sobre las capas 1 y 7 conviene una precisión honesta. WireGuard sí está documentado oficialmente en PVE 9.2, pero como fabric del SDN, es decir como underlay cifrado entre nodos del clúster, no como VPN de acceso del administrador. El paquete es wireguard-tools, las claves privadas van a /etc/pve/priv/wg-keys.cfg, las configs generadas a /etc/wireguard/proxmox/<iface>.conf y el puerto de escucha por defecto es el 51820. Montar wg-quick@wg0 en el host como VPN de administración es un patrón estándar de Debian y funciona, pero no está en la documentación de Proxmox; la alternativa más limpia es poner la VPN en un guest o appliance dedicado en vez de en el host. Lo mismo pasa con fail2ban: es un patrón muy extendido y el tutorial oficial de Hetzner lo recomienda, pero no hay integración documentada por Proxmox.
Configuración mínima recomendada
# /etc/pve/firewall/cluster.fw
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT
[ALIASES]
local_network 203.0.113.10 # la IP publica del nodo, no el /20 del proveedor
[IPSET management]
198.51.100.42 # oficina
10.99.0.0/24 # red de la VPN de administracion
[IPSET blacklist]
77.240.159.182
Súmale el host.fw con protection_synflood, tcpflags y nosmurfs a 1 que viste más arriba, y el /etc/default/pveproxy con ALLOW_FROM limitado a esas mismas dos redes y POLICY="deny". El orden de aplicación importa: primero el IPSet management con tu IP dentro, luego enable: 1, y solo después tocas pveproxy. Con la sesión SSH abierta todo el rato.
Errores comunes y diagnóstico
| Síntoma | Causa probable | Diagnóstico y solución |
|---|---|---|
| Las reglas de firewall de la VM no bloquean nada | Falta alguno de los tres flags | Comprobar enable: 1 en cluster.fw, enable: 1 en <VMID>.fw y firewall=1 en netN; pve-firewall status; iptables-save | grep fwln<VMID>i0 |
Las reglas FORWARD o las de VNet se ignoran en silencio | El backend iptables no las soporta | Requiere proxmox-firewall con nftables: 1 en host.fw y reiniciar todos los guests |
| Pierdes el acceso a la GUI justo al activar el firewall | Política DROP y tu IP no está en management, o local_network mal autodetectado | Tener sesión SSH abierta antes; pve-firewall localnet; ajustar [ALIASES] local_network y [IPSET management] |
| Las conexiones salientes de VMs NATeadas se bloquean | Falta la zona de conntrack | Añadir -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 en /etc/network/interfaces |
| El firewall falla al actualizar el ruleset tras tocar el IPAM | Reglas que referencian guest-ipam-<VMID> inexistente | Quitar la referencia o restaurar la entrada IPAM |
| Tras activar nftables el tráfico del guest se comporta raro | Los guests no se han reiniciado | Reiniciar todas las VMs y contenedores |
Esperabas un REJECT en un guest y ves un timeout | Con nftables REJECT no es posible en tráfico de guest y se hace DROP | Es el comportamiento documentado del backend nftables |
| No aparece nada en el log de un paquete SMB o de UDP con puerto origen 53 | Están en la lista de descartes sin loguear | Es comportamiento por defecto; usar pve-firewall simulate para verificar |
| Un ACL “correcto” no da permisos al token de API | privsep 1 y el token no tiene su propio ACL | pveum acl modify <path> --tokens 'user@realm!tokenid' --roles <rol> |
| El usuario ve el recurso pero no puede hacer nada en él | Un NoAccess en esa ruta, o un ACL más profundo que gana | pveum user permissions <userid> --path <path> |
| Terraform deja de poder arrancar VMs tras actualizar a PVE 9.2 | Ahora se exige VM.PowerMgmt para arrancar tras crear o restaurar | Añadir VM.PowerMgmt y, si registra recursos HA, Sys.Console |
| WebAuthn no se puede registrar | Falta HTTPS con certificado de confianza o falta la línea webauthn: | Emitir certificado ACME y configurar webauthn: rp=...,origin=...,id=... en datacenter.cfg |
| Un usuario se quedó fuera con TOTP | 8 intentos fallidos deshabilitan sus factores TOTP | Entrar con recovery key o pveum user tfa unlock <userid> |
pvenode acme cert order falla con HTTP-01 | El 80 no es alcanzable o hay otro listener ocupándolo | Liberar el 80, comprobar DNS público del subdominio, probar contra el endpoint de staging |
| El certificado ACME no se renueva | El servicio diario no llegó a ejecutarse | systemctl status pve-daily-update.service; se renueva a 30 días o menos de la expiración |
| Cambiaste de staging a producción y sigue emitiendo staging | Cambiar el directory de una cuenta no está soportado | pvenode acme account deactivate default y registrar una cuenta nueva |
| noVNC no abre detrás del reverse proxy | Faltan proxy_http_version 1.1 y las cabeceras de upgrade | Añadirlas; verificar con nginx -t y reiniciar |
| nginx falla al arrancar tras un reboot | Arranca antes de que /etc/pve esté montado | systemctl edit nginx.service con Requires y After de pve-cluster.service |
| Tras renombrar interfaces, el firewall del clúster sigue con los nombres viejos | pve-network-interface-pinning no actualiza cluster.fw | Editar /etc/pve/firewall/cluster.fw a mano |
Chuleta de diagnóstico
pve-firewall status && pve-firewall localnet # compila y avisa de errores
pve-firewall simulate --from outside --to vm100 --dport 22 --verbose
iptables-save | grep PVEFW ; ipset list ; tail -f /var/log/pve-firewall.log
nft list table inet proxmox-firewall # solo con backend nftables
ip -br link | grep -E 'fwbr|fwln|fwpr|tap|veth'
pveum acl list
pveum user permissions ana@pve --path /vms/100
pveum user token permissions terraform@pve tf
pveum user tfa list ana@pve
pvenode cert info ; pvenode acme account list
openssl s_client -connect localhost:8006 -showcerts </dev/null
systemctl status pveproxy spiceproxy ; cat /etc/default/pveproxy
Lo que queda operativo
El nodo ya no está abierto. Tienes el firewall distribuido activo en los tres niveles, con el IPSet management como única puerta de administración, las protecciones de synflood y tcpflags encendidas, y el log en /var/log/pve-firewall.log con el LOGID que te permite filtrar por nivel. Sabes que las reglas FORWARD y las de VNet exigen el backend nftables, que sigue en tech preview, y que activarlo obliga a reiniciar todos los guests.
Del otro lado, los usuarios entran por el realm que corresponde, los permisos se conceden por rol sobre una ruta concreta con reglas de herencia predecibles, la automatización usa tokens con privsep 1 cuyos permisos son la intersección de dos ACL, el 2FA es obligatorio por realm y el certificado lo emite y renueva ACME solo.
En el capítulo 11 se une el segundo y el tercer nodo: corosync, quorum, QDevice, alta disponibilidad con watchdog y migración en vivo. Ahí el firewall vuelve a aparecer, porque corosync necesita el rango UDP 5405-5412 en la red del clúster y las migraciones el TCP 60000-60050. Si has cerrado bien lo de este capítulo, el clúster se forma a la primera; si te has pasado de restrictivo, la primera pista estará en el log del firewall.