クイックスタート(5分):今すぐできるVMポート分離
半年前、私たちのパブリックホスティングクラスタは深刻なセキュリティ課題に直面していました。300台を超える顧客VMが同一の /24 ブロードキャストドメイン内に同居していたため、悪意のあるテナントによる暗号化されていないARPクエリの盗聴、近隣インスタンスへのポートスキャン、さらにはスプーフィング攻撃のリスクに晒されていたのです。
しかし、ネットワークを細かくマイクロサブネットへ分割することは現実的ではありませんでした。セカンダリ市場でのパブリックIPv4アドレスは1個あたり35ドルを超えており、サブネットの細分化は貴重なネットワークアドレスやブロードキャストアドレスの浪費につながるためです。そこで最も迅速な解決策となったのが、Linux Bridgeにネイティブ実装されている isolated フラグの有効化でした。
Linux Bridgeのポート分離(Port Isolation)は、きわめてシンプルなルールを適用します。分離されたポート同士は相互にEthernetフレームを送信できず、物理ルータのインターフェースのような非分離の上位(アップリンク)ポートとの間でのみフレームの送受信が可能になります。
ブリッジ br0、物理アップリンク eth0、そして2つのゲストtapデバイス(vnet0 および vnet1)を備えたハイパーバイザを例にとりましょう。East-Westトラフィックは数秒で遮断できます。
# 1. 既存のブリッジポートの状態を確認
bridge link show
# 2. 両方のゲストVM用tapデバイスに分離フラグを設定
sudo bridge link set dev vnet0 isolated on
sudo bridge link set dev vnet1 isolated on
# 3. ゲートウェイのアップリンクが非分離のままであることを明示的に確認
sudo bridge link set dev eth0 isolated off
# 4. 詳細なリンク一覧で分離フラグが適用されたことを確認
bridge -d link show
これらのフラグを有効にすることで、vnet0 と vnet1 はLayer 2レベルでパケットを一切交換できなくなります。一方で、eth0 を経由するNorth-Southトラフィックは中断することなく正常に流れます。これは、マルチテナント環境のLinux仮想化を保護するための、最も効果的で軽量なテクニックの1つです。
詳細解説:Linux BridgeフィルタリングとPVLANの内部構造
48台のデュアルソケット構成ハイパーバイザでこのセットアップを運用する中で痛感したのは、パケットフローを正確に理解しておくことが、エッジケースでのパケットドロップ調査にかかる時間を大幅に削減してくれるという点でした。
カーネル内部におけるポート分離の仕組み
BR_ISOLATED フラグが設定されたポートにフレームが着信(Ingress)すると、カーネルはブリッジの転送データベース(FDB: Forwarding Database)を参照して宛先ポートを評価します。ターゲットポートにも同様に BR_ISOLATED フラグが設定されている場合、カーネルはパケットが送信キュー(Egress)に到達する前に、net/bridge/br_forward.c 内の br_forward_finish() で直ちにフレームを破棄します。
この破棄処理により、一般的なラテラルアタックの侵入経路が遮断されます。
- 直接的なポートスキャン: 侵害されたテナントVMからアグレッシブな
nmap -sS -p 1-65535を実行しても応答は一切得られず、稼働中の近隣ノードを検知できません。 - ARPポイズニング: 分離ポートから送信されたGratuitous ARPブロードキャストやポイズニングされたARPリプライは、他のVMポートへ到達することはありません。
- マルウェアの横展開(ラテラルムーブメント): Miraiなどのワームや内部サブネットをスキャンするランサムウェアは、Layer 2で即座に行き止まりとなります。
基本的な分離の拡張:プライベートVLAN(PVLAN)
基本的なポート分離では、分離されたすべてのポートが単一のフラットなグループにまとめられます。しかし、複雑なマルチティア環境では、ハードウェアのプライベートVLAN(RFC 5517)に倣った高度なセグメンテーションが必要となります。
- Promiscuous(無差別): すべてのポートと通信する必要があるゲートウェイルータ、ファイアウォール、集中syslogコレクターなど。
- Isolated(隔離): Promiscuousなゲートウェイとのみ通信を許可する、シングルテナントVMやDMZ内のVM。
- Community(コミュニティ): ロードバランシングされた3台のNginx Webノードと共有Redisキャッシュのように、グループ内での相互通信は必要だが、他のテナントからは不可視にしておきたいクラスタノード。
標準的なPVLANの挙動を実現するために、必ずしもOpen vSwitch(OVS)を導入する必要はありません。Linux BridgeのVLANフィルタリングとebtablesまたはnftablesのMACポリシーを組み合わせることで実現可能です。
# 既存のブリッジでVLANフィルタリングを有効化
sudo ip link set dev br0 type bridge vlan_filtering 1
# eth0のゲートウェイアップリンク:プライマリのPromiscuous VLAN 100
sudo bridge vlan add dev eth0 vid 100 pvid untagged
# vnet0のVM 1:セカンダリのIsolated VLAN 101
sudo bridge vlan add dev vnet0 vid 101 pvid untagged
# vnet1のVM 2:セカンダリのIsolated VLAN 102
sudo bridge vlan add dev vnet1 vid 102 pvid untagged
高度な活用法:本番環境での永続化とLibvirt連携
初期の検証段階で真っ先に直面した問題が、インターフェースの再作成でした。テナントがVMを再起動するたびに、QEMUとlibvirtがtapデバイスを一旦破棄してゼロから再作成するため、動的に設定した bridge link フラグがすべて消失してしまったのです。
Libvirtフックスクリプトによる分離の自動化
新しくプロビジョニングされたり再起動されたりしたtapデバイスへ即座に分離フラグを継承させるため、/etc/libvirt/hooks/qemu にlibvirtのQEMUフックスクリプトを配置します。
#!/bin/bash
# /etc/libvirt/hooks/qemu
OBJECT="$1"
OPERATION="$2"
SUB_OPERATION="$3"
if [ "$OPERATION" = "started" ] || [ "$OPERATION" = "attach" ]; then
# このドメインにアタッチされているすべての仮想tapインターフェースを列挙
for nic in $(virsh domiflist "$OBJECT" 2>/dev/null | awk '/vnet/ {print $1}'); do
# インターフェースがマルチテナント用ブリッジbr0に属しているか確認
BR=$(cat "/sys/class/net/$nic/brport/bridge/uevent" 2>/dev/null | grep 'INTERFACE=' | cut -d= -f2)
if [ "$BR" = "br0" ]; then
bridge link set dev "$nic" isolated on
logger -t libvirt-hook "ドメイン $OBJECT の $BR 上にある $nic にポート分離を適用しました"
fi
done
fi
sudo chmod +x /etc/libvirt/hooks/qemu で実行権限を付与し、libvirtd を再起動します。このフックは15ミリ秒未満で実行されるため、ゲストカーネルが起動して最初のDHCPリクエストを発行するよりもはるか前に確実に分離フラグが適用されます。
カーネルのHairpinモード制御によるARPフラッドの防止
分離されたVM同士が同一サブネット上の外付け境界ファイアウォール経由でのみ通信できるようにProxy ARPを構成する場合、ゲスト側ポートのHairpin(ヘアピン)モードが明示的に無効化されていることを確認してください。
# 反射的なブロードキャストループを排除するためHairpinモードを無効化
sudo bridge link set dev vnet0 hairpin off
sudo bridge link set dev vnet1 hairpin off
実践のヒント:本番運用6ヶ月で得られた知見
以下の4つの運用上の知見は、本番環境のメトリクス、インシデントレポート、およびテレメトリから直接得られたものです。
1. 不正DHCPサーバー対策という副次的なメリット
分離ポートは他のVM宛てのトラフィックを破棄するため、分離されたVMから送信されたDHCP DiscoverおよびRequestブロードキャストは物理ゲートウェイには届きますが、他のテナントには決して到達しません。悪意のある、あるいは設定を誤ったテナントがVM内で不正なDHCPサーバーを起動した場合でも、そのDHCP Offerパケットが近隣のゲストVMへ漏れることはありません。これにより、追加のebtablesフィルタや専用のDHCPスヌーピングデーモンを導入することなく、インフラ全体での偶発的なIPハイジャックを防止できました。
2. Sysfsを利用したスクリプトでの状態検査
ターミナル上で対話的にデバッグする際には bridge link show が便利ですが、独自のヘルスチェックやAnsibleプレイブックでは、sysfsからカーネルの状態を直接読み取るのが効率的です。
# 分離時は1、通常の転送時は0を返す
cat /sys/class/net/vnet0/brport/isolated
3. IPv6近隣探索(NDP)の落とし穴
ポート分離はゲスト間のすべてのLayer 2マルチキャストをブロックするため、IPv6の近隣要請(NS: Neighbor Solicitation)および近隣広告(NA: Neighbor Advertisement)が機能しなくなります。同一ブリッジ上で2台のVMが割り当てられた /64 グローバルユニキャストアドレスを使用して通信を試みても、NDPが即座に失敗します。これを解決するには、アップストリームのエッジルータでプレフィックス全体に対するデフォルトホップとして自身を広報するように設定するか、ホスト上で ndppd(Neighbor Discovery Protocol Proxy Daemon)を実行してください。
4. 極めて少ないCPUオーバーヘッド
ハイパーバイザあたり持続ピーク負荷14 Gbpsの環境下でも、パケット評価に伴うCPUオーバーヘッドは無視できるレベルでした。カーネルは、br_forward.c 内のポートフラグに対する単一のビット単位AND演算として分離チェックを実装しているためです。conntrackルールを適用したOpen vSwitchと比較して、激しいEast-Westトラフィック急増時でもコアあたりのCPU使用率が平均して4%低く抑えられました。

