nftablesによるLinux透過型ブリッジファイアウォール:IPアドレスもトポロジも変えずにネットワークを制御する

Network tutorial - IT technology blog
Network tutorial - IT technology blog

よくある状況だ:稼働中のシステムのルーターとスイッチの間にファイアウォールを追加するよう求められる。クライアントはIPアドレスの変更を一切許可せず、サーバーのゲートウェイも変えられない。これは多くのsysadminが直面する問題だ — そしてtransparent firewallこそがその答えだ。

実際の問題:トポロジを「壊さずに」ファイアウォールを挿入する

こんなケースを経験したことがある:192.168.10.0/24のセグメントを使う約50台のサーバーが稼働するproductionシステムで、ゲートウェイは192.168.10.1(クライアントのルーター)だった。サーバーグループ間のトラフィック制御と危険なプロトコルのフィルタリングが求められたが、稼働中の機器のIPアドレスや設定は絶対に変更できない条件だった。

従来のファイアウォールはLayer 3で動作する — パケットを受け取り、ルーティングを処理してからフォワードする。これはネットワーク内の全マシンのゲートウェイをファイアウォールのIPに変更しなければならないことを意味する。50台のproductionサーバーで?業務時間中にそんな作業をする勇気のある人はいない。障害時のダウンタイムリスクやロールバック時間のことを考えると、なおさらだ。

原因の分析:なぜ通常のファイアウォールが適さないのか

問題はLayer 3ファイアウォール(ルーティングベース)がトポロジの変更を強制する点にある:

  • 2つのインターフェースに異なるIPアドレスが必要(LAN側とアップリンク側)
  • ネットワーク内のすべての機器のデフォルトゲートウェイをファイアウォールのIPに変更しなければならない
  • DNS、監視、バックアップ — 旧ゲートウェイに依存するものすべてが連鎖的に影響を受ける

ブリッジファイアウォール(透過型ファイアウォール)はLayer 2で動作する — 通常のスイッチのように「透明」だ。ネットワーク内のマシンから見ると、このファイアウォールは存在しないも同然だ。パケットはその存在を知らずに通過する。

技術的には、Linux bridgeを作成すると、カーネルはMACアドレスもIPアドレスも変更せずに、メンバーインターフェース間でEthernetフレームを転送する。nftablesにはブリッジnetfilterにフックして、フォワードされる前にこれらのフレームを検査・フィルタリングする機能がある。

解決方法

方法1:ebtables(旧式、非推奨)

ebtablesはiptables時代からのbridgeレイヤーのパケットフィルタリングツールだ。構文が複雑でiptables/ip6tablesと統一されておらず、徐々にdeprecatedになっている。数年前に使っていたが、IPv4、IPv6、bridgeの3つのルールセットを別々にメンテナンスしなければならないことほど面倒なことはなかった。

方法2:iptables + physdevモジュール

iptablesのphysdevモジュールを使ってブリッジを通過するパケットをマッチする方法だ。動作はするが、複雑なsysctlを多数有効にする必要があり、構文も覚えにくく間違いやすい:

# iptables physdevを使った旧式の方法 — 非推奨
iptables -I FORWARD -m physdev --physdev-in eth1 --physdev-out eth2 \
  -p tcp --dport 22 -j ACCEPT

方法3:nftables bridge(モダン、推奨)

nftablesはカーネル4.17以降からネイティブのbridgeフィルタリングをサポートしている。単一のルールセットでIPv4、IPv6、Ethernetフレームすべてを処理できる。これが現在新規に導入するすべてのシステムで使っている方法だ。

ベストな方法:nftablesでTransparent Firewallを構築する

ステップ1:システムの準備

Linuxサーバーには少なくとも2つのネットワークインターフェースが必要だ(例:eth0はルーター/アップリンクに接続、eth1は内部LANスイッチに接続)。カーネルバージョンを確認し、必要なモジュールをロードする:

# カーネルバージョンを確認(nftables bridgeサポートには4.17以上が必要)
uname -r

# bridge netfilterモジュールをロード
modprobe br_netfilter
modprobe nft_bridge_meta

# モジュールがロードされているか確認
lsmod | grep -E "br_netfilter|nft_bridge"

ステップ2:Linux Bridgeの作成

bridgeを作成して2つのインターフェースを追加する。bridgeにIPアドレスは必須ではないが、管理用にSSHでアクセスしたい場合はマネジメントIPを設定しておくとよい:

# bridge br0を作成
ip link add name br0 type bridge

# メンバーインターフェースをbridgeに追加
ip link set eth0 master br0
ip link set eth1 master br0

# すべてのインターフェースを起動
ip link set eth0 up
ip link set eth1 up
ip link set br0 up

# (オプション)ファイアウォールへのSSHが必要な場合、マネジメントIPを設定
ip addr add 192.168.10.254/24 dev br0

# ループのないトポロジではSTPを無効化(起動時の30秒遅延を回避)
ip link set br0 type bridge stp_state 0

再起動後も設定を維持するには、Netplan(Ubuntu 22.04+)で設定する:

# /etc/netplan/01-bridge.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
    eth1:
      dhcp4: false
  bridges:
    br0:
      interfaces: [eth0, eth1]
      addresses: [192.168.10.254/24]
      dhcp4: false
      parameters:
        stp: false
netplan apply

ステップ3:bridge netfilterのsysctlを有効化する

これは最もよく見落とされるステップだ。デフォルトではカーネルはbridgeトラフィックをnetfilterに渡さない。明示的に有効化しなければ、どんなルールを書いても意味がない:

cat > /etc/sysctl.d/99-bridge-firewall.conf << 'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-arptables = 1
net.ipv4.ip_forward = 1
EOF

sysctl --system

ステップ4:nftablesのルールセットを記述する

これがコアとなる部分だ。nftablesのbridgeファミリーはLayer 2(Ethernetフレーム)でフィルタリングし、ip/ip6ファミリーはLayer 3(bridgeを通過するIPパケット)でフィルタリングする:

cat > /etc/nftables.conf << 'EOF'
#!/usr/sbin/nft -f

flush ruleset

# Bridge table — Layer 2(Ethernetフレーム)でフィルタリング
table bridge filter {
    chain forward {
        type filter hook forward priority filter; policy accept;

        # 意図しないVLANタグをブロック(802.1Q)
        ether type 0x8100 counter drop comment "Block unexpected VLAN tags"

        # ARPスプーフィング検出のためにARPをログ
        ether type arp counter log prefix "ARP: "
    }
}

# IP table — Layer 3でフィルタリング
table ip filter {
    chain forward {
        type filter hook forward priority filter; policy drop;

        # 確立済み/関連接続を許可(最重要)
        ct state established,related accept
        ct state invalid drop

        # レートリミット付きでICMPを許可
        ip protocol icmp limit rate 10/second accept

        # 管理ネットワークからのSSHを許可
        ip saddr 192.168.10.0/24 tcp dport 22 accept

        # HTTP/HTTPSを許可
        tcp dport { 80, 443 } accept

        # DNSとNTPを許可
        udp dport { 53, 123 } accept
        tcp dport 53 accept

        # 残りすべてをログに記録してdrop
        counter log prefix "FW-DROP: " drop
    }
}

# IPv6 table
table ip6 filter {
    chain forward {
        type filter hook forward priority filter; policy drop;
        ct state established,related accept
        ct state invalid drop
        ip6 nexthdr icmpv6 accept
        counter log prefix "FW6-DROP: " drop
    }
}
EOF

# applyする前にsyntaxを確認
nft -c -f /etc/nftables.conf

# ルールセットをapply
nft -f /etc/nftables.conf

# 再起動時に自動ロードするためにサービスを有効化
systemctl enable --now nftables

# アクティブなルールセットを確認
nft list ruleset

ステップ5:グループ間のトラフィックを隔離する

これが透過型ファイアウォールの真の価値だ — VLANや他の変更なしに、同じサブネット内のマシン間のトラフィックを制御できる。例えば、appサーバーからDBサーバーへの接続だけを許可するケース:

# appサーバーのIPを格納するnamed setを作成
nft add set ip filter app_servers { \
  type ipv4_addr\; \
  elements = { 192.168.10.10, 192.168.10.11, 192.168.10.12 }\; \
}

# app_serversのみがDB(192.168.10.50)のポート3306に接続可能
nft add rule ip filter forward \
  ip daddr 192.168.10.50 tcp dport 3306 \
  ip saddr @app_servers accept

nft add rule ip filter forward \
  ip daddr 192.168.10.50 tcp dport 3306 drop

ステップ6:監視とデバッグ

デプロイ直後は、正当なトラフィックが誤ってブロックされていないかログを監視する。最初の1週間はpolicyをdropに切り替える前に、acceptとログで設定してトラフィックパターンを確認するようにしている:

# ファイアウォールのログをリアルタイムで確認
journalctl -f | grep "FW-DROP:"

# 各ルールのカウンターを確認(どのルールが多くマッチしているかを把握するため)
nft list ruleset | grep counter

# 特定期間のトラフィックを計測するためにカウンターをリセット
nft reset counters table ip filter

# ネットワーク内のマシンから疎通確認
ping -c 3 8.8.8.8
curl -I https://google.com
telnet 192.168.10.50 3306

異なるIPレンジのルールを書く際にサブネットをすばやく計算したいときは、toolcraft.app/ja/tools/developer/ip-subnet-calculatorをよく使っている — CIDRを入力するだけでネットワークレンジ、ブロードキャスト、ホスト数がすぐに表示される。複数のセグメントのルールを同時に分割する必要があり、手計算したくないときに非常に便利だ。

運用時の注意点

  • パフォーマンス:ブリッジファイアウォールはソフトウェアレイヤーで追加処理が発生する。通常の1Gbps回線では無視できるレベルだが、10Gbps以上の場合はhardware offloadまたはXDPの使用を検討すべきだ。
  • 単一障害点:透過型ファイアウォールはネットワーク全体のSPOFになる。本格的なproduction環境では、keepalived/VRRPとペアにしてHAを構成すべきだ — ファイアウォールが落ちてもネットワークが完全に断絶しないように。
  • VLANトランク:ネットワークがVLANトランク(802.1Q)を使用している場合、bridge上でVLANフィルタリングを有効化し、各VLANを個別に処理する必要がある。フラットネットワークと比べて大幅に複雑になる。
  • ログのボリューム:チェーンの末尾のログルールは、スキャンや攻撃があると大量のログを生成する可能性がある。ログにレートリミットを追加すべきだ:limit rate 5/minute log prefix "FW-DROP: "

nftablesを使った透過型ファイアウォールは、既存のトポロジを邪魔せずにセキュリティレイヤーを追加したいときに、私が最もクリーンだと思うソリューションだ。なぜゲートウェイを変える必要があるかをクライアントに説明する必要もなく、長いメンテナンスウィンドウも不要 — 挿入して、設定して、完了。後で取り除きたくなったときも、bridgeを外すだけでネットワークはすぐに元の状態に戻る。

Share: