Ich bau ja wenn ich irgendwo einen Router oder Firewall brauche entweder mit Debian und ferm/iptables oder in wenigen Fällen auch mit OpenBSD/pf. Das hat sich über die Jahrzehnte bewährt.

Jetzt sagt ja das Netz “iptables ist tot”, was zum Glück nicht stimmt aber nichts desto trotz muss man sich die Alternativen anschauen. Nftables ist eigentlich schon ein brauchbarer “Nachfolger”, wenn man davon absieht, dass ein paar Dinge einfach fehlen. Ipset unter Anderem.

Neulich hatte ich die Anforderung, in VMware ein Netz für Migration bzw. Staging eines Check Point Management Servers zu bauen und den ein/ausgehenden Traffic mit einer Firewall zu reglementieren. Aus Gründen musste ich hier Redhat oder Centos und nftables nehmen.

Erst mal ein Ärgernis aber auf den zweiten Blick kein Problem. Linux ist Linux.

Ich schreibe das hier als hoffentlich einigermassen verständliches Tutorial, wie man mit Centos und Nftables mit wenigen Handgriffen eine Firewall bauen kann. Ein Gerät, das man eigentlich prinzipiell hinter den Router seines Internet-Providers bauen sollte, weil die (und damit auch “die Dienste”) nämlich sonst im Zweifelsfall Zugriff auf ALLES in Deinem Netz haben.

Leider braucht es dazu ein bisschen mehr Hardware. Die Firewall selber könnte auf einem Mini-PC laufen (z.B. Zotac ZBOX CI625 nano). DNS lässt man dann entweder Pihole oder Adguard Home machen, gibt’s beides für Linux und könnte auf der Firewall-Maschine mitlaufen. Und für das lokale Netz (LAN und Wireless) gibt’s günstige Accesspoints von Mikrotik.

Aber wir schweifen ab…

Vorbereitung

Auf dem VMware Server einen internen Vswitch (also ohne Uplink-Interface) anlegen. Dann eine VM für den Router erstellen. Der kriegt zwei vNICs: Einen an einem vSwitch mit Verbindung zu einem physikalischen Netz (in Diesem braucht man zwei IP-Adressen) und einen an dem eben angelegten “internen” vSwitch.

Skizze

Ansonsten braucht die Router-VM nicht viel: Zwei Kerne, 2GB Ram, 30 GB Platte, alle virtuelle Hardware auf Standard belassen. Dann das Installations-ISO in den Datastore laden und in die Maschine als DVD-Laufwerk einbinden.

Installation

Die Installation ist Standard: Einfach starten und den Installationsassistenten durchklicken. Keinen grafischen Desktop aktivieren! Nach der Installation neu starten.

Netzwerkschnittstellen

Für die Netzwerkkonfiguration muss erst einmal festgestellt werden, welches das Externe und welches das interne Interface ist. Hierfür kann man unter Centos den Network Manager nehmen. Die Interfaces anzeigen lassen kann man sich mit nmcli -o. Das könnte dann ungefähr so aussehen (Ausgabe geschönt und gekürzt):

[root@centos ~]# nmcli -o
ens192: connected to ens192
        "VMware VMXNET3"
        ethernet (vmxnet3), 00:0C:29:01:02:03, hw, mtu 1500
        ip4 default
        inet4 192.168.108.101/24
        route4 192.168.108.0/24 metric 100
        route4 default via 192.168.108.1 metric 100

ens224: disconnected
        "VMware VMXNET3"
        ethernet (vmxnet3), 00:0C:29:04:05:06, hw, mtu 1500

lo: unmanaged
        "lo"
        loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536

In dem Beispiel hat unser externes Interface eine Adresse und ein Default Gateway per DHCP bekommen, was praktisch ist. Wenn nicht, ist auch okay, Hauptsache, man weiß, welche Adressen hier verwendet werden sollen.

Wenn die Zuordnung nicht offensichtlich ist (also das externe Interface keine IP-Adresse per DHCP bekommen hat), kann man in der Konfiguration des VM in VMware schauen und die MAC-Addressen dort mit den MAC-Adressen in der nmcli-Ausgabe vergleichen.

Das interne Interface soll im Netz 192.168.1.0/24 das Default Gateway sein und kriegt die .1 als Adresse. Die benötigten Konfigurationsdateien liegen im Verzeichnis /etc/sysconfig/network-scripts, heissen ifcg-<Interface-Name> und können mit dem Editor der Wahl editiert werden.

Siehe dazu auch die Anleitung bei Redhat.

NAME=ens192
DEVICE=ens192
ONBOOT=yes
IPADDR=192.168.108.101
PREFIX=24
GATEWAY=192.168.108.1
DNS1=192.168.108.8

Das obige Beispiel zeigt die relevante Informationen für die Konfiguration der externen Schnittstelle. Alle anderen Einträge, die es da eventuell schon gibt, sind optional und im Zweifelsfalle eher störend. Oder verwirrend.

An der Stelle übrigends gerne mal einen reboot machen und alles weitere via SSH erledigen. Die VMware Konsole ist zwar okay aber es geht zum Beispiel kein copy-paste und das scrollen “nach oben” auch nicht.

Nun wird noch eine Datei für das interne Interface angelegt (ifcfg-ens224):

NAME=ens224
DEVICE=ens224
ONBOOT=yes
IPADDR=192.168.1.101
PREFIX=24

Hier fehlen die Einträge GATEWAY und DNS1, die brauchen wir hier nicht.

Damit sind die Netzwerkschnittstellen schon (fast) fertig. Aktivieren könnte man die Konfiguration mit einem Neustart, das machen wir aber später. Theoretisch müsste man auch einfach nur irgendeinen Networkmanager oder eine “Network” Service Unit neu starten aber das scheint (anders als bei Debian) unter Centos nicht aif Anhieb zu funktionieren und wenn was nicht auf Anhieb so funktioniert, wie’s im Handbuch steht, ist das erst mal blöd und ich ignoriere es.

Routing

Das Routing muss explizit aktiviert werden. Unter Centos kann man hierfür zum Beispiel eine Datei /etc/sysctl.d/01-forwarding.conf anlegen, die den Inhalt net.ipv4.ip_forward = 1 bekommt. Aktivieren könnte man das Routing mit sysctl -p, bringt aber jetzt noch nichts, da wir ja bisher nur ein aktives Netzwerkinterface haben.

NAT-Adresse

Um auf das Check Point Management System im Netz 192.168.1.0 von “außen” zugreifen zu können, benötigen wir noch eine zweite IP-Adresse für das NAT auf dem externen Interface. Hierfür wird einfach eine zweite Datei im network-scripts-Verzeichnis angelegt. Der Name der Datei ist ifcfg-ens192:0. Der Inhalt:

DEVICE=ens192:0
ONBOOT=yes
IPADDR=192.168.108.102
PREFIX=24

Nachdem die Datei gespeichert wurde, kann das System neu gestartet werden (reboot) und ist dann schon fast einsatzbereit. Nach dem Reboot kann man die Netzwerkschnittstellen mit ip address show kontrollieren:

[root@centos ~]# ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:01:02:03 brd ff:ff:ff:ff:ff:ff
    altname enp11s0
    inet 192.168.108.101/24 brd 192.168.102.255 scope global noprefixroute ens192
       valid_lft forever preferred_lft forever
    inet 192.168.108.102/24 brd 192.168.102.255 scope global secondary noprefixroute ens192:0
       valid_lft forever preferred_lft forever
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:04:05:06 brd ff:ff:ff:ff:ff:ff
    altname enp19s0
    inet 192.168.1.101/24 brd 192.168.1.255 scope global noprefixroute ens224
       valid_lft forever preferred_lft forever

Wenn alle Interfaces vorhanden sind, werden der Paketfilter und NAT konfiguriert.

Firewall-Regeln anlegen

Als erste Amtshandlung wird der firewalld entfernt. Der ist eh nur ein Frontend für nftables und macht alles unnötig kompliziert und auch irgendwie scheisse mit den Zonen und den drölftausend Chains. Und Google sagt mir, dass ich mit der Einschätzung nicht alleine bin. Also weg damit:

yum remove firewalld

Getreu dem Pfadfinder-Motto: Jeden Tag eine gute Tat. Schnaps drauf.

Das Regelwerk für nftables kann man (wie bei iptables mit ferm) in einer oder mehreren Konfigurationsdateien (eigentlich Skripte, siehe Netfilter Projektseite) formulieren. Das Regelwerk für unser Setup schreiben wir in die Datei /etc/nftables/firewall.nft. Der Inhalt beginnt mit einem Header, in dem ein paar allgemeine Dinge und die Variablen stehen:

#!/usr/sbin/nft -f

flush ruleset

define INT_NET = 192.168.1.0/24
define EXT_NET = 192.168.108.0/24
define INT_IF = ens224
define EXT_IF = ens192
define ADMINS = { 192.168.128.121, 192.168.130.147 }
define CP_MGMT = 192.168.1.11
define MGMT_NAT = 192.168.108.102
define MGMT_PORTS = { 22, 80, 443, 18190, 18205, 18210, 19009 }
define DNS = 192.168.108.8
define DNS_NTP_PORTS = { 53, 123 }
define PROXY = 192.168.108.13
define PROXY_PORT = 8080

Anhand der ersten Zeile weiss nftables, dass dies ein nftables Skript ist. Der erste “richtige” Befehl macht erst einmal “Tabula Rasa” und die Variablendefinition sollte selbsterklärend sein. Das Check Point Management System wird BTW die Adresse 192.168.1.11 bekommen, wie man hoffentlich am Variablennamen sieht.

Nach dem “table inet firewall”, das eine Regel-Tabelle namens “firewall” (Name frei wählbar) erstellt, die sowohl ipv4 als auch ipv6 (das bedeutet das “inet”) Regeln enthalten kann, folgt die Definition der “Inbound” Chain, die der eine oder andere vielleicht von iptables kennt. Hier werden Zugriff auf das System selber definiert.

table inet firewall {

    chain inbound {

        # default deny
        type filter hook input priority 0; policy drop;

        # allow established and related packets.
        ct state established,related accept

        # drop invalid packets.
        ct state invalid drop

        # allow icmp
        ip protocol icmp accept

        # loopback traffic.
        iifname lo accept

        # admin access
        iifname $EXT_IF ip saddr $ADMINS tcp dport 22 accept

        # logging of denied inbound traffic
        log prefix "[nftables] Inbound Denied: " flags all counter drop

    }

Das “policy drop” in der ersten Zeile sagt nftables, dass alle nicht explizit erlaubten Verbindungen verworfen werden sollen, und zwar (im Gegensatz zu “reject”) ohne Rückmeldung an das System, das die Verbindung initiiert hat. Die “ct” Statements beziehen sich auf der Status der Verbindung und erlauben Antwortpakete auf bereits erlaubte Verbindungen oder verbieten kaputte Pakete. Siehe nftables Wiki dazu.

Das eigentliche Regelwerk steckt in “allow icmp” und “admin access”: Diese Regeln erlauben sämtlich ICMP Traffic (ja, auch den “gefährlichen”) und SSH-Verbindungen aus dem “Admin”-Netzwerk. Der letzte Eintrag sorgt dafür, dass in der Input Chain verworfene Pakete auch protokolliert werden.

Der “Forward” Abschnitt regelt die Verbindungen, die durch die Firewall weitergeleitet werden:

chain forward {

    # default deny
    type filter hook forward priority 0; policy drop;

    # established and related packets.
    ct state established,related accept

    # drop invalid packets.
    ct state invalid drop

    # allow icmp
    ip protocol icmp accept

    # management server access
    iifname $EXT_IF ip saddr $ADMINS ip daddr $CP_MGMT tcp dport $MGMT_PORTS log prefix "[nftables] CP Management: " accept
    iifname $INT_IF ip daddr $DNS udp dport $DNS_NTP_PORTS accept
    iifname $INT_IF ip daddr $PROXY tcp dport $PROXY_PORT accept

    # logging of denied forwards
    log prefix "[nftables] Forward Denied: " flags all counter drop

}

Auch hier gibt’s ein “Default Deny”. Die Policy erlaubt den Administratoren (genauer: den beiden Quell-Adressen, die den Admin-PCs zugeordnet sind) ssh und http auf den Check Point Management Server im internen Netz und dem internen Netz den Zugriff auf einen Web Proxy, an dem die Zugriffe auf diejenigen URLs eingeschränkt sind, die ein Check Point Management System so zum Leben und Updaten braucht.

Dann gibt’s im “table inet firewall” Abschnitt noch eine “chain outbound”. Die ist aber unspannend, da wird alles erlaubt.

Spannender ist das NAT-Regelwerk:

table ip nat {
        chain prerouting {
                type nat hook prerouting priority 0; policy accept;
                iifname $EXT_IF ip daddr $MGMT_NAT dnat to $CP_MGMT
        }

        chain postrouting {
                type nat hook postrouting priority 100; policy accept;
                oifname $EXT_IF ip saddr $CP_MGMT snat to $MGMT_NAT
        }
}

In die Pre-Routing Chain laufen Pakete rein, bevor sie an den IP-Stack zum Routen geschickt werden. Und auch Filtern macht nftables erst nach dem NAT in der Prerouting Chain (Siehe Kapitel Netfilter Hooks im Netfilter Wiki). Dies sollte man beim Erstellen der Regeln in der Forward Chain berücksichtigen.

Die Regel im Prerouting greift, wenn die Zieladresse eines eingehenden Paketes die NAT-Adresse für das Check Point Management System ist. Dann wird diese durch die interne Adresse des Systems ersetzt. Im Postrouting wird in Paketen vom Check Point Management nach “draußen” die Quell-Adresse ersetzt.

Das komplette Regelwerk gibts dann hier (Link folgt noch).

Aktivieren!

Nachdem das Regelwerk angelegt ist, wird die Konfigurationsdatei gespeichert und geschlossen. Zm Aktivieren muss die Datei /etc/sysconfig/nftables.conf editiert werden:

# Uncomment the include statement here to load the default config sample
# in /etc/nftables for nftables service.

#include "/etc/nftables/main.nft"
include "/etc/nftables/firewall.nft"

# To customize, either edit the samples in /etc/nftables, append further
# commands to the end of this file or overwrite it after first service
# start by calling: 'nft list ruleset >/etc/sysconfig/nftables.conf'.

Im Anschluss kann der nftables Service mit systemctl restart nftables neu gestartet werden.

Um die Firewall-Regeln anzuschauen, kann man die Befehle

  • nft list table inet firewall
  • nft list table ip nat
  • nft list ruleset

Wobei Letzterer sowohl NAT als auch Firewall Regeln anzeigt.

Jetzt kann man das Check Point Installations-ISO in den Datastore kopieren, eine entsprechend dimensionierte VM erstellen (Betriebssystem Redhat ES 7), die VM an den internen vSwitch hängen und “ganz normal” installieren.

Obacht: Man sollte nicht der Versuchung erliegen, der Check Point Maschine alle ausgehenden Verbindungen zu erlauben. Dann könnte die nämlich auch mit den (bereits vorhandenen) produktiven Systemen sprechen, was unter Umständen zu unerklärlichen Seitenefekten führen könnte…