nftables 入門 — Rocky Linux 9 でファイアウォールを nft で素のまま書く

blue UTP cord

前回、firewalld か iptables か という記事で「Rocky 9 ではどちらも下は nftables なので、これから低レベルを学ぶなら iptables 構文より nft を覚えた方が将来性がある」と書きました。今回はその続きとして、nftables を素のまま書いてみる入門です。

iptables に慣れた人ほど最初は文法に戸惑いますが、モデル自体はむしろシンプルで一貫しています。概念を押さえてから、ゼロから SSH/HTTP を通す実用ルールを書いて永続化し、firewalld を止めて nft に一本化するところまでやります。

前提条件
  • Rocky Linux 9.x が動作するサーバ
  • sudo 権限
  • SSH 以外のコンソール手段(VPS のコンソール等)。設定ミスで締め出される事故を避けるため
  • firewalld か iptables か を先に読んでおくと背景が分かりやすい

1. モデルを「鉄道路線」で理解する

nftables の文法に入る前に、そもそもファイアウォールがパケットをどう捌いているかを掴んでおきましょう。ここを 鉄道の路線図にたとえると、iptables も nftables も一気に見通しが良くなります。

たとえ実体nft / iptables の用語
乗客1つのパケットpacket
路線(通る駅の並び)パケットの通り道hook の経路
パケットを評価する地点chain(INPUT 等)
駅の改札の係員リスト上から順に照合する判定rule
改札の既定対応どれにも当たらなかったときの動作policy

ポイントは、路線(通る駅)は乗客の行き先で変わること。すべての駅に停まるわけではありません。

flowchart LR
    IN["パケット到着"] --> PRE["PREROUTING 駅"]
    PRE --> J{"宛先は<br/>自ホスト?"}
    J -->|自分宛| INP["INPUT 駅"] --> APP["ローカルプロセス<br/>(sshd 等)"]
    J -->|通過するだけ| FWD["FORWARD 駅"] --> POST["POSTROUTING 駅"] --> OUTNIC["外へ"]
    APP --> OUT["OUTPUT 駅"] --> POST
  • 外から SSH で入ってくるパケット → PREROUTING駅 → INPUT駅 を通ってローカルの sshd へ。FORWARD 駅には停まらない
  • サーバが素通りさせるだけのパケット → PREROUTING駅 → FORWARD駅 → POSTROUTING駅
  • サーバ自身が出す応答 → OUTPUT駅 → POSTROUTING駅

そして 各駅の改札(rule)は上から順に照合し、当たったらそこで判定が確定します。

  • accept … 通してOK、次の駅へ
  • drop … ここで降ろして終了(黙って破棄)
  • reject … 「お断り」と通知して終了
  • どの改札にも当たらなければ、最後に policy(駅の既定対応) が適用される

公開サーバで主に整備するのは INPUT 駅(自分宛の着信をどう通すか)です。

iptables と nftables の違いは「路線が敷設済みか、自分で引くか」だけ

ここまでの「駅」と「改札」は、iptables にも nftables にもまったく同じ仕組みで存在します。違うのは一点だけ。

  • iptables … 路線図(どの駅にどの順で停まるか)が最初から敷設済みで見えない。INPUT という名前を使えば、自動的に「INPUT 駅」に置かれる
  • nftables … その路線を自分で宣言して引く。だから登場する概念が増えるわけではなく、iptables が裏で勝手にやっていたことが表に出てくるだけ

nft でベースチェイン(駅)を作るときは、必ず type / hook / priority / policy を書きます。

text
type filter hook input priority 0; policy drop;
       │         │          │           └ 改札の既定対応(マッチしなければ drop)
       │         │          └ 同じ駅に複数の改札があるときの順番(小さいほど先)
       │         └ どの駅か(input/forward/output/prerouting/postrouting)
       └ 駅の種類(filter / nat / route)

つまり hook input は「INPUT 駅を作る」、priority 0 は「その駅での改札の並び順」の宣言。iptables が最初から敷いていた路線を、自分の手で描いているだけ——と分かれば、もう怖くありません。

2. 現状を確認する

まず Rocky 9 の今の状態を見ます。標準では firewalld が動いていて、その実体が nftables のルールとして入っています。

bash
# 現在 firewalld が動いているか
systemctl is-active firewalld

# nftables の「実体」を覗く(firewalld が入れたルールが見える)
sudo nft list ruleset | head -40

firewalld が動いている間は、ここに firewalld 由来の長いルールが並びます。これを自分の手書きルールに置き換えるのがこの記事のゴールです。

やってはいけないこと

この先はロックアウト事故に直結します。 policy drop のチェインを、SSH を許可する前に適用すると即座に締め出されます。必ず (1) SSH 許可ルールを含んだ状態で適用し、(2) 今の SSH セッションは開いたまま別セッションで疎通確認し、(3) VPS のコンソールをいつでも開けるようにしておいてください。

3. ルールセットを書く

nftables の設定は1ファイルにまとめて書けます。Rocky 9 の nftables.service/etc/sysconfig/nftables.conf を読み込むので、ここに書きます。

bash
sudo cp /etc/sysconfig/nftables.conf /etc/sysconfig/nftables.conf.bak
sudo vi /etc/sysconfig/nftables.conf

最小構成の実用ルールがこちらです。inet ファミリにすると IPv4 と IPv6 をまとめて扱えます。

/etc/sysconfig/nftables.conftext
#!/usr/sbin/nft -f

# 既存ルールを全消去してから読み込む(再適用を冪等にする)
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # 確立済み・関連パケットは通す(応答が返らないと詰む)
        ct state established,related accept
        ct state invalid drop

        # ループバックは無条件で許可
        iif "lo" accept

        # ping / IPv6 近隣探索など
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        # 公開するサービス(SSH を必ず含めること!)
        tcp dport { 22, 80, 443 } counter accept
    }

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

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

ポイント:

  • ct state established,related accept を input の先頭付近に置く。これが無いと、こちらから出した通信の応答まで drop されて「繋がらない」状態になります
  • tcp dport { 22, 80, 443 }{ } は集合(set)。複数ポートを 1 行で書けます
  • counter を付けると、そのルールに当たったパケット数が nft list ruleset に表示されます。「どのルールが効いているか」を一覧で追える、nft 流の見やすさです
  • policy drop なので、明示的に許可した以外は全部落ちます
注意

SSH のポートを標準の 22 から変えている場合は、22 をそのポート番号に必ず置き換えてください。ここを間違えると次の適用で締め出されます。

4. 適用して動作確認

文法チェック → 適用 → 確認の順で進めます。

bash
# 文法だけ検査(-c はチェックのみ、適用しない)
sudo nft -c -f /etc/sysconfig/nftables.conf

# 問題なければ適用
sudo nft -f /etc/sysconfig/nftables.conf

# 効いているルールを確認(-a で handle 番号も表示)
sudo nft -a list ruleset
text
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        ...
        tcp dport { 22, 80, 443 } counter packets 128 bytes 9216 accept # handle 9
    }
}

counter packets ... のように、当たった回数が見えます。ここで 必ず別の端末から SSH で入り直せることを確認してください。入れなければ、開いたままの今のセッションで設定を戻します。

5. firewalld を止めて nftables に一本化

疎通が確認できたら、firewalld を停止して nftables.service に寄せます。前回書いたとおり、両者を同時に動かすと衝突するので、片方に寄せるのが鉄則です。

bash
# firewalld を停止・無効化
sudo systemctl disable --now firewalld

# nftables を有効化(起動時に nftables.conf を自動ロード)
sudo systemctl enable --now nftables

# 状態確認
systemctl is-enabled nftables firewalld
出力
enabled
disabled

これで再起動後も、/etc/sysconfig/nftables.conf の内容がそのまま復元されます。firewalld 由来のルールはもう出てこず、nft list ruleset に映るのは自分が書いた分だけになります。

6. 運用でよく使う操作

設定ファイルを編集して nft -f で読み直すのが基本ですが、一時的な追加・削除も覚えておくと便利です。

bash
# 一時的にルールを足す(例: 8080 を開ける)
sudo nft add rule inet filter input tcp dport 8080 counter accept

# handle 番号を調べて、その行だけ消す
sudo nft -a list chain inet filter input
sudo nft delete rule inet filter input handle 9
ノート

nft add での変更はその場限り(再起動で消える)。恒久化したいときは必ず /etc/sysconfig/nftables.conf 側にも反映してください。「ファイルが正本、nft add は実験」と割り切るのが安全です。

IP 単位の許可リスト/ブロックリスト(fail2ban 的な使い方)には named set が便利です。これは別記事で掘り下げます。

text
    set blocklist {
        type ipv4_addr
        flags interval
    }
    chain input {
        ip saddr @blocklist drop
        # ...
    }

まとめ

  • nftables の登場人物は table / chain / rule の3つだけ。組み込みが無いぶん「効いている設定=自分が書いた全部」で見通しが良い
  • ベースチェインは type / hook / priority / policy を宣言する。公開サーバは主に input フック
  • 実用ルールは ct state established,related accept を先頭に、SSH を必ず許可してから policy drop
  • 設定は /etc/sysconfig/nftables.conf に書いて systemctl enable nftables で永続化
  • firewalld は 止めて一本化。混在は厳禁
  • counter を付ければ、どのルールが効いているか一覧で追える

iptables 構文を覚え直すより、最初から nft で書いた方が「実体をそのまま触っている」感覚があって、個人的にはこちらの方が見通しが良いと感じています。