Fase 1.2
Rocky Linux 9 · firewalld

Firewalld · Seguridad de Red

El firewall de red que controla qué puertos y servicios son accesibles desde fuera de la VM. Usa zonas en lugar de reglas planas — cada interfaz de red pertenece a una zona con un nivel de confianza distinto.

01

Zonas, no Reglas Planas

A diferencia de iptables o UFW donde las reglas se aplican globalmente, firewalld organiza la seguridad por zonas. Cada interfaz de red se asigna a exactamente una zona, y esa zona define qué tráfico se acepta. Es el modelo estándar en RHEL, Fedora y Rocky Linux.

public en uso

Zona por defecto. Se usa para la interfaz de red principal. Nivel de confianza: no se confía en otros equipos de la red.

internal

Confianza moderada. Útil si hubiera múltiples VMs en una VCN privada de OCI comunicándose entre sí.

trusted

Todo el tráfico aceptado. Nunca se usa en este proyecto — equivale a desactivar el firewall para esa interfaz.

drop

Todo el tráfico entrante se descarta sin respuesta. Útil para interfaces que no deberían recibir nada.

El proyecto usa exclusivamente la zona public. Es la zona correcta para una VM con una interfaz de red que da a internet o a una red local no confiable. En Oracle Cloud, las VMs también usan public — la seguridad adicional viene de los OCI Network Security Groups, no de cambiar la zona de firewalld.

02

Puerto SSH: de 22 a 2222

Mover SSH del puerto 22 a un puerto no estándar es una de las medidas de hardening más simples y efectivas. Reduce el ruido de escaneos automáticos y fuerza a los atacantes a descubrir el puerto antes de intentar conectarse.

Antes
tcp/22
servicio ssh
Después
tcp/2222
servicio ssh
Importante: firewalld cierra el puerto 22 y abre el 2222. Pero SSH todavía escucha en el 22 hasta que el rol ssh-hardening lo cambie. Por eso el orden en site.yml es firewalld → selinux → ssh-hardening: primero se prepara la red, después se cambia el servicio.
03

Puertos Abiertos

Cada puerto abierto en la VM tiene una razón específica. No se abre ningún puerto por comodidad — solo los necesarios para que el stack funcione.

2222/tcp crítico
SSH custom
Puerto SSH после hardening. El puerto 22 se cierra, todo el tráfico SSH migra a este puerto.
6443/tcp crítico
k3s API Server
API de Kubernetes. Sin este puerto, kubectl y ArgoCD no pueden comunicarse con el cluster.
80/tcp
HTTP — Ingress
Tráfico HTTP para el ingress controller. Redirige a HTTPS en producción, pero se mantiene abierto para ACME challenges.
443/tcp
HTTPS — Ingress
Tráfico HTTPS para Grafana público y cualquier servicio expuesto via ingress.
8200/tcp
HashiCorp Vault
API de Vault. Accesible solo desde dentro del cluster — el ingress no expone este puerto.
Ningún puerto de base de datos está abierto. Prometheus, Loki y Vault usan almacenamiento interno en el cluster. Sus puertos no necesitan estar expuestos a la red externa — solo son accesibles dentro de k3s via ClusterIP.
04

El Rol firewalld

El rol aplica la política de red en seis tareas. La filosofía es: instalar, activar, abrir solo lo necesario y cerrar lo que sobra.

Defaults

roles/firewalld/defaults/main.yml
firewalld_zone: public

firewalld_services: []

firewalld_ports:
    - "2222/tcp"   # SSH custom port
    - "6443/tcp"   # k3s API server
    - "80/tcp"     # HTTP — Ingress
    - "443/tcp"    # HTTPS — Ingress
    - "8200/tcp"   # HashiCorp Vault

firewalld_ssh_port: 2222

Tasks

roles/firewalld/tasks/main.yml
- name: Asegurar que firewalld esté instalado
  ansible.builtin.dnf:
    name: firewalld
    state: present

- name: Asegurar que firewalld esté activo y habilitado
  ansible.builtin.systemd:
    name: firewalld
    state: started
    enabled: true

- name: Establecer zona default
  ansible.builtin.command:
    cmd: firewall-cmd --set-default-zone={{ firewalld_zone }}
  changed_when: false

- name: Abrir servicios necesarios
  ansible.posix.firewalld:
    service: "{{ item }}"
    zone: "{{ firewalld_zone }}"
    permanent: true
    state: enabled
    immediate: true
  loop: "{{ firewalld_services }}"

- name: Abrir puertos necesarios
  ansible.posix.firewalld:
    port: "{{ item }}"
    zone: "{{ firewalld_zone }}"
    permanent: true
    state: enabled
    immediate: true
  loop: "{{ firewalld_ports }}"

- name: Quitar servicio ssh (puerto 22)
  ansible.posix.firewalld:
    service: ssh
    zone: "{{ firewalld_zone }}"
    permanent: true
    state: disabled
    immediate: true

La última tarea es clave: elimina el servicio ssh (puerto 22) de la zona public. Si no se hiciera, el puerto 22 seguiría abierto aunque SSH ya no escuche allí — una superficie de ataque innecesaria.

05

firewalld vs UFW

UFW es el firewall estándar en Ubuntu. Firewalld es el estándar en RHEL/Rocky. Conocer ambos es relevante en entornos mixtos.

RHEL/Rocky firewalld
  • Zonas con niveles de confianza por interfaz
  • Cambios en caliente sin reiniciar (--permanent + --reload)
  • Reglas enriquecidas (rich rules) para filtrado avanzado
  • Integración con NetworkManager — cambios según red conectada
  • Backend: nftables (antes iptables)
Ubuntu UFW
  • Reglas planas — sin concepto de zonas
  • Sintaxis simple: ufw allow/deny
  • Perfiles de aplicación predefinidos
  • Ideal para firewalls simples de una VM
  • Backend: iptables

Para un proyecto como este, firewalld es la elección correcta porque Rocky Linux lo trae por defecto y las zonas son útiles en Oracle Cloud: la interfaz pública va a public y una interfaz privada entre VMs podría ir a internal.

06

CLI Reference

Comandos esenciales para inspeccionar y modificar firewalld manualmente. Todas las operaciones que requieren persistencia necesitan el flag --permanent.

firewall-cmd
# Verificar si firewalld está corriendo
$firewall-cmd --state
running
# Zona por defecto
$firewall-cmd --get-default-zone
public
# Reglas completas de la zona activa
$firewall-cmd --list-all
public (active)
target: default
ports: 2222/tcp 6443/tcp 80/tcp 443/tcp 8200/tcp
services:
...
# Solo puertos abiertos
$firewall-cmd --list-ports
2222/tcp 6443/tcp 80/tcp 443/tcp 8200/tcp
# Abrir un puerto nuevo (ejemplo)
$firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload
success
Regla de oro: usar siempre --permanent seguido de firewall-cmd --reload. Los cambios sin --permanent se pierden al reiniciar firewalld o la VM.
07

Verificar que Funcionó

Después de correr el playbook, estos comandos confirman que firewalld está activo y que solo los puertos necesarios están expuestos.

verify-firewalld.sh
# firewalld debe estar corriendo
$ systemctl is-active firewalld
active
# Zona por defecto
$ firewall-cmd --get-default-zone
public
# Puertos abiertos — 22 no debe aparecer
$ firewall-cmd --list-ports
2222/tcp 6443/tcp 80/tcp 443/tcp 8200/tcp
# Servicio ssh debe estar ausente de la zona public
$ firewall-cmd --list-services
(vacío — sin servicios habilitados)
# Verificar que el puerto 22 está cerrado desde fuera
$ ss -tlnp | grep -E ':22 |:2222 '
LISTEN 0 128 *:2222 *:* users:(("sshd",pid=891,fd=3))
(22 no aparece — solo 2222)

Siguiente: SSH Hardening

Con firewalld protegiendo la red y SELinux protegiendo el kernel, la siguiente capa es el hardening del servicio SSH: sin root login, sin password auth, puerto custom y límites de conexión.

SSH Hardening