Debian Router Part2: Firewall, Portfreigaben und Erreichbarkeit von außen
Dies ist die Fortsetzung des Artikels über die Grundeinrichtung eines Debian PPPoE Routers.
Über die reine IPv4/6-Konnektivität hinaus benötigt unser Router weitere Funktionen - von essentiellen Dingen bis Annehmlichkeiten - damit wir die FritzBox endgültig guten Gewissens beim Recyclinghof abgeben können.
Firewall
Unser Router hängt via PPPoE direkt am Internet und lässt im Standard alle ein- und ausgehenden Verbindungen zu (INPUT+OUTPUT). Da alle Clients dank IPv6 ebenfalls direkt eine öffentliche Adresse haben, kommen eingehende Verbindungen auch direkt dort an. Aus Router-Paketsicht ist das ein FORWARD. Bei IPv4 sind eingehende Verbindungen wegen dem CarrierGradeNAT nicht möglich.
Mit IPv6 ist eine Firewall für eingehende Verbindungen also ein MUSS.
Uncomplicated Firewall
Wir nehmen die UFW und verbieten im Standard eingehende und Forwarded Verbindungen:
apt install ufw
vi /etc/default/ufw.con
IPV6=yes
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"
DEFAULT_FORWARD_POLICY="DROP"
Da wir Forwarding grundsätzlich verbieten, müssen wir von innen kommende Pakete zulassen - sonst können die Clients nicht raus ins Internet:
ufw route allow in on eth0 out on ppp0
ufw route allow in on eth0 out on ip6tnl
Wir erlauben DHCP, DNS und SSH auf dem Router von innen (eth0):
ufw allow 67/udp
ufw allow in on eth0 to any port 53
ufw allow in on eth0 to any port 22
Portfreigaben
Wir möchten einen Dienst in unserem Netzwerk von außen erreichbar machen. Nehmen wir an, wir haben einen Jump-Host mit SSH-Server im Netzwerk und möchten von überall aus uns damit verbinden.
Dazu benötigen wir eine Portfreigabe. Das geht nur über IPv6, da unsere IPv4-Verbindung nicht öffentlich ist. Bei IPv6 genügt es in der Firewall die Verbindung zu erlauben. Wir brauchen nicht wie bei IPv4 ein Port-Forwarding, da das Ziel ja direkt eine öffentliche IPv6 hat.
ufw route allow in on ppp0 to <IPv6 des Ziel-Hosts> port 2222
Es empfiehlt sich, dem Ziel-Host (hier: unser Jump-Host) eine stabile IPv6 (ausgenommen wechselnder Prefix) zu geben. Das geht über Systemd-Networkd so:
vi /etc/systemd/network/eth0.network
[IPv6AcceptRA]
Token=::1
Somit ist unser SSH-Server ab sofort über die IPv6 <prefix>::1 auf Port 2222 erreichbar! Vorausgesetzt, dort läuft der SSH-Server auf Port 2222.
Es gibt aber zwei Probleme: Mit der deutschen Giganetz ändert sich die Adresse alle 24 Stunden und ein Zugriff mit IPv4-only Internet ist nicht möglich.
Für beide Probleme gibt es verschiedene Lösungen. Ich habe mich dazu entschieden, meinen vServer zu nutzen. Dieser hat eine stabile IPv4+6 und wir können die TCP-Pakete dynamisch an unseren Router tunneln. Das geht auch von IPv4 zu IPv6.
IPv6-only Portreigabe mit dynamischem IPv6 Präfix
Als erstes müssen wir erkennen, wann wir ein neues IPv6 Subnetz bekommen. PPPoE loggt dies in die Syslog-Datei. Mit rsyslog
können wir die Logdatei auf das Ereignis monitoren und ein Script ausführen, wann immer wir einen neuen Prefix erhalten haben.
So sieht das in der /var/log/syslog
aus:
2024-09-29T04:30:04.409945+02:00 rtr systemd-networkd[120]: ppp0: DHCP: received delegated prefix 2a01:41e3:26d7:5800::/56
vi /etc/rsyslog.conf
ans Ende einfügen:
:msg, contains, "DHCP: received delegated prefix" ^/root/ip-change.sh
Rsyslog neustarten:
systemctl restart rsyslog
touch /root/ip-change.sh
chmod 755 /root/ip-change.sh
Bei neuem Präfix wird ip-change.sh
ausgeführt und als Argument die Logzeile übergeben. Das neue Subnetz extrahieren wir daraus und passen unsere UFW-Regel für die Portfreigabe auf das neue Subnetz an. Dazu löschen wir die alte Regel und fügen die neue hinzu. Aus dem Subnetz ersetzen wir die "/56" durch die einzel IPv6-des Zielhosts. In meinem Fall hat das Ziel die ::1, weshalb "/56" mit dem sed-Befehl durch "1" ersetzt wird. Hat der Zielhost die IPv6 "<prefix>::a:b:123", muss das "/56" durch "a:b:123" ersetzt werden (in Zeile 1):
ipv6=$(echo $@ | awk '{print $NF}' | sed "s/\/56/1/")
echo "IP Address has changed to: $ipv6" >> /tmp/iplog.txt
old=$(ufw status | grep -E '22\s+ALLOW FWD' | awk '{print $1}')
ufw route delete allow in on ppp0 to $old port 2222
ufw route allow in on ppp0 to $ipv6 port 2222
Jetzt müssen wir noch einen Tunnel von unserem vServer zum Router aufbauen und diesen bei jedem Präfix-Change aktualisieren.
Wir nutzen dazu das Programm "rinetd", um eine einfache TCP-Weiterleitung einzurichten:
ssh me@meinvserver.de
apt install rinetd
vi /etc/rinetd.conf
Wir richten je für IPv4 und IPv6 ein TCP-Tunnel ein. "1234" ist der öffentliche Port des vServers (muss frei sein). "ROUTER" ist ein DNS-Name, der per Hosts-Datei zur jeweils aktuellen IPv6 unseres Router aufgelöst wird.
<ipv4 des vServers> 1234 ROUTER 2222
<ipv6 des vServers> 1234 ROUTER 2222
In die /etc/hosts
schreiben wir ein Mapping, damit "ROUTER" entsprechend aufgelöst wird
<ipv6 des Routers> ROUTER
Wenn sich die IPv6 des Routers ändert, pushen wir die neue IPv6 via ssh in die Hosts-Datei des vServers. Das ist unser poor-mans-dyndns.
Der ip-change.sh fügen wir folgende Zeile hinzu:
ssh me@meinvserver.de "sed -i '$ d' /etc/hosts && echo '$ipv6 ROUTER' >> /etc/hosts && /etc/init.d/rinetd restart"
Der Befehl löscht die letzte Zeile der Hosts-Datei und fügt das neue Mapping hinzu. Danach muss rinetd neu gestartet werden.
Mit dieser Konfiguration kann immer über den vServer von außen eine SSH-Verbindung zu unserem Jump-Host im lokalen Netzwerk aufgebaut werden:
ssh daheim@meinvserver.de -p 1234
So sieht eine Verbindung aus. Dabei ist es egal, ob SSH via IPv4 oder v6 genutzt wird. rinetd
auf dem vServer aktzeptiert beide Protokolle, übersetzt aber v4 Verbindungen nach v6 und erreicht somit unseren Jump-Host.
SSH v4/6:1234 -> v4/6:1234 vServer v6:2222 -> v6:2222 Jump-Host