Ok, due sezioni fa abbiamo detto al kernel di buttare via tutto quanto può avere a che fare col nostro computer. Per andare avanti nella nostra configurazione, dobbiamo quindi indicare cosa invece lasciare passare. Questo si fa aggiungendo delle regole. Una soluzione potrebbe essere quella di aggiungere tutte le regole relative al traffico che vogliamo lasciare passare nelle 3 catene di base. Funzionerebbe, ma vi posso garantire che sarebbe molto facile commettere degli errori e vi posso garantire che altrettanto facilmente diventerebbero così tante da non poter essere più gestibili.
Uno degli approcci più semplici nella gestione delle regole di firewalling è quindi quello di dividere il traffico in ``flussi'' di dati dopodichè considerare indipendentemente ogni singolo flusso, evitando così di avere tutte le regole insieme e formalizzandosi in modo da evitare gli errori più comuni.
Analizzando il nostro esempio, potremmo quindi dividere il nostro traffico in 6 grandi flussi:
Ma andiamo per gradi: iniziamo a creare delle nuove catene. Per fare questo dobbiamo eseguire iptables seguito da un ``-N'' e da un nome per la catena:
16: iptables -N landmz #dalla scheda di rete eth0 alla scheda di rete eth1
17: iptables -N laninet #dalla scheda di rete eth0 alla scheda di rete eth2
18: iptables -N dmzinet #dalla scheda di rete eth1 alla scheda di rete eth2
19: iptables -N dmzlan #dalla scheda di rete eth1 alla scheda di rete eth0
20: iptables -N inetdmz #dalla scheda di rete eth2 alla scheda di rete eth1
21: iptables -N inetlan #dalla scheda di rete eth2 alla scheda di rete eth0
Per il kernel, però, il nome che abbiamo dato ed il commento non hanno alcun
significato. Dobbiamo quindi trovare un modo per indicare quali catene devono essere
percorse in quali situazioni. Per fare questo, possiamo specificare 6 semplici
regole. Trattandosi di regole per traffico che non è né originato
né destinato al firewall, dovremo inserirle nella catena di FORWARD.
Per esempio, potremmo dare dei comandi come:
23: iptables -A FORWARD -i eth0 -o eth1 -j landmz
24: iptables -A FORWARD -i eth0 -o eth2 -j laninet
25: iptables -A FORWARD -i eth1 -o eth2 -j dmzinet
26: iptables -A FORWARD -i eth1 -o eth0 -j dmzlan
27: iptables -A FORWARD -i eth2 -o eth1 -j inetdmz
28: iptables -A FORWARD -i eth2 -o eth0 -j inetlan
Il significato di queste righe è molto semplice: aggiungi (-A, append) alla
catena FORWARD, una regola per cui se un pacchetto proviene dalla scheda
di rete eth0 (-i) ed è destinato ad uscire dalla scheda eth1 (-o), la sua
sorte deve essere decisa (-j) dalla catena landmz e così via. In pratica,
abbiamo diviso tutti i pacchetti in transito nella catena di forward in
6 categorie, dove ogni categoria ha associata una catena e quindi una
serie di regole. Riassumendo:
A questo punto però, vi sarà venuto spontaneo chiedervi perché effettuare una prima classificazione in base all'hardware e non, ad esempio, in base all'indirizzo ip sorgente o all'indirizzo ip destinazione. Ancora una volta, la risposta è molto semplice: il contenuto di un pacchetto si può falsificare. Il fatto che arrivi su un'interfaccia piuttosto che un'altra, no. Utilizzando almeno per le prime regole le interfacce di input e quelle di output, avremo quindi la certezza che i pacchetti saranno valutati dalle regole contenute nella catena corretta e che nessuno potrà imbrogliarci utilizzando ip fasulli (ancora, cosa succederebbe se basassimo le nostre regole solo sugli indirizzi ip e qualcuno ci mandasse dei pacchetti provenienti da 127.0.0.1 con l'rp_filter disabilitato?).
Ricordandoci della configurazione della nostra ipotetica rete, abbiamo deciso che dalla nostra lan vogliamo che tutte le richieste per pagine web vengano deviate sul nostro server proxy (transparent proxy) in maniera trasparente, che i nostri utenti non controllino altre caselle di posta elettronica se non quelle da noi fornite, che possano collegarsi al nostro server web direttamente e che siano in grado di scaricare file con ftp direttamente da internet. Dall'esterno, vogliamo soltanto rendere accessibile il nostro dns, il server di posta elettronica, il nostro server web ed il server ftp. Ma lavoriamo anche qua per flussi, in modo da semplificare un po' le cose.
Iniziamo allora a creare le regole per la nostra lan, lasciando indietro (per ora) ciò che riguarda il ``deviare''. Dalla lan alla dmz, vogliamo quindi consentire:
30: iptables -A landmz -s ! 192.168.200.0/24 -j DROP
31: iptables -A landmz -p tcp -d nostro.server.web --dport www -j ACCEPT
32: iptables -A landmz -p tcp -d nostro.server.smtp --dport smtp -j ACCEPT
33: iptables -A landmz -p tcp -d nostro.server.pop3 --dport pop3 -j ACCEPT
34: iptables -A landmz -p tcp -d nostro.server.proxy --dport webcache -j ACCEPT
35: iptables -A landmz -p tcp -d nostro.dns --dport domain -j ACCEPT
36: iptables -A landmz -p udp -d nostro.dns --dport domain -j ACCEPT
Prima di tutto, qua dentro sarebbe molto meglio indicare direttamente
degli indirizzi ip al posto dei nomi dei computer. Come abbiamo già detto,
infatti, il protocollo dei DNS deve essere considerato insicuro. Se proprio
volessimo usare i nomi dei computer, potremmo utilizzare il vecchio file
degli hosts, in /etc/hosts, dove ogni linea è formata dall'indirizzo
ip di un computer seguito dal suo nome (e separati da spazi).
Come potete vedere, questa volta le regole le ``appendiamo'' (-A) non più alla catena di FORWARD bensì alla catena landmz, trattandosi di regole che andranno a discriminare il traffico tra la nostra lan e la nostra dmz.
Ora, la prima regola indica che non vogliamo che passi niente che non abbia un indirizzo ip proveniente dalla nostra rete interna. Questa regola eviterà quindi che qualcuno dei nostri utenti possa fare spoofing verso la nostra dmz o che comunque tenti di imbrogliarci con dei giochi strani sull'indirizzo ip. E' una regola un po' superflua, avendo già abilitato la protezione del kernel, ma a volte è meglio aggiungere qualche regola in più piuttosto che avere qualche regola in meno (e poi, siete sicuri che nelle prossime versioni del kernel il filtro sarà ancora disponibile o che si comporterà sempre nello stesso modo?).
Le altre sono ancora regole molto semplici. Le uniche novità introdotte rispetto prima sono la negazione (il !), che indica che perché una regola venga applicata una certa condizione non deve essere soddisfatta, ed il fatto che finalmente potete vedere diversi criteri messi in pratica. -s, specifica un ip sorgente e può essere seguito da un indirizzo ip o da un indirizzo di rete (indicato come x.x.x.x/y) o dal nome di un host. -p può essere seguito dal nome o dal numero di un protocollo (file /etc/protocols conserva le associazioni). -d, ancora, indica la destinazione di un pacchettino e può essere seguito sempre da un indirizzo ip, da un nome di host o da un indirizzo di rete (con lo stesso formato indicato prima). --dport, invece, indica una porta di destinazione e può essere seguito da un numero o dal nome di una porta (il file /etc/services conserva le associazioni). In tutti i casi, è possibile usare la negazione dopo l'indicazione del criterio.
Infine, ACCEPT dice ad iptables di accettare tali pacchetti nel caso in cui la regola risulti applicabile. Per configurare un firewall, è necessaria una buona conoscenza dei vari protocolli di rete: l'ultima riga è stata aggiunta in quanto il protocollo per la comunicazione con i dns utilizza il più delle volte connessioni udp, mentre utilizza connessioni tcp solo in condizioni particolari. Senza questa regola, il dns avrebbe funzionato sempre tranne qualche volta, e sarebbe stato estremamente difficile trovare il problema (se mai ce ne fossimo resi conto).
Per chi di voi si fosse invece chiesto come si fa a specificare una rete o che senso ha il /24, basti sapere che si tratta di un sistema estremamente comodo per indicare le netmask. Per esempio, una netmask 255.255.255.0 indica che se i primi 24 bit di due indirizzi ip sono uguali, allora i due si trovano sulla stessa rete (255.255.255.0 scritto in notazione binaria sarebbe 11111111.11111111.11111111.00000000, con 24 uno). 255.255.255.0 è quindi equivalente a /24 (ogni ottetto - 255 - sono 8 bit).
Visto però che si tratta delle prime regole ``serie'' della nostra trattazione, vediamo comunque di descriverle a parole, un po' come abbiamo fatto prima:
L'ultima regola indicata tra parentesi, poi, non è stata da noi scritta: è implicitamente aggiunta dalla policy (DROP) che abbiamo deciso di utilizzare.
L'esperienza però mi insegna che a questo punto potrebbe essere conveniente inserire come regola 38 qualcosa di simile a:
38: iptables -A landmz -p tcp -j REJECT --reject-with tcp-reset
Questo ha a che vedere con i meandri del tcp. Ma vediamo di spiegarla in poche parole: quando
avviene una connessione TCP/IP, ha luogo il solito ``three way handshake''. In pratica, per
avere la certezza che i pacchetti arrivino a destinazione, il computer A manda un messaggio
del tipo ``questo è il mio numero, e se ci sei, mandami il tuo'', dopodichè il computer B
risponde con un messaggio del
tipo ``ho ricevuto il tuo numero, questo è il mio'', infine il computer A risponde dicendo ``ho ricevuto
il tuo numero'' e la comunicazione ha inizio. Normalmente però, rispettando il protocollo TCP/IP,
se il computer A dovesse tentare di collegarsi ad una porta chiusa (ad un servizio non disponibile) su
B, allora B dovrebbe rispondere con ``la porta è chiusa''.
Ma vediamo cosa succede nel nostro caso:
Vediamo però che il target REJECT è stato specificato semplicemente con un -j, come per tutti gli altri target. Quando si parla di target, infatti, non c'è alcuna differenza tra ``estensioni'' esterne e target forniti direttamente da iptables, se non la pagina di manuale.
Alcuni di voi si potranno chiedere a questo punto ``ma che diavolo, cosa mi interessa se l'altro tentando di accedere ad una porta non consentita rimane in attesa?? Così impara per la prossima volta...''. Beh, ci sono almeno tre buoni motivi per non lasciarlo in attesa:
Il problema è che questi sistemi non vi fanno accedere al servizio fino a quando non scoprono la vostra identità o non appurano che il vostro sistema non offre questo servizio (rispondendo che la porta è chiusa). In entrambi i casi, il sistema remoto rimane in attesa di una risposta, e se questa non arriva, può ipotizzare due cose:
L'esempio più classico è questo, ma altri protocolli usano un approccio simile, e vi posso garantire che una regola come la 38 può evitarvi diversi delay nell'utilizzo della rete altrimenti difficilmente spiegabili.
Ok, qua (a dirsi) le cose sono molto più semplici: dobbiamo consentire soltanto quei pacchettini in risposta alle richieste partite dalla LAN (i server non si devono connettere di loro spontanea volontà ai nostri client -- teoricamente, nessuno dovrebbe usarli).
Fare questo in ipchains era una cosa abbastanza complessa: bisognava specificare delle regole per consentire ogni tipo di pacchetto che ci sarebbe potuto tornare in risposta (giocando con l'opzione --syn, che seleziona i pacchetti che stabiliscono nuove connessioni) e si doveva quindi avere una buona conoscenza (se non ottima) dei vari protocolli. iptables facilita molto le cose tramite l'introduzione di ``moduli per il tracciamento delle connessioni'' (conntrack -- uno dei molti vantaggi dei firewall statefull rispetto quelli stateless).
Vediamo quindi una delle soluzioni che potremmo adottare:
40: iptables -A dmzlan -s ! 123.45.67.9/29 -j DROP
41: iptables -A dmzlan -m state --state ESTABLISHED,RELATED -j ACCEPT
42: iptables -A dmzlan -p tcp -j REJECT --reject-with tcp-reset
Bene, il primo comando è la solita regola per evitare lo spoofing.
Il secondo invece, è diverso da ogni altro comando finora incontrato:
-m chiede ad iptables di caricare un modulo esterno, in questo caso il modulo
state (si ricorda lo stato delle connessioni), per il tracciamento delle connessioni.
L'opzione --state relativa
al modulo state è quello che ci permette di identificare i pacchetti: in pratica,
la regola ha come significato quello di consentire tutti i pacchetti
facenti parte connessioni già stabilite (ESTABLISHED, e se sono già stabilite vuol dire
che sono state consentite) e tutte quelle connessioni relative a connessioni già esistenti (RELATED).
In questo caso, la parolina magica è proprio RELATED. Ci consente cioè di scaricare sul modulo state la responsabilità di identificare i pacchettini che non soltanto sono risposte a pacchetti già inviati, ma anche quelli che fanno parte dello stesso protocollo.
Vediamo però qualche esempio:
Se state configurando un firewall, però, e la vostra preoccupazione principale è la sicurezza, vi sarà sorta spontanea una domanda: ``e se qualcuno imbrogliasse il modulo di state, facendo aprire connessioni non volute?''. Bene, in passato qualche problema di questo tipo è stato sollevato, ma il codice del netfilter dovrebbe essere attualmente abbastanza maturo per evitare questo tipo di problemi.
Vi consiglio però di seguire le mailing list dedicate, per essere eventualmente prontamente avvisati di possibili problemi, e di leggere qualche documento in più sul funzionamento del protocollo ftp.
Attenzione però che perché le regole sopra elencate funzionino, potrebbe essere necessario caricare dei moduli nel kernel, con comandi del tipo ``modprobe ip_conntrack'', ``modprobe ip_conntrack_ftp'', ``modprobe ip_conntrack_altri_moduli_di_protocolli_che_volete_utilizzare''.
Per quanto riguarda le prestazioni, il modulo state utilizza un po' più di risorse rispetto alle regole manuali, ma comunque nulla di rilevante se rapportato ai vantaggi che offre (alcune cose, non è proprio possibile farle con firewall stateless, a meno di non aprire migliaia di porte).
Un'altra cosa da dire è che prima, nella definizione della catena dalla LAN alla DMZ, ho volutamente dimenticato la regola 37:
37: iptables -A landmz -m state ESTABLISHED,RELATED -j ACCEPT
che sarebbe stata troppo prematura da discutere. In pratica, anche dalla
lan alla dmz è importante far passare tutti quei dati relativi a connessioni
già stabilite o comunque i messaggi di errore.
Probabilmente non molto importante per reti di piccole dimensioni,
ma rilevante in reti più grosse o con regole più complicate. Anche questa regola la
vedrete apparire molto spesso.
Dalla nostra lan ad internet vogliamo invece essenzialmente che passi traffico ftp (che non può essere deviato sul proxy), in modo che gli utenti possano scaricare direttamente il loro materiale. Per fare questo, basta aggiungere le regole:
44: iptables -A laninet -s ! 192.168.200.0/24 -j DROP
45: iptables -A laninet -p tcp --dport ftp -j ACCEPT
48: iptables -A laninet -m state ESTABLISHED,RELATED -j ACCEPT
49: iptables -A laninet -p tcp -j REJECT --reject-with tcp-reset
Dove però non c'è nulla di nuovo di cui parlare...
Infine, per terminare con la nostra lan:
50: iptables -A inetlan -s 192.168.200.0/24 -j DROP
51: iptables -A inetlan -s 123.15.67.9/29 -j DROP
52: iptables -A inetlan -m state --state ESTABLISHED,RELATED -j ACCEPT
53: iptables -A inetlan -p tcp -j REJECT --reject-with tcp-reset
In questo caso, le prime due regole bloccano tutti quei pacchetti
che hanno come mittente l'indirizzo di una delle nostre due reti.
La terza regola, invece, consente a tutte le connessioni create dall'interno di poter ricevere una risposta, senza dover scrivere troppe regole.
All'epoca di ipchains invece, non c'era un modo di ``tenere traccia'' delle connessioni ed era quindi necessario aprire molte più porte per consentire a ftp di funzionare, cosa che diminuiva notevolmente l'efficacia del firewall.
Andiamo avanti ora con la configurazione della nostra dmz. Abbiamo detto che da internet saranno accessibili i seguenti servizi:
54: iptables -A inetdmz -s 192.168.200.0/24 -j DROP
55: iptables -A inetdmz -s 123.15.67.9/29 -j DROP
56: iptables -A inetdmz -p tcp -d nostro.server.web --dport www -j ACCEPT
57: iptables -A inetdmz -p tcp -d nostro.server.smtp --dport smtp -j ACCEPT
58: iptables -A inetdmz -p tcp -d nostro.dns --dport domain -j ACCEPT
59: iptables -A inetdmz -p udp -d nostro.dns --dport domain -j ACCEPT
60: iptables -A inetdmz -p tcp -d nostro.ftpserver --dport ftp -j ACCEPT
61: iptables -A inetdmz -m state --state ESTABLISHED,RELATED
62: iptables -A inetdmz -p tcp -j REJECT --reject-with tcp-reset
In questo caso, la prima regola è un po' superflua ma estremamente
importante. I nostri server, infatti, potrebbero essere configurati per essere un
po' meno restrittivi nei confronti dei nostri utenti. Se qualcun altro
riuscisse da internet ad usare i nostri indirizzi ip, sarebbe in
grado di usufruire di questi benefici.
Dalla DMZ ad Internet, invece
63: iptables -A dmsinet -s ! 123.15.67.9/29 -j DROP
64: iptables -A dmzinet -p tcp -s nostro.server.smtp --dport smtp -j ACCEPT
65: iptables -A dmzinet -p udp -s nostro.server.dns --dport domain -j ACCEPT
66: iptables -A dmzinet -p tcp -s nostro.server.dns --dport domain -j ACCEPT
67: iptables -A dmzinet -p tcp -s nostro.server.proxy --dport www -j ACCEPT
68: iptables -A dmzinet -m state --state ESTABLISHED,RELATED -j ACCEPT
69: iptables -A dmzinet -p tcp -j REJECT --reject-with tcp-reset
Qui qualche commento è doveroso farlo...
La prima regola consente al server smtp di inviare la posta elettronica dei nostri utenti.
Dello stesso tipo sono la seconda e terza regola, che consentono invece al dns server di effettuare delle query ricorsive ed eventualmente degli zone transfer.
La quarta regola, infine, è quella che consente al proxy server di uscire all'esterno a procurarsi le pagine non in cache. Attenzione, però, che a secondo delle configurazioni del vostro proxy server, potrebbe essere necessario aprire più porte (per fare comunicare più proxy tra di loro, per consentire l'utilizzo al proxy del protocollo ftp...).
Le ultime due sono le solite regole: consenti le risposte alle richieste effettuate, chiudi le connessioni tcp vietate.
Come ultima cosa, ci rimane da filtrare il traffico originato o destinato al firewall direttamente. Giusto a titolo esemplificativo, ecco alcune regole:
71: iptables -A INPUT -m limit --limit 10/min -p tcp --syn --dport ssh -j ACCEPT
72: iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
73: iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
74: iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
75: iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Buona norma sarebbe quello di consentire il meno possibile verso il
firewall, e di bloccare tutto ciò che può da lui essere originato.
In questo caso, viene accettato il traffico ssh ed i ping (non sempre una buona
idea) e tutto il traffico relativo, mentre in uscita vengono consentite solo le risposte ai
pacchettini ricevuti. Se utilizzate demoni come zebra o simili, potrebbe essere
necessario aprire più porte ed allentare un po' la cintura soprattutto per quanto
riguarda icmp. Ricordatevi inoltre che se non viene specificata esplicitamente
una interfaccia, la catena di INPUT e di OUTPUT determineranno il comportamento
di input e output da tutte le interfacce (compreso il loopback!!!). State
quindi molto attenti a quello che fate.
Potete vedere anche l'utilizzo di un nuovo modulo: il limit, che consente di impostare dei limiti di frequenza, e per la prima volta dell'opzione (tcp) --syn, che seleziona soltanto le nuove connessioni (i famosi pacchettini ``mandami il tuo numero''). Questo per evitare un problema con ssh segnalato diverso tempo fa (e probabilmente già corretto) per cui in determinate condizioni e con reti molto veloci era possibile fare hijacking di una connessione tentando di indovinare alcuni parametri.
Ultima cosa importante da dire prima di passare ad altro: a differenza da ipchains, in iptables ciò che non è esplicitamente consentito non passa (o vice versa, a secondo della policy). Per esempio, nel nostro caso un client interno alla rete non potrebbe ``pingare'' alcun server all'esterno né sarebbe in grado di utilizzare altri messaggi ICMP che non siano imparentati (related) con connessioni esistenti.
Con questo paragrafo viene chiuso l'argomento filtraggio. Nel prossimo paragrafo si parlerà infatti del NAT e di come fare quelle ``deviazioni'' di cui ci siamo temporaneamente dimenticati.
Lo scopo era quello di introdurre l'utente all'uso di iptables e di metterlo nelle condizioni di poter leggere facilmente le pagine del manuale, dove può trovare un elenco di tutti i possibili match che si possono effettuare (divisi per protocollo e modulo) e di tutti i target che si possono utilizzare. Per le target extension installate con il patch-o-matic, riferitevi alla documentazione fornita.