Skip to main content

Absicherung eines DNS Servers mit nftables

Einleitung

Ziel war es, den in Konfiguration eines Secondary DNS mit Plex erstellten DNS Server über nftables sauber abzusichern, so dass Dienste nur von entsprechenden Quellen erreicht werden können.

Firewall konfigurieren

Erforderliche Ports:

Dienst

Quelle

Freigabe

DNS

Internet

UDP/53

DNS

Internet

TCP/53

SSH

definierte Admin-IP-Adressen

TCP/22

RNDC

ausschließlich Plesk-Primary

TCP/953

alles andere eingehend

beliebig

blockiert

ausgehender Verkehr

Server

erlaubt

DNS muss öffentlich sowohl über UDP als auch über TCP erreichbar sein. RFC 9210 verlangt für DNS-Server die Unterstützung beider Transportprotokolle. TCP/53 darf daher nicht ausschließlich auf den Primary eingeschränkt werden.

Platzhalter festlegen

Beispielwerte:

Plesk-Primary:       203.0.113.10
Admin-IP 1:          198.51.100.25
Admin-Netz:          198.51.100.64/28
SSH-Port:            22
Secondary-DNS-IP:    192.0.2.20

Die Adressen 192.0.2.0/24, 198.51.100.0/24 und 203.0.113.0/24 sind Beispiel Adressen. Sie müssen zwingend ersetzt werden.

Beim Plesk-Primary muss die tatsächlich am Secondary sichtbare Quelladresse eingetragen werden. Befindet sich Plesk hinter NAT, ist das die öffentliche NAT-Adresse, nicht die interne Serveradresse.


nftables installieren

sudo apt update
sudo apt install -y nftables

Prüfen, ob bereits ein anderes Firewall-Frontend läuft:

systemctl is-active ufw
systemctl is-active firewalld
systemctl is-active nftables

Nicht gleichzeitig ufw, firewalld und eine manuell verwaltete nftables.conf verwenden. Das führt zu schwer nachvollziehbaren beziehungsweise überschriebenen Regeln.

Firewall-Konfiguration

Vorhandene Konfiguration sichern:

sudo cp -a /etc/nftables.conf "/etc/nftables.conf.backup-$(date +%F-%H%M%S)"

Datei bearbeiten:

sudo nano /etc/nftables.conf

Vollständiger Inhalt:

#!/usr/sbin/nft -f

#
# Dedizierter Secondary-DNS-Server
# Debian 13 / nftables
#
# WARNUNG:
# Alle Beispiel-IP-Adressen vor Aktivierung ersetzen
#

flush ruleset

define SSH_PORT = 22

table inet firewall {

    #
    # IPv4-Adressen und Netze, von denen SSH erlaubt wird
    #
    set ssh_admin_ipv4 {
        type ipv4_addr
        flags interval

        elements = {
            198.51.100.25,
            198.51.100.64/28
        }
    }
    #
    # IPv6-Adressen und Netze, von denen SSH erlaubt wird
    #    
        set ssh_admin_ipv6 {
        type ipv6_addr
        flags interval

        elements = {
            2001:db8:100::25,
            2001:db8:200::/64
        }
    } 
    #
    # Öffentliche Quelladresse des Plesk-Primary
    # Nur diese Adresse darf RNDC/TCP 953 verwenden
    #
    set plesk_primary_ipv4 {
        type ipv4_addr
        flags interval

        elements = {
            203.0.113.10
        }
    }

    chain input {
        type filter hook input priority filter;
        policy drop;

        #
        # Ungültige Pakete verwerfen.
        #
        ct state invalid counter drop

        #
        # Antworten und zugehörige Verbindungen zulassen
        # Dadurch bleibt auch die bestehende SSH-Sitzung beim
        # Aktivieren des Rulesets zunächst bestehen
        #
        ct state established,related counter accept

        #
        # Loopback-Kommunikation zulassen.
        #
        iifname "lo" counter accept

        #
        # Gefälschte Loopback-Quelladressen auf externen Interfaces
        # verwerfen.
        #
        iifname != "lo" ip saddr 127.0.0.0/8 counter drop
        iifname != "lo" ip6 saddr ::1 counter drop

        #
        # ICMP für Fehlerbehandlung
        #
        ip protocol icmp counter accept

        #
        # ICMPv6 ist unter IPv6 unter anderem für Neighbor Discovery
        # und Path-MTU-Discovery erforderlich
        #
        meta l4proto ipv6-icmp counter accept

        #
        # Öffentlicher autoritativer DNS-Dienst
        # Gilt durch die inet-Tabelle für IPv4 und IPv6
        #
        udp dport 53 counter accept
        tcp dport 53 counter accept

        #
        # SSH ausschließlich von definierten IPv4-Quellen
        #
        ip saddr @ssh_admin_ipv4 \
            tcp dport $SSH_PORT \
            ct state new \
            counter accept
        ip6 saddr @ssh_admin_ipv6 \
            tcp dport $SSH_PORT \
            ct state new \
            counter accept

        #
        # BIND-RNDC ausschließlich vom Plesk-Primary
        #
        ip saddr @plesk_primary_ipv4 \
            tcp dport 953 \
            ct state new \
            counter accept

        #
        # Rate Limit auf Management-Ports 
        # protokollieren und anschließend verwerfen
        #
        tcp dport { $SSH_PORT, 953 } \
            ct state new \
            limit rate 6/minute burst 10 packets \
            log prefix "nft-denied-mgmt: " level info \
            counter drop
        #
        # OPTIONAL: Sollte der Server seine IP per DHCP bekommen, muss DHCP
        #           in der Firewall freigegeben werden.
        #
        udp sport 67 udp dport 68 counter accept
        udp sport 547 udp dport 546 counter accept
        #
        # Alles Weitere wird durch policy drop verworfen
        #
    }

    chain forward {
        type filter hook forward priority filter;
        policy drop;
    }

    chain output {
        type filter hook output priority filter;
        policy accept;
    }
}

Konfiguration prüfen

nft -c prüft die Gültigkeit, ohne die Regeln anzuwenden.

sudo nft -c -f /etc/nftables.conf

Bei erfolgreicher Prüfung gibt der Befehl normalerweise nichts aus.

Zusätzlich kann noch ein Check ausgeführt werden:

sudo nft --check --file /etc/nftables.conf

Sichere Aktivierung bei Remote-Zugriff

Vor dem Anwenden eine automatische Rücknahme planen:

sudo systemd-run --unit=nft-rollback --on-active=5m /usr/sbin/nft flush ruleset

Nimmt nach 5min automatisch mit einem flush ruleset das Regelwerk zurück.

Danach Regeln anwenden:

sudo nft -f /etc/nftables.conf

Aktives Ruleset anzeigen:

sudo nft list ruleset

Nun in einem zweiten Terminalfenster eine neue SSH-Verbindung aufbauen. 

Wenn die neue Verbindung funktioniert, Rollback stoppen:

sudo systemctl stop nft-rollback.timer
sudo systemctl reset-failed nft-rollback.service 2>/dev/null || true

Firewall dauerhaft aktivieren:

sudo systemctl enable nftables --now

Status prüfen:

sudo systemctl status nftables

Da die Regeln bereits manuell geladen wurden, ist an dieser Stelle kein Neustart notwendig.

Neustartfestigkeit prüfen

sudo systemctl restart nftables
sudo nft list ruleset

Danach erneut eine neue SSH-Sitzung öffnen.

Abschließend sollte ein kontrollierter Neustart getestet werden:

sudo reboot

Nach dem Neustart:

systemctl status nftables
sudo nft list ruleset

Ports und Listener kontrollieren

sudo ss -lntup

Erwartet werden insbesondere:

UDP 0.0.0.0:53
TCP 0.0.0.0:53
UDP [::]:53
TCP [::]:53
TCP <Secondary-IP>:953
TCP 0.0.0.0:22
TCP [::]:22

Dass ss SSH auf allen Adressen anzeigt, ist nicht automatisch ein Problem. Die Erreichbarkeit wird durch nftables eingeschränkt. RNDC sollte idealerweise nicht auf 0.0.0.0:953 lauschen, sondern nur auf der Secondary-IP und gegebenenfalls Loopback.

Funktionstests

DNS über UDP

Von einem externen System:

dig @192.0.2.20 example.org SOA +norecurse

DNS über TCP

dig @192.0.2.20 example.org SOA +tcp +norecurse

Beide Abfragen müssen eine autoritative Antwort liefern. Das flags-Feld sollte aa enthalten.

SSH von erlaubter Adresse

nc -vz 192.0.2.20 22

Oder direkt:

ssh admin@192.0.2.20

SSH von nicht erlaubter Adresse

Von einem anderen Internetanschluss:

nc -vz -w 5 192.0.2.20 22

Der Zugriff sollte in einen Timeout laufen.

RNDC vom Plesk-Primary

Auf dem Plesk-Server:

nc -vz 192.0.2.20 953

Der Port muss erreichbar sein.

Firewall-Logs prüfen

Abgewiesene SSH- und RNDC-Verbindungen werden mit dem Präfix nft-denied-mgmt protokolliert:

sudo journalctl -k -g 'nft-denied-mgmt'

Live-Ansicht:

sudo journalctl -k -f

Oder:

sudo dmesg --follow

Die Protokollierung ist bewusst limitiert, damit Portscanner nicht unkontrolliert das Journal füllen.