W pracy administratora wielokrotnie spotykałem się z sytuacją konieczności analizy działającego firewalla opartego o iptables.
Udostępnij!
Reguły iptables na bardziej skomplikowanych systemach zarządzanych przez kilku ludzi często mają tendencję do “rośnięcia”. Czasami ktoś jakąś regułę doda, czasami coś zmieni, czasami zostaną dodane nowe reguły przez jakieś oprogramowanie… Administratorzy często mają różny styl tworzenia firewall – ktoś może preferować dodanie reguł na początku łańcucha przez komendę I, ktoś inny na końcu poprzez A. Przy kilku tysiącach reguł, w sytuacji gdy reguły nie były dodawane z jednego miejsca, do tego gdy są porozdzielane na różne łańcuchy, z których często występują odwołania do innych łańcuchów tudzież ruch jest oznaczany za pomocą markowania a potem filtrowany na podstawie oznaczeń – połapanie się co odpowiada za blokowanie albo przepuszczenie konkretnego ruchu nie jest proste. A tym bardziej bezpieczne zmodyfikowanie takiego firewalla i sprawdzenie jak konkretna modyfikacja wpłynie na przepuszczenie konkretnego ruchu.
W artykule postaram się pokazać, jak za pomocą Linuksowego mechanizmu netns służącego do wirtualizacji stosu sieciowego można stosunkowo prosto zasymulować działanie Linuksowego routera oraz znaleźć pod jakie reguły iptables podpada konkretny ruch.
Dla przykładu – zasymulujemy środowisko testowe składające się z:
- Routera R posiadajacego dwa interfejsy sieciowe:
- ppp0 z adresem 172.20.1.2/24 i bramą 172.20.1.1, za którą stoi komputer A
- eth0 z adresem 192.l168.1.1/24 który jest bramą dla komputera B
oraz zestawem reguł iptables (zrzut z iptables-save):
# Generated by iptables-save v1.4.21 on Sat Jan 7 15:00:51 2017
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 1000:2000 -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -s 192.168.1.0/32 -p tcp -m tcp --dport 1234 -j ACCEPT
-A FORWARD -s 192.168.1.0/24 -j ACCEPT
COMMIT
# Completed on Sat Jan 7 15:00:51 2017
# Generated by iptables-save v1.4.21 on Sat Jan 7 15:00:51 2017
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.1.100/32 -j MASQUERADE
-A POSTROUTING -s 192.168.1.0/28 -j ACCEPT
-A POSTROUTING -s 192.168.1.10/32 -j MASQUERADE
COMMIT
# Completed on Sat Jan 7 15:00:51 2017
- Komputera A o adresie 172.20.1.1/24 będącego za interfejsem ppp0 routera R który udostępnia jakąś usługę na porcie 1234
- Komputera B o adresie 192.168.1.10/24 który będzie próbował podłączyć się na adres 172.20.1.1 na port 1234
Oraz sprawdzimy dlaczego powyższe reguły nie pozwalają na podłączenie się do usługi.
Na początku utwórzmy wirtualny router:
root@aramin:~# ip netns add R
root@aramin:~# ip netns exec R ip link set dev lo up
root@aramin:~# ip netns exec R sysctl -w net.ipv4.conf.default.forwarding=1
Następnie zobaczmy jak wygląda on “sieciowo” w środku:
root@aramin:~# ip netns exec R bash
root@aramin:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
root@aramin:~#
Jak widać – po uruchomieniu w nim bash widać tylko jeden interfejs siecowy – loopback.
Utwórzmy więc netnsy dla komputerów A i B:
root@aramin:~# ip netns add A
root@aramin:~# ip netns exec A ip link set dev lo up
root@aramin:~# ip netns add B
root@aramin:~# ip netns exec B ip link set dev lo up
Komendą ip netns exec B bash – możemy uruchomić powłokę w środowisku sieciowym B.
Następnie – dodajmy interfejsy do konkretnych netnsów
root@aramin:~# ip link add R-ppp0 type veth peer name A-eth0
root@aramin:~# ip link set R-ppp0 netns R
root@aramin:~# ip link set A-eth0 netns A
root@aramin:~# ip link add R-eth0 type veth peer name B-eth0
root@aramin:~# ip link set R-eth0 netns R
root@aramin:~# ip link set B-eth0 netns B
I pozmieniajmy im nazwy, tak by odpowiadały środowisku które chcemy zamodelować:
ip netns exec R ip link set dev R-ppp0 name ppp0 up
ip netns exec A ip link set dev A-eth0 name eth0 up
ip netns exec R ip link set dev R-eth0 name eth0 up
ip netns exec B ip link set dev B-eth0 name eth0 up
Po tym wszystkim – zobaczmy jak wygląda konfiguracja sieci na routerze:
root@aramin:~# ip netns exec R ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
12: ppp0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 5e:10:11:d4:89:34 brd ff:ff:ff:ff:ff:ff
inet6 fe80::5c10:11ff:fed4:8934/64 scope link
valid_lft forever preferred_lft forever
14: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether f6:6b:b2:97:53:12 brd ff:ff:ff:ff:ff:ff
inet6 fe80::f46b:b2ff:fe97:5312/64 scope link
valid_lft forever preferred_lft forever
Zostaje jeszcze dodanie adresacji, routingu, maskarady i sprawdźmy czy “ping” przechodzi:
root@aramin:~# # konfiguracja routera
root@aramin:~# ip netns exec R ip a a 172.20.1.2/24 dev ppp0
root@aramin:~# ip netns exec R ip r a default via 172.20.1.1
root@aramin:~# ip netns exec R ip a a 192.168.1.1/24 dev eth0
root@aramin:~# ip netns exec R iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
root@aramin:~#
root@aramin:~# # konfiguracja A
root@aramin:~# ip netns exec A ip a a 172.20.1.1/24 dev eth0
root@aramin:~#
root@aramin:~# # konfiguracja B
root@aramin:~# ip netns exec B ip a a 192.168.1.10/24 dev eth0
root@aramin:~# ip netns exec B ip r a default via 192.168.1.1
root@aramin:~#
root@aramin:~# ip netns exec B traceroute -n 172.20.1.1
traceroute to 172.20.1.1 (172.20.1.1), 30 hops max, 60 byte packets
1 192.168.1.1 0.082 ms 0.028 ms 0.026 ms
2 172.20.1.1 0.070 ms 0.028 ms 0.033 ms
Tada – traceroute przechodzi.
To spróbujmy czegoś trudniejszego.
Wrzućmy podany na początku zestaw reguł na R:
root@aramin:~# (
> cat <<EOF
> # Generated by iptables-save v1.4.21 on Sat Jan 7 15:00:51 2017
> *filter
> :INPUT ACCEPT [0:0]
> :FORWARD ACCEPT [0:0]
> :OUTPUT ACCEPT [0:0]
> -A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 1000:2000 -j REJECT --reject-with icmp-port-unreachable
> -A FORWARD -s 192.168.1.0/32 -p tcp -m tcp --dport 1234 -j ACCEPT
> -A FORWARD -s 192.168.1.0/24 -j ACCEPT
> COMMIT
> # Completed on Sat Jan 7 15:00:51 2017
> # Generated by iptables-save v1.4.21 on Sat Jan 7 15:00:51 2017
> *nat
> :PREROUTING ACCEPT [0:0]
> :INPUT ACCEPT [0:0]
> :OUTPUT ACCEPT [0:0]
> :POSTROUTING ACCEPT [0:0]
> -A POSTROUTING -s 192.168.1.100/32 -j MASQUERADE
> -A POSTROUTING -s 192.168.1.0/28 -j ACCEPT
> -A POSTROUTING -s 192.168.1.10/32 -j MASQUERADE
> COMMIT
> # Completed on Sat Jan 7 15:00:51 2017
> EOF
> ) | ip netns exec R iptables-restore
root@aramin:~#
I sprawdźmy liczniki:
root@aramin:~# ip netns exec R iptables -vxnL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 REJECT tcp -- * * 192.168.0.0/16 0.0.0.0/0 tcp dpts:1000:2000 reject-with icmp-port-unreachable
0 0 ACCEPT tcp -- * * 192.168.1.0 0.0.0.0/0 tcp dpt:1234
0 0 ACCEPT all -- * * 192.168.1.0/24 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
root@aramin:~# ip netns exec R iptables -vxnL -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * * 192.168.1.100 0.0.0.0/0
0 0 ACCEPT all -- * * 192.168.1.0/28 0.0.0.0/0
0 0 MASQUERADE all -- * * 192.168.1.10 0.0.0.0/0
Wszystkie chwilowo wynoszą 0. To teraz uruchomimy prosty serwer na środowisku A na porcie 1234 którego jedynym zadaniem po skutecznym nawiązaniu połączenia TCP będzie wypisanie tekstu HELLO
root@aramin:~# ip netns exec A nc -l -p 1234 -c "echo HELLO" &
[1] 1096
I spróbujmy się do niego podłączyć z środowiska B
root@aramin:~# ip netns exec B nc -n -v -w1 172.20.1.1 1234
(UNKNOWN) [172.20.1.1] 1234 (?) : Connection refused
Jak widać połączenie nieudane – zostało odrzucone. Spójrzmy więc które reguły iptables brały udział w połączeniu:
root@aramin:~# ip netns exec R iptables -vxnL -t nat
Chain PREROUTING (policy ACCEPT 2 packets, 120 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all — * * 192.168.1.100 0.0.0.0/0
0 0 ACCEPT all — * * 192.168.1.0/28 0.0.0.0/0
0 0 MASQUERADE all — * * 192.168.1.10 0.0.0.0/0
root@aramin:~# ip netns exec R iptables -vxnL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
2 120 REJECT tcp — * * 192.168.0.0/16 0.0.0.0/0 tcp dpts:1000:2000 reject-with icmp-port-unreachable
0 0 ACCEPT tcp — * * 192.168.1.0 0.0.0.0/0 tcp dpt:1234
0 0 ACCEPT all — * * 192.168.1.0/24 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 2 packets, 176 bytes)
pkts bytes target prot opt in out source destination
Jak widać – do tablicy nat nic nie doszło, natomiast 2 pakiety zostały odrzucone przez regułę REJECT. Mamy więc pierwszy błąd w firewallu. Usuńmy regułę i ponawiamy próbę:
root@aramin:~# ip netns exec R iptables -D FORWARD -s 192.168.0.0/16 -p tcp --dport 1000:2000 -j REJECT
root@aramin:~# ip netns exec B nc -n -v -w1 172.20.1.1 1234
(UNKNOWN) [172.20.1.1] 1234 (?) : Connection timed out
root@aramin:~# ip netns exec R iptables -vxnL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp — * * 192.168.1.0 0.0.0.0/0 tcp dpt:1234
2 120 ACCEPT all — * * 192.168.1.0/24 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
root@aramin:~# ip netns exec R iptables -vxnL -t nat
Chain PREROUTING (policy ACCEPT 3 packets, 180 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all — * * 192.168.1.100 0.0.0.0/0
1 60 ACCEPT all — * * 192.168.1.0/28 0.0.0.0/0
0 0 MASQUERADE all — * * 192.168.1.10 0.0.0.0/0
Tym razem widać że pakiety przeszły przez reguły w FORWARD, natomiast w regułach z tablicy nat – podlegały pod regułę ACCEPT, która wykonała się jako pierwsza i co prawda spowodowała przejście pakietu do komputera A, natomiast nie “znatowała” go – przez co nie dotarł do komputera B ruch powrotny – nie udało się więc nawiązać skutecznego połączenia TCP. Usuńmy więc tą regułę i zobaczmy czy działa:
root@aramin:~# ip netns exec R iptables -t nat -D POSTROUTING -s 192.168.1.0/28 -j ACCEPT
root@aramin:~# ip netns exec B nc -n -v -w1 172.20.1.1 1234
(UNKNOWN) [172.20.1.1] 1234 (?) open
HELLO
[1]+ Zakończono ip netns exec A nc -l -p 1234 -c "echo HELLO"
Działa!
Upewnijmy się jeszcze że ruch przechodzi przez właściwe reguły w tablicy nat:
root@aramin:~# ip netns exec R iptables -vxnL -t nat
Chain PREROUTING (policy ACCEPT 1 packets, 60 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all — * * 192.168.1.100 0.0.0.0/0
1 60 MASQUERADE all — * * 192.168.1.10 0.0.0.0/0
I na koniec usuńmy netnsy:
root@aramin:~# ip netns del R
root@aramin:~# ip netns del A
root@aramin:~# ip netns del B
Dzięki netns możemy emulować praktycznie każdy aspekt sieci w linuksie – włączając w to bonding, brydże, można również uruchomić na nich dynamiczny routing w oparciu np o quaggę lub bird, co pozwala na symulowanie bardziej zaawansowanych konfiguracji sieciowych. Warto oczywiście pamiętać że system plików i przestrzeń procesów jest tutaj wspólna z normalnym systemem – więc uruchamianie procesów oznacza że mogą one konfliktować z normalnie uruchomionymi na systemie procesami. Jeżeli jest to niepożądane – warto użyć oprócz netns również np chroot-a – tworząc w ten sposób namiastkę wirtualizacji w stylu lxc, tudzież chociaż wskazać ręcznie osobne katalogi do np plików pid.
Wszystkie skrypty – umieściłem dodatkowo na https://github.com/theundefined/netnsplay