ltcp/bericht/nftables/nftables-vs.tex

175 lines
9.7 KiB
TeX

\subsubsection{Rückblick}
Da es bereits vor \texttt{nftables} und \texttt{iptables} mehrere Firewall-Implementierungen für Linux gab, lohnt sich ein kleiner Rückblick.
\begin{itemize}
\item ab Kernel 1.0 (1994): \texttt{ipfw} (IP FireWall), von FreeBSD auf Linux portiert
\item ab Kernel 2.0 (1996): \texttt{ipfwadm} (IP FireWall Admin)
\item ab Kernel 2.2 (1999): \texttt{ipchains}
\item ab Kernel 2.3 (2000): \texttt{iptables}
\item ab Kernel 3.13 (2014): \texttt{nftables}
\end{itemize}
Wie man sieht mussten sich Linux-Nutzer, die eine Firewall benötigten, seit 1994 bis jetzt mit vier verschiedenen Firewall-Lösungen auseinander setzen. Die letzte davon, \texttt{iptables} ist nun bald seit 14 Jahr im Einsatz und soll jetzt auf Dauer von \texttt{nftables} abgelöst werden.
\subsubsection{Funktionsweise}
\paragraph{\texttt{iptables}} selbst ist nur für das Firewalling des IPv4-Traffics verantwortlich. Für andere Protokolle gibt es weitere entsprechende Tools:
\begin{itemize}
\item \textbf{\texttt{ip6tables}} für IPv6
\item \textbf{\texttt{arptables}} für ARP (Address Resolution Protocol, zu IP-Adressen werden im lokalen Subnetz MAC-Adressen abgefragt, um Ethernet-Pakete versenden zu können)
\item \textbf{\texttt{ebtables}} für Ethernet Bridging
\end{itemize}
Alle diese Tools haben im Linux-Kernel eine Entsprechung in der Netfilter-Infrastruktur. An dieser Stelle sind für jedes Protokoll alle möglichen Filterkriterien und -aktionen einzeln im Kernel (meist als Modul ladbar) implementiert, der Code kann auf jedes einzelne Feld eines Pakets über eine Datenstruktur zugreifen. Da diese Implementierungen alle sehr protokollspezifisch sind, musste zunächst der Code für alle Protokolle repliziert werden und wurde dann getrennt erweitert und gewartet.
\paragraph{\texttt{nftables}} soll alle Implementierungen für die verschiedenen Protokolle zusammenfassen und damit die Schnittstelle der Konfigurationstools zum Kernel vereinfachen, die Code-Replikation verringern und ein effizienteres Abarbeiten der Regeln gewährleisten.
Dies wird durch den Einsatz einer kleinen virtuellen Maschine im Kernel gewährleistet. Im Gegensatz zu \texttt{iptables}, wo für jedes Filterkriterium einer Regel eine eigene Funktion im Kernel aufgerufen wurde, wo also der Kernel jedes Protokoll mit allen Einzelheiten kennt, wird nun stattdessen das zu verarbeitende Paket geladen und bestimmter Byte-Code in der virtuellen Maschine angewendet. Dieser Byte-Code entsteht, indem die Firewall-Regeln noch im Userspace geparst, kompiliert und über die Netlink-Schnittstelle in den Kernel geladen werden. Dieser Code kann nun Operationen auf einzelnen Felder und Bits eines Pakets ausführen:
\begin{itemize}
\item betrachten und vergleichen: IP-Adressen können z.B. direkt mit einer festgelegten Adresse verglichen werden
\item arithmetische Operationen darauf ausführen: bestimmte Bereiche von IP-Adressen können maskiert werden, um bspw. nur zu untersuchen, ob das Paket aus einem bestimmten Subnetz kommt
\item das Paket ändern: DNAT (Destination Network Address Translation) und SNAT (Source NAT) um Ziel- oder Absenderaddressen und -ports zu manipulieren
\end{itemize}
Darüber hinaus soll nun ein atomares Austauschen der Firewall-Regeln in einer einzelnen Netlink-Transaktion möglich sein. (Netlink ist die Schnittstelle zum Kernel, mit der die Netfilter-Infrastruktur angesprochen wird; dies ist die bisher schnellste Schnittstelle zum Kernel, besonders wenn große Mengen an Daten übertragen werden sollen) Dies funktioniert bisher allerdings noch nicht effektiv, weil das Userspace-Tool \texttt{nft} noch keine Möglichkeit bietet, alle bereits existenten Regeln zu entfernen und gleichzeitig die aktualisierte Fassung der Regeln zu laden. Zur Lösung dieses Problems gäbe es zunächst zwei Möglichkeiten:
\begin{itemize}
\item Man könnte hier für jede Regel-Aktualisierung, die man durchführen will, ein eigenes nft-Script schreiben, dass zunächst die alte Regeln entfernt und einen neuen Regelsatz hinzufügt. (Eine Art Migrations-Skript.) Diese Aktualisierung ist dann atomar. Dies könnte auch automatisiert werden.
\item Man könnte zunächst in einem ersten Skript alle Regeln entfernen und noch in der gleichen Transaktion eine einzige Regel hinzufügen, die sämtlichen Datenverkehr verbietet. In einem zweiten Skript könnte man dann innerhalb einer zweiten Transaktion diese Regel wieder entfernen und gleichzeitig den gewünschten neuen Regelsatz einfügen. Auch hier hätte man dann kein Zeitfenster mehr, indem das System »offen« ist. Es besteht aber die Gefahr, dass man hier einen Fehler in den neuen Regeln hat, so dass diese gar nicht angewendet werden und man sich somit aus dem System aussperren könnte.
\end{itemize}
\subsubsection{table, chain, hook}
Während \texttt{iptables} noch vordefinierte Tabellen (\texttt{filter}, \texttt{nat}, \texttt{mangle}) und mehrere Chains (\texttt{PREROUTING}, \texttt{INPUT}, \texttt{FORWARD}, \texttt{OUTPUT}, \texttt{POSTROUTING}) in diesen Tabellen hatte, gibt es in \texttt{nftables} keine vorgefertigen Tabellen und Chains mehr. Stattdessen gibt es sogenannte Hooks, in die man sich in selbst erstellten Chains »einhängen« kann. Dabei kann man eine Priorität angeben, die die Reihenfolge der Abarbeitung der Chains angibt. Diese Hooks entsprechen dabei größtenteils den Tabellen und Chains aus \texttt{iptables}.
\pagebreak
\subsubsection{Syntax}
Im folgenden werde ich einige Regeltypen zeigen und wie sich diese bei den beiden Firewall-System unterscheiden.
\paragraph{Einfache Targets} \mbox{} \\
Für \texttt{iptables} könnte man bspw. folgenden Aufruf verwenden, um eingehende TCP-Pakete auf Port 22 zunächst zu loggen und anschließend zu verwerfen: \\
\shellcmd{iptables -A INPUT -p tcp ---dport 22 -j LOG} \\
\shellcmd{iptables -A INPUT -p tcp ---dport 22 -j DROP} \\
Als Script:
\begin{lstlisting}
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp --dport 22 -j LOG
-A INPUT -p tcp --dport 22 -j DROP
COMMIT
\end{lstlisting}
\begin{center}
\rule{0.5\paperwidth}{0.4pt}
\end{center}
Für \texttt{nftables} müsste man sich zunächst mit folgenden Befehlen je eine beispielhafte Tabelle und Chain anlegen: \\
\shellcmd{nft add table filter} \\
\shellcmd{nft add chain filter input \enquote{\{ type filter hook input priority 0; \}}} \\
Die Regel kann man nun bspw. wie folgt einfügen: \\
\shellcmd{nft add rule filter input tcp dport 22 log drop} \\
Als Script:
\begin{lstlisting}
table filter {
chain input {
type filter hook input priority 0;
tcp dport 22 log drop
}
}
\end{lstlisting}
\paragraph{Mehrere benutzerdefinierte Chains} \mbox{} \\
In \texttt{iptables} kann wie folgt eine neue Chain eingefügt werden: \\
\shellcmd{iptables -N sshchain} \\
Der Name dieser Chain kann nun auch direkt als Target bei weiteren Regeln angegeben werden: \\
\shellcmd{iptables -A INPUT -p tcp ---dport 22 -j sshchain} \\
Dieser Chain lassen sich nun ebenfalls weitere Regeln hinzufügen. \\
\shellcmd{iptables -A sshchain -j DROP} \\
\shellcmd{iptables -A sshchain -j LOG} \\
Als Script:
\begin{lstlisting}
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:sshchain - [0:0]
-A INPUT -p tcp --dport 22 -j sshchain
-A sshchain -j LOG
-A sshchain -j DROP
COMMIT
\end{lstlisting}
\begin{center}
\rule{0.5\paperwidth}{0.4pt}
\end{center}
In \texttt{nftables} sieht der Befehl zum Anlegen einer neuen Chain (in einer bereits existierenden Tabelle \texttt{filter}) wie folgt aus: \\
\shellcmd{nft add chain ip filter sshchain} \\
Um diese Chain »anspringen« zu können muss bei \texttt{nftables} das \texttt{jump}-Target verwendet werden: \\
\shellcmd{nft add rule ip filter input tcp dport 22 jump sshchain} \\
Regeln lassen sich dann wie gewohnt hinzufügen: \\
\shellcmd{nft add rule ip filter sshchain log drop} \\
Als Script:
\begin{lstlisting}
table ip filter {
chain input {
type filter hook input priority 0;
tcp dport ssh jump sshchain
}
chain sshchain {
log drop
}
}
\end{lstlisting}
\paragraph{Mehrfaches Matching auf ein Paketheader-Feld}
Möchte man bei \texttt{iptables} in einer Regel mehrere Werte für ein Header-Feld matchen, so muss man für jeden Wert eine eigene Regel anlegen. Beispiel für das Matching von ICMP-Paketen: \\
\shellcmd{iptables -A INPUT -p icmp --icmp-type echo-request -j DROP} \\
\shellcmd{iptables -A INPUT -p icmp --icmp-type echo-reply -j DROP} \\
oder für IP-Adressen: \\
\shellcmd{iptables -A INPUT -s 10.30.44.7 -j DROP} \\
\shellcmd{iptables -A INPUT -s 10.27.21.2 -j DROP}
\begin{center}
\rule{0.5\paperwidth}{0.4pt}
\end{center}
Bei \texttt{nftables} lässt sich dies zu einer Regel zusammenfassen: \\
\shellcmd{nft add rule ip filter input icmp type \{ echo-request, echo-reply \} drop} \\
und für IP-Adressen: \\
\shellcmd{nft add rule ip filter input saddr \{ 10.30.44.7, 10.27.21.2 \} drop} \\
Während sich bei \texttt{iptables} das Matching für IP-Adressen noch auf \texttt{ipset}s übertragen lässt, bei denen in einem Set mehrere IP-Adressen gespeichert sein können, gegen die dann gematcht werden können, so existiert hierfür aber kein Äquivalent für andere Datentypen wie ICMP-Typen, einzelne Ports usw. Bei \texttt{nftables} hingegen lassen sich in diesen »unnamed sets« alle möglichen Datentypen speichern und matchen. Außerdem gibt es noch die »named sets«, die man in mehreren Regeln gleichzeitig verwenden kann: \\
\shellcmd{nft add set filter ipmatches \enquote{\{ type ipv4\_address; \}}} \\
\shellcmd{nft add element filter ipmatches \enquote{\{ 10.30.44.7, 10.27.21.2 \}}} \\
Gematcht werden können diese Sets dann wie folgt: \\
\shellcmd{nft add rule ip input ip saddr @ipmatches drop} \\