Sveliamo i misteri di Firewalld (Parte 2 di 2)

tux-firewalld


N.B. Sono stato costretto a spezzare l'articolo perché, quello originale di 8836 parole (per un totale di 76309 bytes effettivi), veniva troncato dopo il salvataggio a 7533 parole (65.356 bytes), cosa che mi fa sospettare che la grandezza del body non possa superare i 64k.


Nel capitolo precedente (Sveliamo i misteri di firewalld (Parte 1 di 2), abbiamo esaminato abbastanza nel dettaglio la cli di firewalld (firewall-cmd) con un focus particolare sul suo man, fatto molto bene.

Nel seguito ci concentreremo soprattutto su casi d'uso per vedere firewalld in azione, analizzando alcuni possibili cortocircuiti logici e approfondendo quanto già enunciato nei capitoli 1, 2, 4 della parte 1.

5. Esempi

Nel configurare firewalld, da utente, la difficoltà più grande per me consiste nel sapersi districare in mezzo alla ragnatela di zone, policy, servizi, nat, rich-rules ecc.

Come dicevo in precedenza per un firewall personale, bastano le zone con la dichiarazione dei servizi, così da filtrare il traffico in ingresso e finisce il gioco.

Quando invece lo scenario si complica, è necessario capire bene come funzioni il routing dei pacchetti fatto da firewalld e qual sia l'ordine di valutazione che firewalld fa dei vari oggetti che poi si traduce nell'accettazione o nel rifiuto del pacchetto.

Provo a tracciare una scaletta che però è frutto di esperienza nell'uso, di sperimentazione personale, nulla di ufficiale.

Fase 1: Associazione alle Zone firewalld per prima cosa deve capire quali siano le zone interessate controllando l'ip sorgente del pacchetto.

  1. Se c'è una sorgente associata esplicitamente ad una zona, il pacchetto viene assegnato a quella zona.
  2. Se non c'è alcuna sorgente, si controlla l'interfaccia fisica da cui è entrato il pacchetto e si assegna alla zona che trusta quell'interfaccia.
  3. Se non c'è nessuna zona associata all'interfaccia, il pacchetto finisce alla zona impostata come default

Fase 2: Selezione del contesto (zone vs policy) Una volta identificate la zona di ingresso (e l'eventuale zona di uscita se il traffico è in transito/forwarding), firewalld decide quale “contenitore di regole” applicare:

  1. Le policy vengono valutate prima delle singole regole delle zone. In base alle zone associate, firewalld verifica se esiste una Policy che le unisce e applica i filtri presenti nella policy
  2. Se non ci sono policy object applicabili a quel flusso, il pacchetto viene processato direttamente all'interno della singola zona identificata nella Fase 1. Questo vuol dire che se ci sono filtri nella zona e c'è una policy del tipo zone-to-host, i filtri della policy vincono.

Fase 3: Valutazione delle regole interne (servizi, porte e NAT) Ora che abbiamo il contesto, è importante capire in che ordine firewalld applica i filtri.

  1. come prima cosa si valutano le rich-rules con priorità negativa;
  2. in seguito si prende in esame il dnat in modo che i filtri successivi lavorino sull'indirizzo e sulla porta di destinazione corretti;
  3. successivamente si valutano i servizi e le porte standard, quelli aggiunti con --add-service e --add-port per capirci
  4. dopo i servizi, vengono valutate le rich-rule tradizionali, quelle con priorità 0
  5. come ultimo passo, se c'è, viene preso in carico il SNAT Masquerade

Fase 4: Il target di default Se un pacchetto è arrivato fino a questo punto significa che non ha trovato nessuna regola esplicita (nessun servizio o porta lo ha accettato o rifiutato prima) e viene applicato il target della zona (DROP/REJECT/ACCEPT/default) o della policy (DROP/REJECT/ACCEPT/CONTINUE)

La fase 3 è quella critica, quella che emette il verdetto di accettazione o rifiuto. E qui bisogna fare attenzione.

Le azioni di manipolazione dei pacchetti come il NAT non corrispondono ad un verdetto, che avviene invece:

  1. nei servizi (--add-service o --add-port): accettazione (o rifiuto) implicita
  2. nelle rich-rule se uno dei target viene esplicitato

Quindi, ad es. se nella zona o nella policy, c'è un add-service che accetta un determinato servizio e di seguito una rich rule che invece lo nega, add-service ha priorità e un pacchetto che richieda quel servizio verrebbe accettato e non scartato. Come pure, in una zona/policy default drop, se fosse presente un masquerade o un dnat senza alcuna accettazione esplicite, il pacchetto verrebbe rifiutato dalle politiche della zona/policy.

Alla luce di questa nuova consapevolezza, proverò a mostrare come risolvere alcuni tipici casi d'uso, partendo dalla configurazione di un semplice firewall personale che protegge l'esposizione di servizi standard, per proseguire in maniera incrementale arricchendo di volta in volta la configurazione iniziale.

5.1. Caso 1: host con una sola interfaccia

È il caso classico di un personal firewall.

Nel primo blocco di esempi mostrerò anche le configurazioni di un approccio full-policy.

Non ci sono vantaggi evidenti perché si tratta di casi semplici però concentrare la logica nelle policy piuttosto che nelle zone permette per es. di bloccare tutto il traffico in una zona default drop semplicemente disabilitando la policy.

Oppure posso usare la stessa logica di filtraggio su altre zone senza doverle replicare ma solo aggiungendo egress-zone e ingress-zone opportue.

5.1.1. Esposizione semplice

Ipotizziamo quindi di avere un host con una sola interfaccia e di voler configurare il firewall per filtrare i servizi ssh (22/tcp) e http(s) (80,443/tcp) che questo host espone su una lan.

Versione zone-oriented:

# creazione zona, binding su eth0, policy di default DROP (l'host non sarà nemmeno "pingabile")
firewall-cmd --permanent --new-zone=server-lan
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# abilitazione servizi ssh, http(s)
firewall-cmd --permanent --zone="server-lan" --add-service=ssh
firewall-cmd --permanent --zone="server-lan" --add-service=http
firewall-cmd --permanent --zone="server-lan" --add-service=https

Come si può vedere, la configurazione è pulita, lineare ed è veramente “parlante”. Chiunque bussi all'interfaccia eth0, accede ai servizi esposti dall'host.

Versione policy-oriented:

# creazione zona, binding su eth0, policy di default DROP (l'host non sarà nemmeno "pingabile")
firewall-cmd --permanent --new-zone="server-lan"
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# creazione policy
firewall-cmd --permanent --new-policy="lan-to-host"
firewall-cmd --permanent --policy="lan-to-host" --add-ingress-zone="server-lan"
firewall-cmd --permanent --policy="lan-to-host" --add-egress-zone=HOST
firewall-cmd --permanent --policy="lan-to-host" --set-target=REJECT

# binding dei servizi ssh, http(s)
firewall-cmd --permanent --policy=lan-to-host --add-service=ssh
firewall-cmd --permanent --policy=lan-to-host --add-service=http
firewall-cmd --permanent --policy=lan-to-host --add-service=https

Non cambia quasi nulla se non il fatto che il filtering ora avviene attraverso la policy mentre la zona contiene solo il trust delle sorgenti.

5.1.2. Esposizione di servizi custom con restrizioni

L'host stavolta esporrà i servizi su porte custom con questi vincoli:

Versione zone-oriented:

# creazione zona, binding su eth0, policy di default DROP (l'host non sarà nemmeno "pingabile")
firewall-cmd --permanent --new-zone=server-lan
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# creazione servizio "web_custom"
firewall-cmd --permanent --new-service="web_custom"
firewall-cmd --permanent --service="web_custom" --set-description="My HTTP(S) service"
firewall-cmd --permanent --service="web_custom" --add-port=8080/tcp --add-port=8443/tcp

# creazione servizio "ssh_custom"
firewall-cmd --permanent --new-service="ssh_custom"
firewall-cmd --permanent --service="ssh_custom" --set-description="My SSH service"
firewall-cmd --permanent --service="ssh_custom" --add-port=2222/tcp

# chiunque bussi a eth0 sulle porte 8080 e 8443, verrà accettato 
firewall-cmd --permanent --zone="server-lan" --add-service=web_custom

# ssh_custom invece è ristretto con una rich-rule al solo ip 192.168.10.21
firewall-cmd --permanent --zone="server-lan" --add-rich-rule='rule family="ipv4" source address="192.168.10.21" service name="ssh_custom" accept'

Versione policy-oriented:

# creazione zona, binding su eth0, policy di default DROP (l'host non sarà nemmeno "pingabile")
firewall-cmd --permanent --new-zone="server-lan"
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# creazione servizio "web_custom"
firewall-cmd --permanent --new-service="web_custom"
firewall-cmd --permanent --service="web_custom" --set-description="My HTTP(S) service"
firewall-cmd --permanent --service="web_custom" --add-port=8080/tcp --add-port=8443/tcp

# creazione servizio "ssh_custom"
firewall-cmd --permanent --new-service="ssh_custom"
firewall-cmd --permanent --service="ssh_custom" --set-description="My SSH service"
firewall-cmd --permanent --service="ssh_custom" --add-port=2222/tcp

# creazione policy
firewall-cmd --permanent --new-policy="lan-to-host"
firewall-cmd --permanent --policy="lan-to-host" --add-ingress-zone="server-lan"
firewall-cmd --permanent --policy="lan-to-host" --add-egress-zone=HOST
firewall-cmd --permanent --policy="lan-to-host" --set-target=REJECT

# binding web_custom su tutta eth0
firewall-cmd --permanent --policy="lan-to-host" --add-service=web_custom

# ssh_custom invece è ristretto con una rich-rule al solo ip 192.168.10.21
firewall-cmd --permanent --policy="lan-to-host" --add-rich-rule='rule family="ipv4" source address="192.168.10.21" service name="ssh_custom" accept'

5.1.3. Esposizione con port forwarding statico

Facciamo il caso che i servizi ssh e web siano in ascolto sull'interfaccia di loopback. Vogliamo comunque esporli attraverso un port redirect facendo in modo che:

In più vogliamo restringere l'accesso ssh a 3 soli ip: 192.168.10.20, 192.168.10.21, 192.168.10.22 e vogliamo bloccare tutto il traffico ICMP ad eccezione di echo-request.

Versione zone-oriented:

# creazione zona, binding su eth0, policy di default DROP
firewall-cmd --permanent --new-zone="server-lan"
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# icmp solo request
firewall-cmd --permanent --zone="server-lan" --add-icmp-block-inversion
firewall-cmd --permanent --zone="server-lan" --add-icmp-block=echo-request

# ipset per ssh
firewall-cmd --permanent --new-ipset="ssh-allowed" --type=hash:ip
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.20" 
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.21" 
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.22"

# Esposizione dei servizi
firewall-cmd --permanent --zone="server-lan" --add-service="http"
firewall-cmd --permanent --zone="server-lan" --add-service="https"
firewall-cmd --permanent --zone="server-lan" --add-service="ssh"

# Per il local forward è necessario abilitare il routing verso loopback
sysctl -w net.ipv4.conf.eth0.route_localnet=1

# local forward 80 → 8080, 443 → 8443
firewall-cmd --permanent --zone="server-lan" --add-forward-port=port=80:proto=tcp:to-port=8080:to-addr=127.0.0.1
firewall-cmd --permanent --zone="server-lan" --add-forward-port=port=443:proto=tcp:to-port=8443:to-addr=127.0.0.1

# rich-rule per l'accesso filtrato ssh con local forward 22 → 2222
firewall-cmd --permanent --zone="server-lan" --add-rich-rule='rule family="ipv4" source ipset="ssh-allowed" forward-port port="22" protocol="tcp" to-port="2222" to-addr="127.0.0.1"'

Anche qui la configurazione rimane molto pulita e lineare.

Versione policy-oriented:

# creazione zona, binding su eth0, policy di default DROP
firewall-cmd --permanent --new-zone="server-lan"
firewall-cmd --permanent --zone="server-lan" --set-target=DROP
firewall-cmd --permanent --zone="server-lan" --add-interface=eth0

# icmp solo request
firewall-cmd --permanent --zone="server-lan" --add-icmp-block-inversion
firewall-cmd --permanent --zone="server-lan" --add-icmp-block=echo-request

# ipset per ssh
firewall-cmd --permanent --new-ipset="ssh-allowed" --type=hash:ip
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.20"
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.21"
firewall-cmd --permanent --ipset="ssh-allowed" --add-entry="192.168.10.22"

firewall-cmd --permanent --new-policy="lan-to-host"
firewall-cmd --permanent --policy="lan-to-host" --add-ingress-zone="server-lan"
firewall-cmd --permanent --policy="lan-to-host" --add-egress-zone=HOST
firewall-cmd --permanent --policy="lan-to-host" --set-target=REJECTED

# Esposizione dei servizi
firewall-cmd --permanent --policy="lan-to-host" --add-service="http"
firewall-cmd --permanent --policy="lan-to-host" --add-service="https"
firewall-cmd --permanent --policy="lan-to-host" --add-service="ssh"

# Per il local forward è necessario abilitare il routing verso loopback
sysctl -w net.ipv4.conf.eth0.route_localnet=1

# local forward 80 → 8080, 443 → 8443
firewall-cmd --permanent --policy="lan-to-host" --add-forward-port=port=80:proto=tcp:to-port=8080:to-addr=127.0.0.1
firewall-cmd --permanent--policy="lan-to-host" --add-forward-port=port=443:proto=tcp:to-port=8443:to-addr=127.0.0.1

# rich-rule per l'accesso filtrato ssh con local forward 22 → 2222
firewall-cmd --permanent --policy="lan-to-host" --add-rich-rule='rule family="ipv4" source ipset="ssh-allowed" forward-port port="22" protocol="tcp" to-port="2222" to-addr="127.0.0.1"'

N.B. In realtà, casi di questo genere che non rientrano nella casistica più ampia del routing e del dnat che giustificherebbero l'uso di regole firewall, si risolverebbero più elegantemente con un reverse proxy (posto che i servizi siano reverse-proxabili), agire quindi a livello applicativo (L7) invece che a livello di indirizzamento e trasporto (L3 e L4 rispettivamente.)

Infine, ciliegina sulla torta, vogliamo che sia filtrato anche il traffico in uscita consentendo solo il traffico web per gli aggiornamenti di sistema (80,443/tcp) e quello dns (53/udp)

Sarà necessario un policy object per il flusso in uscita, sia nella versione zone-oriented che in quella policy-oriented

# creazione policy object, default policy REJECT
firewall-cmd --permanent --new-policy=host-to-any
firewall-cmd --permanent --policy=host-to-any --set-target=REJECT

# definizione della direzione del traffico: HOST → dmz (eth0)
firewall-cmd --permanent --policy="host-to-any" --add-ingress-zone=HOST
firewall-cmd --permanent --policy="host-to-any" --add-egress-zone=myZone

# traffico consentito: http(s), dns
firewall-cmd --permanent --policy="host-to-any" --add-service=http
firewall-cmd --permanent --policy="host-to-any" --add-service=https
firewall-cmd --permanent --policy="host-to-any" --add-service=dns

Per la configurazione di un firewall personale, esempi di questo tipo coprono il 95% dei casi ordinari, anche senza considerare il filtro del traffico in uscita.

5.2. Caso 2: Configurazione di un fw personale su un host con 3 interfacce

Proviamo a immaginare uno scenario leggermente più complesso, un host con 3 interfacce per segregare la rete in 3 flussi: management, ingresso, uscita.

L'host espone un servizio web, fa delle richieste ad un server mysql nel backend e consente di essere contattato via ssh per il management. Dall'interfaccia di backend invece l'host non può essere contattato.

Le policy invece riguarderanno la gestione del traffico in ingresso, dalle rispettive interfacce solo per ssh e web, e del traffico in uscita solo verso il backend mysql.

# ip che possono accedere al management dell'host
firewall-cmd --permanent --new-ipset="ssh_allowed" --type="hash:net"
firewall-cmd --permanent --ipset="ssh_allowed" --add-entry="192.168.5.32/27" --add-entry="192.168.6.32/27"

# per aggregare http/https in un unico servizio
firewall-cmd --permanent --new-service="web"
firewall-cmd --permanent --service="web" --add-include="http" --add-include="https"

# zona di management
firewall-cmd --permanent --new-zone="mgmt"
firewall-cmd --permanent --zone="mgmt" --add-interface=eth0
firewall-cmd --permanent --zone="mgmt" --set-target=DROP

# zona del servizio web
firewall-cmd --permanent --new-zone="webserver"
firewall-cmd --permanent --zone="webserver" --add-interface=eth1
firewall-cmd --permanent --zone="webserver" --set-target=DROP

# zona del backend
firewall-cmd --permanent --new-zone="backend"
firewall-cmd --permanent --zone="backend" --add-interface=eth2
firewall-cmd --permanent --zone="backend" --set-target=DROP

# policy per il traffico WEBSERVER → HOST (traffico IN ↑)
firewall-cmd --permanent --new-policy="any-to-webserver"
firewall-cmd --permanent --policy="any-to-webserver" --add-ingress-zone=webserver
firewall-cmd --permanent --policy="any-to-webserver" --add-egress-zone=HOST
firewall-cmd --permanent --policy="any-to-webserver" --set-target=DROP
firewall-cmd --permanent --policy="any-to-webserver" --add-service="web"

# policy per il traffico MGMT → HOST (traffico IN ↑)
firewall-cmd --permanent --new-policy="mgmt-host"
firewall-cmd --permanent --policy="mgmt-host" --add-ingress-zone=mgmt
firewall-cmd --permanent --policy="mgmt-host" --add-egress-zone=HOST
firewall-cmd --permanent --policy="mgmt-host" --set-target=DROP
firewall-cmd --permanent --policy="mgmt-host" --add-rich-rule='rule family="ipv4" source ipset="ssh_allowed" service name="ssh" accept'

# policy per il traffico HOST → backend (traffico OUT ↓)
firewall-cmd --permanent --new-policy="webserver-to-backend"
firewall-cmd --permanent --policy="webserver-to-backend" --add-ingress-zone=HOST
firewall-cmd --permanent --policy="webserver-to-backend" --add-egress-zone=backend
firewall-cmd --permanent --policy="webserver-to-backend" --set-target=DROP
firewall-cmd --permanent --policy="webserver-to-backend" --add-rich-rule='rule family="ipv4" service name="mysql" accept'

Il fatto che ci siano servizi con restrizioni sul sorgente, mi permetterà di porre l'attenzione su una particolarità di firewalld poco evidente ma responsabile di errori talmente gravi da invalidare le nostre policy di sicurezza.

In generale, i servizi con restrizioni si gestiscono SEMPRE con rich-rule per evitare spiacevoli effetti laterali.

Le rich-rules danno la granularità necessaria in questi casi accettando solo il traffico indicato in zone default drop.

Come negli esempi precedenti, alle definizioni di zona posso sostituire quelle di policy se voglio un approccio policy-oriented.

5.3. Caso 3: Router Firewall-Gateway

Ora facciamo l'esempio del classico router-firewall domestico.

L'host avrà quattro interfacce per altrettante reti. Una wan, una rete guest, una dmz e un backend che ospita servizi.

Condizioni

Piano di indirizzamento

Host in DMZ

Host in Backend

###########
# Servizi #
###########
firewall-cmd --permanent --new-service="web"
firewall-cmd --permanent --service="web" --add-include="http" --add-include="https"

firewall-cmd --permanent --new-service="p2p"
firewall-cmd --permanent --service="p2p" --add-port="36119/tcp" --add-port="36119/udp"

########
# Zone #
########
firewall-cmd --permanent --new-zone="pubblica"
firewall-cmd --permanent --zone="pubblica" --add-interface="wan0"
firewall-cmd --permanent --zone="pubblica" --set-target=DROP

firewall-cmd --permanent --new-zone="dmz"
firewall-cmd --permanent --zone="dmz" --add-interface="eth0"
firewall-cmd --permanent --zone="dmz" --set-target=DROP

firewall-cmd --permanent --new-zone="p2p"
firewall-cmd --permanent --zone="p2p" --add-interface="eth1"
firewall-cmd --permanent --zone="p2p" --set-target=DROP

firewall-cmd --permanent --new-zone="backend"
firewall-cmd --permanent --zone="backend" --add-interface="wlan0"
firewall-cmd --permanent --zone="backend" --set-target=DROP

firewall-cmd --permanent --new-zone="guest"
firewall-cmd --permanent --zone="guest" --add-interface="wlan1"
firewall-cmd --permanent --zone="guest" --set-target=DROP


##########
# Policy #
##########
##############################
# pubblica-dmz (wan0 → eth0) #
##############################
# Dalla rete pubblica arrivano le richieste da inoltrare sui
# server interni (DMZ).
# Definizione policy con default target DROP.
firewall-cmd --permanent --new-policy="pubblica-to-dmz"
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-ingress-zone="pubblica"
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-egress-zone="dmz"
firewall-cmd --permanent --policy="pubblica-to-dmz" --set-target=DROP
# Inoltri consentiti verso il reverse proxy.
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-rich-rule='rule family="ipv4" forward-port port="80" protocol="tcp" to-port="8080" to-addr="192.168.1.2" accept'
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-rich-rule='rule family="ipv4" forward-port port="443" protocol="tcp" to-port="8443" to-addr="192.168.1.2" accept'
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-rich-rule='rule family="ipv4" forward-port port="36119" protocol="tcp" to-port="36119" to-addr="192.168.1.3" accept'
firewall-cmd --permanent --policy="pubblica-to-dmz" --add-rich-rule='rule family="ipv4" forward-port port="36119" protocol="udp" to-port="36119" to-addr="192.168.1.3" accept'

##############################
# dmz-backend (eth0 → wlan0) #
##############################
# Solo il reverse proxy (DMZ) può contattare mysql e nfs (Backend).
# Definizione policy con default target DROP.
firewall-cmd --permanent --new-policy="dmz-to-backend"
firewall-cmd --permanent --policy="dmz-to-backend" --add-ingress-zone="dmz"
firewall-cmd --permanent --policy="dmz-to-backend" --add-egress-zone="backend"
firewall-cmd --permanent --policy="dmz-to-backend" --set-target=DROP
# Transito consentito solo per rp verso mysql e per p2p verso nfs.
firewall-cmd --permanent --policy="dmz-to-backend" --add-rich-rule='rule family="ipv4" source address="192.168.1.2" destination address="192.168.2.2" service name="mysql" accept'
firewall-cmd --permanent --policy="dmz-to-backend" --add-rich-rule='rule family="ipv4" source address="192.168.1.3" destination address="192.168.2.3" service name="p2p" accept'

##############################
# dmz-pubblica (eth0 → wan0) #
##############################
# Abilitazione del solo browsing agli host (nattati) in DMZ.
# Abilitazione full all'host p2p (nattato) in DMZ.
# Definizione policy con default target DROP.
firewall-cmd --permanent --new-policy="dmz-to-pubblica"
firewall-cmd --permanent --policy="dmz-to-pubblica" --add-ingress-zone="dmz"
firewall-cmd --permanent --policy="dmz-to-pubblica" --add-egress-zone="pubblica"
firewall-cmd --permanent --policy="dmz-to-pubblica" --set-target=DROP
# Servizi
firewall-cmd --permanent --policy="dmz-to-pubblica" --add-rich-rule='rule family="ipv4" source address="192.168.1.2 service="web" masquerade accept'
firewall-cmd --permanent --policy="dmz-to-pubblica" --add-rich-rule='rule family="ipv4" source address="192.168.1.3 masquerade accept'

##################################
# backend-pubblica (eth0 → wan0) #
##################################
# Abilitazione del solo browsing degli host (nattati) in Backend.
# Qualunque altra richiesta verrà droppata dalle policy di zona.
# Definizione policy con default target DROP.
firewall-cmd --permanent --new-policy="backend-to-pubblica"
firewall-cmd --permanent --policy="backend-to-pubblica" --add-ingress-zone="backend"
firewall-cmd --permanent --policy="backend-to-pubblica" --add-egress-zone="pubblica"
firewall-cmd --permanent --policy="backend-to-pubblica" --set-target=DROP
# servizi
firewall-cmd --permanent --policy="backend-to-pubblica" --add-service="web"
firewall-cmd --permanent --policy="backend-to-pubblica" --add-service="dns"
# masquerade
firewall-cmd --permanent --policy="backend-to-pubblica" --add-masquerade

#################################
# guest-pubblica (wlan1 → wan0) #
#################################
# I client, nattati, hanno pieno accesso alla rete pubblica.
# Qualunque richiesta verso altre reti, verrà droppata
# dalle policy di zona.
# Definizione policy con default target ACCEPT.
firewall-cmd --permanent --new-policy="guest-to-pubblica"
firewall-cmd --permanent --policy="guest-to-pubblica" --add-ingress-zone="guest"
firewall-cmd --permanent --policy="guest-to-pubblica" --add-egress-zone="pubblica"
firewall-cmd --permanent --policy="guest-to-pubblica" --set-target=ACCEPT
# masquerade
firewall-cmd --permanent --policy="guest-to-pubblica" --add-masquerade

5.4. Trappole nascoste

Può capitare che definizioni di zone poco accorte aprano le maglie della gabbia che noi vorremmo cucire attorno all'esposizione dei servizi.

Vediamo un po' di scenari per analizzare le conseguenza delle scelte di design.

Per semplicità esaminerò il caso di un singolo host (192.168.1.2) che ospita un servizio web e un servizio mysql che vogliamo confinare con firewalld nel seguente modo:

5.4.1. Caso 1. Restrizioni con solo --add-source

firewall-cmd --permanent --new-zone=zone1
firewall-cmd --permanent --zone=zone1 --set-target=REJECT
firewall-cmd --permanent --zone=zone1 --add-source=192.168.1.3
firewall-cmd --permanent --zone=zone1 --add-service=http
firewall-cmd --permanent --zone=zone1 --add-service=https

firewall-cmd --permanent --new-zone=zone2
firewall-cmd --permanent --zone=zone2 --set-target=REJECT
firewall-cmd --permanent --zone=zone2 --add-source=192.168.1.4
firewall-cmd --permanent --zone=zone2 --add-service=mysql

firewall-cmd --set-default-zone=zone2

Questo tipo di configurazione è soggetto a quello che viene chiamato “zone hijacking” che permette a ip non autorizzati di accedere a risorse a cui non dovrebbero avere accesso situate nella zona di default o di fallback.

Nel caso specifico, un ip come 192.168.1.10, o qualunque altro a cui le policy di routing consentano di bussare lecitamente all'interfaccia, chiede di accedere a mysql, avrà il via libera vanificando tutte le nostre politiche di confinamento.

Vediamo perché succede questo.

  1. 192.168.1.10 bussa alla porta 3306
  2. Step 1: Verifica sorgenti
    1. 192.168.1.10 corrisponde a zone1 (192.168.1.3)? NO.
    2. 192.168.1.10 corrisponde a zone2 (192.168.1.4)? NO.
  3. Step 2: Verifica interfacce
    1. Non ci sono interfacce associate
  4. Il pacchetto viene consegnato alla zona di fallback, zone2, per l'elaboraziona finale, richiede lecitamente l'accesso a mysql che viene autorizzato → ❌ ERRORE!

5.4.2. Caso 2: Combinazione di --add-source e --add-interface

Quando l'ingresso alla zona viene mediato da una combinazione sorgenti e interfacce, non c'è il rischio di zone hijacking ma può essere ugualmente insidioso se non si capisce bene che fine facciano i pacchetti.

In generale basta ricordarsi sempre che la presenza di sorgenti e interfacce ha l'obiettivo opposto della restrizione perché va ad estendere l'insieme degli aventi diritto al servizio.

Facciamo sempre conto che 192.168.1.10 provi a bussare sulla 3306.

firewall-cmd --permanent --new-zone=zone1
firewall-cmd --permanent --zone=zone1 --set-target=REJECT
firewall-cmd --permanent --zone=zone1 --add-source=192.168.1.3
firewall-cmd --permanent --zone=zone1 --add-service=http
firewall-cmd --permanent --zone=zone1 --add-service=https

firewall-cmd --permanent --new-zone=zone2
firewall-cmd --permanent --zone=zone2 --set-target=REJECT
firewall-cmd --permanent --zone=zone2 --add-source=192.168.1.4
firewall-cmd --permanent --zone=zone2 --add-interface=eth0
firewall-cmd --permanent --zone=zone2 --add-service=mysql

firewall-cmd --set-default-zone=zone2

Anche facendo finta di non accorgersi che l'add-source della zona2 sia ridondante avendo la disponibilità dell'intera interfaccia che lo contiene, l'accesso in questo caso sarà consentito proprio perchè nello step2 della valutazione, la presenza dell'interfaccia fa ricadere il pacchetto nella zona 2 (accesso consentito) → ⚠️ ATTENZIONE!

Se invece spostassi il binding dell'interfaccia nella zona1,

firewall-cmd --permanent --new-zone=zone1
firewall-cmd --permanent --zone=zone1 --set-target=REJECT
firewall-cmd --permanent --zone=zone2 --add-interface=eth0
firewall-cmd --permanent --zone=zone1 --add-source=192.168.1.3
firewall-cmd --permanent --zone=zone1 --add-service=http
firewall-cmd --permanent --zone=zone1 --add-service=https

firewall-cmd --permanent --new-zone=zone2
firewall-cmd --permanent --zone=zone2 --set-target=REJECT
firewall-cmd --permanent --zone=zone2 --add-source=192.168.1.4
firewall-cmd --permanent --zone=zone2 --add-service=mysql

firewall-cmd --set-default-zone=zone2

il binding dell'interfaccia fa ricadere la richiesta nella zona1 che rifiuta l'accesso → ⚠️ ATTENZIONE!

5.4.3. Remediation per la “zone hijacking”

L'utilizzo delle sole sorgenti per restringere l'accesso non è sufficiente perché ip che transitano sull'host e non appartengano a nessuna sorgente di zona, possono richiedere lecitamente i servizi appartenenti alla zona di fallback

Ci sono un paio di modi per evitare questo tranello.

Uno consiste nell'usare le rich-rules come abbiamo fatto nella gestione delle zone di management e di backend del punto 5.2. Caso 2: Configurazione di un fw personale su un host con 3 interfacce. Accesso pieno alle sole interfacce e i sorgenti nelle rich-rules sono le white-list che consentono puntualmente chi può accedere a cosa.

Un altro metodo consiste nell'usare come zona di default, una zona senza alcun binding, con lo scopo di droppare tutto ciò che vi finisce dentro.

firewall-cmd --permanent --new-zone=default-drop
firewall-cmd --permanent --zone=default-drop --set-target=DROP
firewall-cmd --set-default-zone=default-drop

In questo modo la zona di default che riceverà il pacchetto sospeso non avrà alcun servizio associato a differenza di prima, ma dropperà tutto ciò che incontra.

6. Tips

6.1. Le azioni di firewall-cmd

Per districarsi rapidamente nella sintassi di firewall-cmd, mi viene comodo ricordare “le azioni” distintive principali in un'ottica CRUD:

E più precisamente:

6.2. Comandi utili

Di seguito. un sottinsieme di comandi già visti, utili per catturare velocemente informazioni sulla configurazione del firewall.

Elenco di tutti gli ipset:

firewall-cmd --permanent --get-ipsets

Elenco di tutti i tipi di ipset:

firewall-cmd --get-ipset-types

Elenco di tutti tipi di icmp:

firewall-cmd --get-icmptypes

Elenco di tutti i servizi:

firewall-cmd --get-services

Individuare la zona dall'interfaccia:

firewall-cmd --permanent --get-zone-of-interface="<nome interfaccia>"

Individuare la zona dal sorgente:

firewall-cmd --permanent --get-zone-of-source=ip | MAC | ipset:"<nome_ipset>"

Zona di default:

firewall-cmd --get-default-zone

Zone attive:

firewall-cmd --get-active-zones

Elenco di tutte le zone/policies:

firewall-cmd --get-zones
firewall-cmd --get-policies

Dettaglio delle configurazione di tutte le zone/policy:

firewall-cmd --list-all-zone
firewall-cmd --list-all-policies

Dettaglio della configurazione di una zona/policy:

firewall-cmd --permanent --info-zone="<nome_zona>"
firewall-cmd --permanent --info-policy="<nome_policy>"

Elenco di tutti i servizi di una zona/policy:

firewall-cmd --zone="<nome_zona>" --list-services
firewall-cmd --policy="<nome_policy>" --list-services

Elenco di tutte le porte di una zona/policy:

firewall-cmd --permanent --zone="<nome_zona>" --list-ports
firewall-cmd --permanent --policy="<nome_policy>" --list-ports

Elenco di tutte le rich rules di una zona/policy:

firewall-cmd --permanent --zone="<nome_zona>" --list-rich-rules
firewall-cmd --permanent --policy="<nome_policy>" --list-rich-rules

Elenco di tutte le forward ports di una zona/policy:

firewall-cmd --permanent --zone="<nome_zona>" --list-forward-ports
firewall-cmd --permanent --policy="<nome_policy>" --list-forward-ports

Dettaglio della configurazione di un servizio:

firewall-cmd --permanent --info-service="<nome_servizio>"

Elenco di tutte le porte di un servizio:

firewall-cmd --permanent --service="<nome_servizio>" --get-ports

Dettaglio della configurazione di un ipset:

firewall-cmd --permanent --info-ipset="<nome_ipset>"

Elenco di tutte le entry di un ipset:

firewall-cmd --permanent --ipset="<nome_ipset>" --get-entries

6.3. Considerazioni di contorno

Concludendo, firewalld, in quanto frontend fedele di nftables, rappresenta il futuro del firewalling linux.

Aspetti comuni:

  1. Configurare i servizi ove possibile è sempre da preferire rispetto a porte/protocolli sparati direttamente nella zona o nella policy.
  2. Ricorrere alle rich-rules solo se si devono gestire flussi puntuali, altrimenti è sufficiente il bind dell'interfaccia alla zona.

Approccio conservativo: Per configurazioni semplici, bastano zone e servizi.

In presenza di più interfacce, obbligatorio anche il ricorso ai policy object, perché ci sarà da gestire di sicuro traffico interzona.

Approccio spregiudicato: Delegare alle zone solo il trust delle sorgenti e spostare la logica dei flussi interamente ai policy object.

#firewall #firewallcmd #firewalld #iptables #netfilter #nftables #ufw