Die moderne Paketfilterung mit nftables bietet unter Linux enorme Performance-Vorteile, bringt jedoch eine konzeptionelle Hürde mit sich: Sie kann, soweit ich weiß, keine dynamischen DNS-Namen direkt in Firewall-Regeln auflösen. Wer ausgehenden Datenverkehr auf bestimmte externe Domains beschränken will, steht vor einem Problem.

Die Lösung für dieses Problem ist ein Named Set. Ein Script löst die Domain regelmäßig per DNS auf und aktualisiert dieses Set automatisch via systemd-Timer. Wir nehmen hier als Beispiel Gitlab.com. Dadurch geben wir nicht die gesamte Cloudflare-IP-Range frei, sondern ausschließlich die IP-Adressen, die gitlab.com liefert.

1. nftables-Set definieren

Zuerst legen wir das Set gitlab_ipv4 und die dazugehörige Filter-Regel in der bestehenden nftables-Konfiguration (z. B. /etc/nft/Foobar.ruleset) an. Das Flag interval ist wichtig, damit das Set IP-Bereiche und einzelne Adressen effizient verarbeiten kann.

table ip filter {
    set gitlab_ipv4 {
        type ipv4_addr
        flags interval
    }

    chain OUTPUT {
        ip daddr @gitlab_ipv4 tcp dport { http, https } ct state new counter accept
    }
}

2. Update-Skript erstellen

Das Skript holt sich die aktuellen IPs über getent und schreibt die Adressen direkt in das nftables-Set. Wir erstellen die Datei /usr/local/sbin/domainset.sh:

#!/bin/sh
set -e

DOMAIN="gitlab.com"
ips=(getent ahostsv4 "DOMAIN" | awk '{print \$1}' | sort -u)

# Abbrechen, falls keine IP gefunden wurde
[ -n "\$ips" ] || exit 1

# Set leeren und neu befüllen
nft flush set ip filter gitlab_ipv4
for ip in \$ips; do
    nft add element ip filter gitlab_ipv4 "{ \$ip }"
done

Damit das System den Code ausführen darf, machen wir das Skript ausführbar:

chmod 755 /usr/local/sbin/domainset.sh

3. Automatisierung mit systemd

Die zeitgesteuerte Ausführung wird nun über zwei modulare systemd-Komponenten realisiert.

Service

Die Service-Unit unter /etc/systemd/system/domainset.service steuert den eigentlichen Aufruf des Skripts. Sie dient als Bindeglied zwischen dem Timer und der Systemumgebung.

[Unit]
Description=Update external domain nftables set
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/domainset.sh

Abhängigkeiten über After= und Wants=: Da das Skript eine DNS-Abfrage im Internet durchführt, darf es erst starten, wenn die Netzwerkverbindung vollständig aufgebaut ist. network-online.target stellt sicher, dass der DNS-Resolver des Servers bereit ist.

Der [Service]-Block: Definiert die Art der Ausführung. Type=oneshot ist ideal für Skripte, die eine kurze Aufgabe erledigen und sich danach sofort wieder beenden. Erst wenn ExecStart= erfolgreich durchgelaufen ist, gilt die Unit für systemd als erledigt.

Timer

Der Timer unter /etc/systemd/system/domainset.timer übernimmt ausschließlich die Zeitplanung und stößt den oben definierten Service in festen Intervallen an.

[Unit]
Description=Update external domain nftables set every 15 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=15min
Unit=domainset.service

[Install]
WantedBy=timers.target

Der [Timer]-Block: Bestimmt das “Wann”. Im Gegensatz zu festen Uhrzeiten nutzen wir hier relative Intervalle. OnBootSec=1min startet das Skript das erste Mal genau eine Minute nach dem Systemstart. OnUnitActiveSec=15min sorgt dafür, dass der Service danach alle 15 Minuten erneut getriggert wird. Unit= verknüpft diesen Timer explizit mit unserem Update-Service.

Der [Install]-Block: Das ist der Autostart-Eintrag für den Timer. WantedBy=timers.target sorgt dafür, dass die Zeitschaltuhr beim Systemstart automatisch geladen und aktiviert wird. Ohne diesen Block würde der Timer nach einem Reboot inaktiv bleiben.

Timer aktivieren und testen

Damit systemd die neuen Konfigurationen einliest und den Timer startet, führen wir folgende Befehle aus:

systemctl daemon-reload
systemctl enable --now domainset.timer

Administration & Fehlersuche

Ein großer Vorteil gegenüber Cronjobs ist die einfache Überwachung mit Standard-Befehlen:

# Status des Timers und die nächste geplante Ausführung anzeigen
systemctl status domainset.timer
systemctl list-timers domainset.timer

# Den Update-Service manuell triggern (bequemer Test)
systemctl start domainset.service

# Inhalt des nftables-Sets live überprüfen
nft list set ip filter gitlab_ipv4

# Logs der Skriptausführung auslesen
journalctl -u domainset.service

Dateiübersicht

Nach der Einrichtung sind folgende Dateien auf unserem System aktiv:

  • /etc/nft/Foobar.ruleset (nftables-Konfiguration)
  • /usr/local/sbin/domainset.sh (Update-Script)
  • /etc/systemd/system/domainset.service (systemd-Dienst)
  • /etc/systemd/system/domainset.timer (systemd-Zeitsteuerung)

Wichtige Hinweise für die Praxis

⚠️ Reines IPv4-Szenario: Diese Konfiguration ist auf type ipv4_addr ausgelegt. Falls Verbindungen zu GitLab auch über IPv6 laufen sollen, muss ein analoges Set mit type ipv6_addr in einer ip6- oder inet-Tabelle angelegt werden. Das Skript muss in diesem Fall um ahostsv6 erweitert werden.