nftables/iptables/ufwの使い分け・ベストプラクティス

概要

Linuxファイアウォール(パケットフィルタ)は、内部的にはLinuxカーネルのnetfilterという機能が担っています。iptablesやnftablesはnetfilterを操作するフロントエンドで、ufwiptablesやnftableを操作するさらに上位のフロントエンドです。また、筆者は使ったことがありませんがfirewalldというのもufwと同じくiptablesやnftablesを操作する上位のフロントエンドのようです。

Linuxファイアウォールを運用するにあたってこのどれを使うべきかが問題になると思うのですが、うまくいいとこどりをして良い感じに運用できそうな方法を見つけたので紹介します。なお、主に個人でのサーバー運営程度を想定しており、業務で使うような大規模システムだったらもっとちゃんとしたやり方が必要かと思います。

iptablesとnftablesの基本

この2つの違いとしては、簡単にいうとnftablesはiptablesの後継で、nftablesが本格的に導入されているUbuntu 22.04などではiptablesコマンドもnftablesを操作するフロントエンドとして動作するようになっています。

iptablesiptablesコマンド、nftablesはnftコマンドを使用します。(処理の共通化など細かい使用感を除けば)パケット処理に関してできることは大体同じですが、構文の見た目は結構違います。nftables自体はそこまで最近導入されたものではないと思うのですが、依然としてiptablesコマンドがほぼ変わらない動作で使用可能なこともあってか、使い方に関する情報が比較的少ない印象です。

例えば以下が参考になります。

nftablesのテーブル

nftablesとiptablesの(特にこの記事において)重要な違いは、ユーザーが独自のテーブルを作成できるということです。

iptablesではnatやmangleのように役割ごとに決まった名前のテーブルを使う必要がありました。一方でnftablesではテーブルは好きな名前で作成し、その中のチェインにおいてnatやmangleなどのタイプを指定するやり方に変わりました。

nftables環境でiptablesコマンドを使用した際には、natやmangleといった従来の固定的なテーブル名に由来するテーブルだけが読み書きされます。つまり、iptablesコマンドを使って独自のテーブルの内容を管理することはできず、nftコマンドを使う必要があります。ufwは(nftables環境でも)依然としてiptablesで動いていますが、firewalldはnftablesを使って独自テーブルを管理しているので、nftを使わないと見られないということに注意が必要です。

nftablesのルールの管理

nftablesでは、nftコマンドを用いてルールをいちいち追加・削除してもいいですが、設定ファイルを用いてルールを管理することもできます。

nftablesというサービスがあって、これを有効化(sudo systemctl enable nftables)すると、/etc/nftables.confというファイル(ディストリビューションによって異なる可能性あり)を起動時に読み込んでくれます(デーモンではなくOneShotタイプ)。

設定ファイルの読み込みの際にはnft -f xxx.confとfオプションを使用します。nftablesが動くのは起動時だけなので、起動後であればルールを反映させたいタイミングでこのコマンドを実行します。

設定ファイルでは、以下のように最初でflush rulesetとして既存のルールを全て削除してから新たにルールを追加していく方法がよく使われます。

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
   ......(省略)
}

そのまま追加するとnft -fでの読み込みのたびにルールが複製されてしまいますが、flushを使用するとこれを防げます。

flush rulesetの問題点

しかし、このflush rulesetには問題があり、文字通り全ての設定を消去してしまうので、ufwやfirewalldやその他VPNなど(例: tailscale)が追加したルールまで全て消えてしまうのです。実際、ufw稼働中にnft -f xxx.confと(ufw関連のルールがない)confファイルを読み込むと、ufwは勝手にdisabledの状態になってしまいます。tailscaleも、うまく通信が通らない状態になってしまいます。

全てをnftablesのファイルで管理するようにすればこの問題はなくなりますが、ufwの簡潔なコマンドの恩恵を受けられなくなり、tailscaleは二重管理のような状態にならざるを得ません。

table単位でのflush

そこで、この記事で解決策として提案したいのが、tableの単位でルールを管理してflushを行うことです。

ntfablesでは、flush rulesetのように全てを消去するのではなく、flush table ip my_ip_tableのようにテーブルを指定してその中のルールを全て消去するということが可能です。これならmy_ip_tableの外側にあるルールとは一切干渉しません。

具体的には、以下のような記述を基本単位としてルールを管理するのが良さそうです。

table ip my_ip_table {
}
flush table ip my_ip_table
table ip my_ip_table {
......(独自のチェイン・ルール)
}

このようにすれば、ファイルを読み込み時にmy_ip_tableの内容だけファイルに書かれた内容に変更できます。flushの前に最初で一旦空のmy_ip_tableを作成しているのは、my_ip_tableが存在しない状態だとflushコマンドが失敗してしまうからです。(既存のテーブルを改めて作成するのはエラーにはなりません)

なお、前述のようにnatやmangleなどの役割に応じてテーブルを分ける必要はありませんが、arpとipのテーブルなどは別々に書く必要があるので、その個数の分だけ上記の基本単位を書くことになります。

この内容をどこに書けばいいかというと、もちろん既存の/etc/nftables.confに書いてもいいですが、/etc/nftablesにはflush ruleset文を始めとしてちょっとした内容が書いてあるのでそれを変えたくないという場合は、

include "/etc/nftables.custom.conf"

のように独自のファイルをインクルードしておいて、そのcustomファイルのほうに上記の基本単位を記述していく(ルールの更新の際はcustomのほうだけをnft -fで読み込む)という方法も考えられます。

nftables.custom.confに誤りがあると、起動時にnftablesサービス自体が失敗し、ufwなども連鎖的にうまく起動できなくなってしまうので注意してください。(セキュリティ的に必須でない内容は事後的に読み込むのもアリかも?)

ufw/iptablesとの使い分け

これで、ufwVPNの動作を阻害することなく任意のタイミングで独自ルールを適用できるようになりました。

ufwは、ポートやインターフェイスなど条件を指定して許可/不許可の設定をする分には見通しがよく便利で、gufwというGUIツールまであるのですが、NATなどの(必ずしも「ファイアウォール」とあまり関係のない)機能を扱おうとすると設定ファイルの編集が必要になります。

そこで、ファイアウォール的な部分だけはufwで管理して、NATなどの細かい設定は前述の独自のユーザーテーブルを用いて管理するという使い分け方がいいのではないかと思います。ufwとnftablesのどちらか一方だけを選ぶ必要はありません。

ところで、結局nftablesとufwの使い分けの話ばっかりしてしまいましたが、(nftables環境の)iptablesにも使い道はあります。

nftコマンドの大きな欠点として、追加したルールの削除が非常に面倒というのがあります。追加のしかたは大体iptablesと同じなのですが、削除はルールの内容を指定して検索することはできず、ルールの番号(handle)を取得してそれを指定して削除しなければいけません。

iptablesであれば、追加の際の-A(または-I)を-Dに変えるだけで対応するルールを見つけ出して削除してくれます。構文の見やすさ、ネット上の情報の多さなどからいっても、動作テスト時にルール単位で追加・削除をする分にはiptablesコマンドのほうが圧倒的に便利です。

従って、試験的なルールの追加・削除はiptablesで行い、確定したらそのルールをnft list rulesetで表示してnftablesでの書き方を学び、それを先ほどのnftables.custom.confに転記し、iptablesで設定したものは削除する、といった使い方をすると結構やりやすいです。

nftables未導入環境やfirewalldでの方法

この記事ではnftables環境を前提にしていましたが、iptables環境でも似たようなことはできます。

iptablesでは残念ながら独自のテーブルまでは作れませんが、独自のチェインは作れて、またチェイン単位でのflushもできます。また、iptables-restoreを使えばアトミックにルールを置き換えてくれます。従って、まず自分用のチェインをそれぞれのテーブルに登録しておいてから、好きなタイミングでチェイン内のflushとルール追加を行うことができます。(空行がないとエラーになったりするので気をつけてください: firewall - Error applying iptables rules using iptables-restore - Server Fault

ただしiptables-restoreのファイル内でincludeを行うことができないのと、チェインの追加・削除はそれぞれチェインが存在しない状態・する状態で実行しないとiptables-restore全体の実行が失敗する(何も起こらない)のでさっきのnftでのmy_ip_tableの追加のような「既に存在するならそのまま、存在しないなら作成」ができません。

従って、起動時に一度のみ実行されるファイルと起動後の任意のタイミングで実行するファイルに分けて、独自チェインを追加する部分は前者、チェイン内を編集する部分は後者に書いておく必要があります。多分。

あと、具体的には触れませんが、firewalldについてもufw同様にiptables/nftablesとの使い分けができます。

アトミックなルールの置き換え

この記事ではルールを一旦削除して追加するということを行っていますが、このときに、削除されている一瞬の間にパケットが侵入してこないか?ということが不安になるかと思います。

実際には、nftablesやiptablesでは、ルールの変更をアトミック(原子的)に実行することができます。簡単にいえば、全ての変更が一気に適用されるという意味で、設定ファイルなどを読み込んだ際に「既存のルールがある状態」か「既存のルールがなくなって新規ルールが導入された状態」のどちらか一方でのみパケットを処理するため、隙が生じないということです。

nft -fでの読み込みや、iptablesならiptables-restoreというコマンドがこのアトミックな動作をします。