neunzehn83.de

Ein Mann, ein Blog, kein Plan.

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

Debian Router statt FritzBox mit Glasfaser von der Deutschen Giganetz

Ich habe nun einen Glasfaser-Anschluss von der Deutschen Giganetz (DGN). Yay! Ein Debian Linux soll das Routing übernehmen, wie zuvor auch schon für meinen DSL-Anschluss.

Mein Router ist ein LXC-Container auf einem Raspberry Pi4 mit Debian bookworm Linux. Wir wollen routen ganz ohne Klimbim, ausschließlich mit Systemd-Networkd und ein paar Scripts! Wir brauchen kein radvd oder wide-dhcp.

Der Pi hat einen separaten Netzwerk-Dongle zur PPPoE-Einwahl - dieser ist wahrscheinlich nicht unbedingt erforderlich, da PPPoE über ein VLAN stattfindet.

eth0: Internes Netzwerk
eth1: Patchkabel zum ONT (Glasfaser-Modem)

Von der DGN gibt es einen DS-Lite Anschluss mit 24h Zwangstrennung (!). Das IPv6 Subnetz ändert sich alle 24 Stunden. IPv4 gibt es nur über einen Tunnel zu einem AFTR mit Carrier-Grade-Nat.

Mit der PPPoE-Einwahl bekommen wir eine IPv6 und ein /56 IPv6 Subnetz. Ein /64 davon geben wir per RA/Router Advertisement in unser Netzwerk zur selbständigen Konfiguration der Clients (SLAAC).

Part 1: Netzwerk-Config für PPPoE

Wir benötigen ein VLAN mit der ID=7 für die PPPoe Einwahl:

root@rtr:~# vi /etc/systemd/network/00-vlan.netdev

[NetDev]
Name=myvlan
Kind=vlan

[VLAN]
Id=7

Dem Interface mit der Verbindung zum ONT geben wir dieses vlan (hier eth1).

root@rtr:~# vi /etc/systemd/network/10-eth1.netdev

[Match]
Name=eth1
Type=ether

[Network]
VLAN=myvlan
DHCP=no

Dem PPP-Interface sagen wir, dass es ein RA akzeptieren soll (das ist unser /56-Netz für Clients). DHCP (Client) wird benötigt um selbst via PPP eine einzelne IPv6 zu erhalten.

root@rtr:~# vi /etc/systemd/network/11-ppp0.network

[Match]
Name=ppp0
Type=ppp

[Network]
DHCP=ipv6
IPv6AcceptRA=yes
IPv6PrivacyExtensions=yes
KeepConfiguration=yes

[DHCPv6]
UseDelegatedPrefix=true
WithoutRA=solicit

Jetzt Netzwerk neustarten

systemctl restart systemd-networkd

Part 2: PPPoE Einwahl

Wir installieren die Tools ppp und pppoe und legen eine Config an. Außerdem packen wir die Zugangsdaten in eine Datei.

apt install ppp pppoe

root@rtr:~# vi /etc/ppp/peers/DGN

noipdefault
defaultroute
replacedefaultroute
hide-password
lcp-echo-interval 20
lcp-echo-failure 3
connect /bin/true
noauth
persist
noaccomp
default-asyncmap
plugin rp-pppoe.so
nic-myvlan					# Anpassen auf VLAN-Name
user "XXX@dgn.digital"		# Anpassen auf Username
nodetach
persist

root@rtr:~# vi /etc/ppp/chap-secrets

"XXX@dgn.digital" * "XXX"	# Anpassen Username und Passwort

PPPoE-Dienst anpassen vi /etc/systemd/system/pppoe.service

ExecStart=/usr/sbin/pppd call DGN

Hier den Config-Namen mit der PPPoE-Config (in /etc/ppp/peers) angeben. Hier: DGN

PPPoE-Einwahl!

systemctl restart pppoe

Mit systemctl status pppoe prüfen, ob die Verbindung geklappt hat. IPv6-Zugriff sollte nun funktionieren: ping6 heise.de.
Bei mir musste ich noch eine Route hinzufügen um IPv6-Konnektivität zu erhalten: ip -6 route add 0::/0 dev ppp0

Part 3: IPv4

Wir benötigen einen 4in6 Tunnel zum AFTR von DGN (bzw. Purtel, der tech. Provider von DGN)

ip -6 tunnel add ip6tnl mode ipip6 local <lokale IPv6> remote 2a01:41e3:ffff:cafe:face::3 dev eth1
ip addr add 192.0.0.2/29 peer 192.0.0.1 dev ip6tnl
ifconfig ip6tnl up
ip route add default via 192.0.0.1 dev ip6tnl

Damit sollte auf dem Router IPv4 funktionieren: ping 8.8.8.8

Part 4: Konfiguration für die Clients

Wir konfigurieren das interne Interface (hier: eth0) wie folgt:

root@rtr:# vi /etc/systemd/network/eth0.network

[Match]
Name=eth0
[Network]
Address=10.11.12.254/24

IPv6AcceptRA=yes
IPv6SendRA=yes
DHCPPrefixDelegation=yes
DHCPv6PrefixDelegation=yes

DHCPServer=true

[DHCPServer]
PoolOffset=100
PoolSize=100
EmitDNS=yes
DNS=10.11.12.254

[IPv6SendRA]
EmitDNS=true
DNS=fe80::1

Bei [Network] vergeben wir eine eigene IPv4 für den Router - das ist die Gateway-Adresse für alle v4-Verbindungen. Bei [DHCPServer] konfigurieren wir den v4 DNS: jeder Client bekommt eine IPv4 aus dem Pool 10.11.12.100-199.
Interessant ist hier, dass wir kein NAT in unserem Netzwerk brauchen! Die Pakete mit den privaten IPs gehen in den Tunnel und werden erst beimAFTR umgeschrieben.
Bei [IPv6SendRA] verteilen wir auch einen DNS via IPv6. Der Router selbst ist DNS-Server. Hier könnte auch ein öffentlicher IPv6-DNS-Server stehen.

Wenn der Router selbst der DNS-Server sein soll (um bspw. Adblocking-Aufgaben zu übernehmen) muss zusätzlich bspw. dnsmasq installiert und konfiguriert werden.

Damit Clients via ICMP die MTU der Pakete anpassen können müssen wir die Kommunikation zulassen:

iptables -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Part 5: Automatisierung

Da die PPPoE-Verbindung nach 24h getrennt wird, muss bspw. die Default-Route und der IPv4-Tunnel nach jedem Verbinden erneut aufgebaut werden. Dazu kann man die Befehle in ein "IPv6-up" Script packen, welches PPPoE automatisch ausführt:

root@rtr:# vi /etc/ppp/ipv6-up.d/rtr

#!/bin/sh
sleep 1
ip -6 route add 0::/0 dev ppp0
sleep 1
ipv6=$(ip -6 addr show ppp0 | grep inet6 | awk -F '[ \t]+|/' '{print $3}' | grep -v '^\(::1\|fe80\)' | head -n1)
ip -6 tunnel add ip6tnl mode ipip6 local $ipv6 remote 2a01:41e3:ffff:cafe:face::3 dev eth1
ip addr add 192.0.0.2/29 peer 192.0.0.1 dev ip6tnl
ifconfig ip6tnl up
ip route add default via 192.0.0.1 dev ip6tnl

iptables -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Das Script holt automatisch die IPv6 und nutzt diese als Tunnel-Eingang. Tunnel-Ausgang ist der AFTR von Purtel (aftr.fra.purtel.com). Das Script muss executable sein: chmod +x /etc/ppp/ipv6-up.d/rtr

Automatischer Reconnect

Damit die Zwangstrennung nicht in ungünstige Zeiten fällt, kommen wir ihr zuvor, indem wir täglich ppp neustarten und damit eine Neueinwahl auslösen. Zudem gibt es ein neues IPv6 Subnetz.

crontab -e

30 4 * * * systemctl restart pppoe

Part 6: ToDos

Das alles ist erst der Anfang! Wir benötigen zwangläufig noch eine Firewall (ufw) und eventuell die ein oder andere IPv6-Portfreigabe. Diese Portfreigabe soll dann auch trotz ständig wechselndem IPv6-Präfix von außen erreichbar sein. Auch wollen wir unterwegs, wenn nur IPv4 verfügbar ist, uns nach Hause verbinden! Ideen dazu folgen im nächsten Blog-Beitrag ..

OpenVPN in einem LXC-Container auf einem Raspberry Pi 4 mit Ubuntu

Der LXC-Container braucht ein "tun" device. Dazu gibt es viele Artikel im Internet. Keiner davon hat so richtig funktioniert, deshalb hier die Notiz an mich selbst.

Es ist eigentlich ganz einfach. Die Container-Config (/var/lib/lxc/<container-name>/config) benötigt folgende zwei Einträge:

lxc.cgroup2.devices.allow = c 10:200 rwm
lxc.mount.entry = /dev/net/tun dev/net/tun none bind,create=file

Ganz wichtig ist natürlich "cgroup2" (beachte: die Zwei) zu verwenden - ist ja klar!

Das ganze bezieht sich auf einen Privileged Container.

Debian Linux Fileserver für Windows und Mac Clients

Ich mag keine NAS-Boxen. Zu eingeschränkt, zu langsam, zu unflexibel. Natürlich besteht für mich der perfekte Fileserver aus einem Debian-System mit ein paar wenigen Standardpaketen. In diesem Fall Debian 10 Buster.

Idee: Für den Zugriff von Windows benötigen wir Samba, für Linux und Mac Clients genügt ein entsprechender SSH-Zugang für SSHFS.

Doch beim Zugriff mit SSHFS ergibt sich ein Problem..

Die Rechtesituation

Eigentlich einfach. Wir möchten verschiedene Linux-Systembenutzer. Gleichzeitig sind dies Samba-Nutzer. Über die Gruppenzugehörigkeit können Zugriffsrechte auf Verzeichnisebene für SSH vergeben werden.

Damit neu erzeugte Dateien der Gruppe gehören und nicht dem User selbst, müssen wir das SETGID Bit beim Hauptverzeichnis setzen.

chmod g+s /storage

Problem: neue Dateien haben je nach Umask des Clients(!) nur Leserechte für die Gruppe. Noch schlimmer: wird eine Datei kopiert, die auf dem Client nur Schreib-/Leserechte für den User und keine Rechte für die Gruppe hat, wird diese so auf den Fileserver kopiert - ganz egal welche Umask gilt. Das ist ein Problem, da andere User diese Datei dann nicht bearbeiten oder vielleicht sogar nicht einmal lesen können.

SSHFS ist auf Serverseite ein SFTP-Server. Genauer gesagt der sftp-server von OpenSSH. Dieser hat leider keine Möglichkeit wie bspw. bei Samba mit inherit permissions die Dateirechte beim Schreiben explizit zu setzen.

OpenSSH sftp-server patchen

Ich habe lange hierzu eine Lösung gesucht - mir erschien der Use-Case eigentlich straight-forward - scheinbar gibt es aber keine einfache Lösung. Auf folgenden Patch für den sftp-server von OpenSSH bin ich gestoßen:

https://bugzilla.mindrot.org/show_bug.cgi?id=1844

Dieser ermöglicht mit dem dann zur Verfügung stehenden "-m" Parameter eine Umask zu forcen. Leider hat es dieser Patch auch nach 10 Jahren noch nicht ins offizielle sftp-server-Paket von Debian geschafft. Außerdem genügt das forcen einer Umask nicht, wenn die Datei auf dem Client grundsätzlich zu wenig Rechte hat. Denn eine Umask erhöht niemals die Rechte.

Ade Standardpakete, selbst ist der Mann - wir erweitern den Patch um das wirkliche Forcieren von Permissions bei allen Schreibvorgängen.

Wir benötigen zunächst grundsätzliche Zugriff auf die Sourcen von Debian-Paketen:

vi /etc/apt/sources.list
deb-src http://ftp.debian.org/debian buster main contrib

apt update

Source holen

apt source openssh-sftp-server
cd openssh-7.9p1/

Patch anwenden

Ich habe den originalen Patch erweitert, so dass Permissions von Dateien nicht übernommen sondern immer die Permissions wie im "-m" Parameter angegeben gesetzt werden.

Der Unterschied zum Originalpatch:

--- sftp-server.c.2     2020-07-13 17:17:58.152172604 +0000
+++ sftp-server.c       2020-07-13 17:18:39.136288633 +0000
@@ -919,7 +919,7 @@
                if (r == -1)
                        status = errno_to_portable(errno);
        }
-       if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
+       if (permforce == 0 && a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
                logit("set \"%s\" mode %04o", name, a.perm);
                r = chmod(name, a.perm & 07777);
                if (r == -1)
@@ -972,7 +972,7 @@
                        if (r == -1)
                                status = errno_to_portable(errno);
                }
-               if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
+               if (permforce == 0 && a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
                        logit("set \"%s\" mode %04o", name, a.perm);
 #ifdef HAVE_FCHMOD
                        r = fchmod(fd, a.perm & 07777);    

Entweder der Reihe nach den Originalpatch anwenden, dann die zwei Änderungen oben oder hier auch als Komplett-Patch herunterladen.

wget https://neunzehn83.de/blog/files/2020/openssh-sftp-server-patch.diff
patch sftp-server.c openssh-sftp-server-patch.diff

Bauen & installieren

apt build-dep devscripts openssh-sftp-server
cd debian
debuild -b -uc -us
cd ../../
dpkg -i openssh-sftp-server_7.9p1-10+deb10u2_amd64.deb

Damit haben wir den openssh-ftp-server mit "-m" Parameter gebaut!

Konfiguration

Damit bei SSHFS-Verbindungen der neue Parameter auch genutzt wird, muss die openssh-Konfiguration entsprechend angepasst werden:

vi /etc/ssh/sshd_config
Subsystem sftp /usr/lib/openssh/sftp-server -m 770
/etc/init.d/sshd restart

Testen

Wir kopieren eine Datei mit den lokalen Rechten "700" auf ein SSHFS-Mount (hier: /mnt/files/test). Nach dem Kopieren hat die Datei die Rechte "770".

Mailjet und die Deliverability zu @t-online.de

Als kleiner Mailserver-Betreiber tut man sich bekanntlich schwer, eine gute Zustellbarkeit zu erreichen. Für wirklich wichtige E-Mails, bspw. transaktionale E-Mails eines Online-Shops wie Bestellbestätigungen usw., sollte man daher auf einen externen SMTP-Service zurückgreifen.

Da gibt es einige: Mailgun, Amazon SES, etc. Wer dann noch darauf achten muss oder will, dass die über den SMTP-Service versendeten Daten nicht in irgendwelchen US-Clouds landen hat es schon schwerer. Mailjet sieht hier nach einer ordentlichen Alternative aus.

Aus aktueller Erfahrung kann ich aber sagen, dass es dort selbst mit den bezahlten Paketen ohne exklusive IP immer wieder zu Problemen mit der Zustellbarkeit kommt. Insbesondere mit @t-online.de Adressen aber auch des öfteren zu @yahoo.de und ähnliche.

Ich wollte das zuerst nicht so recht glauben, dass so ein großer Anbieter solche Probleme mit der Zustellbarkeit zu einem der größten deutschen Mailprovider hat. Der Support wollte darauf aber nicht eingehen, hat das Problem aber bestätigt und als Alternative dazu nur ein Upgrade auf eine exklusive IP angeboten (Kosten: ab 50 Euro im Monat).

Da meiner Meinung nach eine exklusive IP nur mit entsprechend hohen Versandzahlen Sinn macht, scheidet für mich wie es scheint Mailjet als Mailversender aus.

Es ist verständlich, dass deren shared IP-ranges blacklist-anfällig sind, da darüber jeder free-user wilde E-Mails versenden darf. Warum bietet Mailjet dann aber keine premium shared IP-ranges an, welche nur bezahlte Accounts nutzen dürfen? Hier hätte Mailjet die bessere Kontrolle über blacklisting.

Ich bin also auf der Suche nach einer Alternative zu Mailjet, einem zuverlässigen SMTP-Service gehostet in Deutschland oder der EU. Selbstverständlich darf dieser Service auch etwas kosten. Das ist gar nicht so einfach, hier etwas zu finden. Vorschläge willkommen.

Archiv »