4-5 Zone-Based Firewall — ゾーン設計とステートフルインスペクション
ACL のステートレスな限界を起点に、インターフェースをゾーンで束ねてステートフルに検査する ZBF を扱う。ゾーン間は default deny・self は既定 permit という既定挙動と、inspect が戻りを自動許可する非対称性を csr1000v の show 出力で検証する。
1. 前節の振り返りと本節の内容
前節 4-4 L2 セキュリティ では、L2 で成立する 3 つの攻撃を起点に、それを止める防御機能を扱いました。MAC フラッディング・なりすましには Port-Security が許可 MAC を制限し、Rogue DHCP には DHCP Snooping が trust 境界で偽のサーバ応答を破棄し、ARP スプーフィングには DAI (Dynamic ARP Inspection) がその binding table を入力に ARP を突合しました。
ここまでの 4-1 ACL から 4-4 L2 セキュリティまでは、個々の要素 を制御する仕組みでした。ACL はパケットの送信元・宛先アドレスやポート番号という単位で許可・拒否を 1 本ずつ書き、Port-Security は MAC の個数、DHCP Snooping は DHCP の応答方向、DAI は ARP の中身というように、それぞれが「ある 1 つの要素」を検査する防御でした。
本節で扱う ZBF (Zone-Based Firewall) は、防御の単位が変わります。ZBF は個々のインターフェースやアドレスではなく、インターフェースをゾーン (zone) という単位でまとめ、ゾーンとゾーンの間を流れる通信をまとめて検査します。ACL が「許可・拒否のルールを 1 本ずつ書く」のに対し、ZBF は「INSIDE から OUTSIDE への通信」というゾーン単位のポリシーで通信を表現します。さらに ZBF は ステートフル (stateful) に動作します。ACL がパケット 1 つずつを独立に判定する ステートレス (stateless) なのに対し、ZBF は通した通信を覚え、その戻りを自動で許可します。この「ステートレス ACL とステートフル ZBF」の対比が本節の背骨です。CML 上の csr1000v で、ゾーンを割り当てただけでは通らない既定 deny、zone-pair と policy-map type inspect による許可、戻りの自動許可と非要求の遮断、そして self ゾーンの扱いを、実機の show 出力で検証します。
2. なぜゾーンで考えるのか — ACL の限界とステートフルの動機
ZBF が解決する問題を理解するために、まず ACL の限界を 2 点で整理します。ZBF の設計は、この 2 つの限界への回答になっています。
第一の限界は、インターフェースと方向の組み合わせで管理が膨れ上がる ことです。ACL は「どのインターフェースの、どちらの方向 (in / out) に適用するか」を 1 つずつ指定します (4-1 ACL で扱いました)。ルータのインターフェースが 2 つなら方向を含めて 4 通りですが、インターフェースが増えるとこの組み合わせが増え、どのインターフェースにどの ACL が当たっているかを追うのが難しくなります。ZBF は、複数のインターフェースを 1 つのゾーンにまとめ、「ゾーンからゾーンへ」という単位でポリシーを書くことで、この組み合わせの爆発を抑えます。同じセキュリティレベルのインターフェースを 1 つのゾーンに束ねれば、インターフェースが何本あってもゾーン間のポリシーは変わりません。
第二の限界は、戻りトラフィックの穴を手で開ける必要がある ことです。ステートレスな ACL は、パケット 1 つずつを独立に判定します。内部から外部へ出す通信を許可しても、その通信に対する外部からの応答 (戻り) は別のパケットとして扱われるため、応答を通す許可を別途書かなければなりません。この戻りを許可するために、ACL では established キーワード (TCP の応答フラグを見て通す) や reflexive ACL (出た通信を記憶して戻りを一時許可する) を使いますが、いずれも設定が煩雑で、TCP 以外のプロトコルでは扱いにくいという弱点があります。
ステートフルインスペクション (stateful inspection) は、この第二の限界を根本から解きます。ステートフルなファイアウォールは、内部から外部へ通した通信を セッション (session) として記憶し、その通信に対応する戻りだけを自動で許可します。管理者が戻りの許可を書く必要はなく、ファイアウォールが「これは先ほど通した通信の戻りだ」と判断して通します。ZBF の inspect アクションがこのステートフル検査を担い、established や reflexive ACL を書かずに戻り穴が自動で開くようにします。以降、この仕組みを構成要素から順に見ていきます。
3. ZBF の構成要素 — zone / zone-member / zone-pair / policy-map type inspect
ZBF は 4 つの構成要素を組み合わせて通信ポリシーを表現します。それぞれの役割を整理します。
- ゾーン (zone) — セキュリティレベルが同じインターフェースを束ねる論理的なグループ。
zone security <名前>で定義します。「信頼できる内部 (INSIDE)」「信頼できない外部 (OUTSIDE)」のように、通信ポリシー上で同じ扱いをしたいインターフェースを 1 つのゾーンにまとめます。 - ゾーンメンバー (zone-member) — インターフェースをゾーンに所属させる設定。インターフェース側で
zone-member security <ゾーン名>を投入し、そのインターフェースがどのゾーンに属するかを決めます。 - ゾーンペア (zone-pair) — 通信の向き を定義する器。
zone-pair security <名前> source <送信元ゾーン> destination <宛先ゾーン>で「どのゾーンから、どのゾーンへ」を指定します。ゾーンペアは向きを持つため、INSIDE から OUTSIDE への通信と、OUTSIDE から INSIDE への通信は別々のゾーンペアになります。 - 検査ポリシー (policy-map type inspect) — ゾーンペアを流れる通信に対して「何を、どう扱うか」を定義するポリシー。どの通信を対象にするかを
class-map type inspectで分類し、その分類に対してinspect(ステートフル検査して通す) /pass(ステートレスに通す) /drop(破棄) のいずれかのアクションをpolicy-map type inspectで指定します。
ここで、ZBF の検査ポリシーが MPF (Modular Policy Framework) の枠組みに従う点が重要です。MPF は「class-map で分類し、policy-map でアクションを決め、それをどこかに適用する」という Cisco IOS の共通の設定パターンで、QoS などでも使われます。ZBF はこの MPF の type inspect 版 であり、class-map type inspect で通信を分類し、policy-map type inspect でアクションを決め、それを zone-pair に service-policy で適用します。ACL のように 1 本の許可・拒否行を並べるのではなく、「分類 → アクション → 適用先」という 3 段の構造でポリシーを組み立てるのが ZBF の書き方です。
class-map type inspect では、対象とする通信を プロトコル名 で分類できます。match protocol icmp や match protocol tcp のように、アプリケーション層のプロトコルを指定して分類する点が、アドレスとポート番号で分類する ACL との違いです。ZBF はプロトコルを認識して検査するため、そのプロトコルが戻りに使うポートを自動で開けられます。
4. ZBF の既定動作 — ゾーン間は default deny、self は既定 permit
ZBF を扱う上で最も重要なのが、ゾーンを定義した瞬間に変わる 既定の通信可否 です。これを正確に理解していないと、ゾーンを割り当てただけで通信が止まる理由が分からなくなります。既定動作を 4 点で整理します。
第一に、異なるゾーン間は、ゾーンペアが無ければすべて deny です。インターフェースをゾーンに割り当てると、そのゾーンと別のゾーンの間の通信は、対応するゾーンペアが存在しない限りすべて破棄されます。ゾーンペアは「明示的に許可を書く器」であり、ゾーンペアを作って初めてその向きの通信が検査対象になります。ゾーンペアが無い向きは暗黙的に deny されるため、ZBF は「デフォルト拒否 (default deny)」のファイアウォールです。この挙動を §6 の実機で確認します。
第二に、同一ゾーン内のインターフェース間は暗黙的に permit です。同じゾーンに属するインターフェース同士の通信は、ゾーンペアを作らなくても通ります。同じセキュリティレベルの中での通信は自由、という設計です。
第三に、ゾーンに割り当てられたインターフェースと、ゾーンに割り当てられていないインターフェースの間の通信は通れません。片方だけがゾーンに属している場合、その間の通信を許可するゾーンペアを作れないため、通信は成立しません。管理用インターフェースをゾーンに入れない設計にする場合は、この点に注意が必要です (§11 で補足します)。
第四に、self ゾーン (ルータ自身) は既定で permit です。ZBF には、システムが最初から用意している特別なゾーンとして self ゾーン があります。self ゾーンはルータ自身を宛先または送信元とする通信 (ルータへの SSH・SNMP、ルータが送る OSPF など) を表します。self ゾーンとほかのゾーンの間の通信は、ゾーンペアを作らない限り既定で許可 されます。インターフェースをゾーンに割り当てても、そのインターフェースからルータ自身への通信 (self 宛) は、self へのゾーンペアが無い限り通ります。ここが誤解しやすい点で、「インターフェースをゾーンに入れると self 宛も deny になる」というのは誤りです。self ゾーンへのゾーンペアを作った瞬間に、初めてその向きが検査ポリシーの支配下に入ります。この挙動を §10 の実機で確認します。
以上をまとめると、ゾーンをまたぐ通信は原則 deny で、ゾーンペアを作った向きだけが通り、self だけは例外的に既定 permit、という 4 つの規則で ZBF の既定動作が決まります。
5. ラボトポロジ
本節の検証で使うトポロジを示します。csr1000v 3 台を直列につなぎ、中央の R2 に ZBF を適用します。
トポロジの構成は以下のとおりです。
- R1 (INSIDE 側の内部ホスト) — Loopback10 に
10.0.10.1、R2 への Gi2 に10.12.0.1を持ちます。信頼側の送信元として使います。 - R2 (ZBF firewall) — R1 への Gi2 に
10.12.0.2、R3 への Gi3 に10.23.0.1を持ちます。Gi2 を INSIDE ゾーン、Gi3 を OUTSIDE ゾーンに割り当て、ルータ自身が self ゾーンになります。ここに ZBF のポリシーを構築します。 - R3 (OUTSIDE 側の外部サーバ) — Loopback20 に
10.0.20.1、R2 への Gi2 に10.23.0.2を持ちます。非信頼側の宛先として使います。
3 台は OSPF area 0 で全 Loopback とリンクを広告しており、ZBF を適用する前は双方向に到達できます。R1 が OSPF で学習した経路を確認します。
show ip route ospf
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
E1 - OSPF external type 1, E2 - OSPF external type 2, m - OMP
n - NAT, Ni - NAT inside, No - NAT outside, Nd - NAT DIA
i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
ia - IS-IS inter area, * - candidate default, U - per-user static route
H - NHRP, G - NHRP registered, g - NHRP registration summary
o - ODR, P - periodic downloaded static route, l - LISP
a - application route
+ - replicated route, % - next hop override, p - overrides from PfR
& - replicated local route overrides by connected
Gateway of last resort is not set
10.0.0.0/8 is variably subnetted, 9 subnets, 3 masks
O 10.0.2.1/32 [110/2] via 10.12.0.2, 00:02:40, GigabitEthernet2
O 10.0.3.1/32 [110/3] via 10.12.0.2, 00:02:40, GigabitEthernet2
O 10.0.20.1/32 [110/3] via 10.12.0.2, 00:02:40, GigabitEthernet2
O 10.23.0.0/30 [110/2] via 10.12.0.2, 00:02:40, GigabitEthernet2
R1#R1 は外部サーバ R3 の Loopback 10.0.20.1/32 を、R2 の 10.12.0.2 経由で学習しています。経路が届いているため、この時点では R1 から R3 へ通信できます。ZBF を適用する前のベースラインとして、R1 (INSIDE) から R3 (OUTSIDE) の 10.0.20.1 へ ping します。
ping 10.0.20.1 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.20.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms
R1#逆向きに、R3 (OUTSIDE) から R1 (INSIDE) の 10.0.10.1 へも ping します。
ping 10.0.10.1 source Loopback20 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.10.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.20.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/2 ms
R3#どちらの向きも Success rate is 100 percent (5/5) で、ZBF を適用する前は inside↔outside が双方向に疎通しています。この双方向到達が、ゾーンを割り当てた後にどう変わるかを §6 以降で追います。
6. ゾーンを定義しインターフェースに割り当てる — まだ通らない
R2 でゾーンを 2 つ定義し、インターフェースを割り当てます。INSIDE ゾーンに Gi2 (R1 方向)、OUTSIDE ゾーンに Gi3 (R3 方向) を所属させます。この時点ではゾーンペアをまだ作りません。ゾーンを割り当てただけの状態から、R1 (INSIDE) から R3 (OUTSIDE) へ ping します。
ping 10.0.20.1 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.20.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
.....
Success rate is 0 percent (0/5)
R1#§5 では 100 percent だった同じ ping が、Success rate is 0 percent (0/5) に変わりました。ゾーンペアを 1 つも作っていないため、INSIDE と OUTSIDE の間の通信が §4 の第一の規則 (異なるゾーン間はゾーンペアが無ければ deny) によってすべて破棄されています。OSPF の経路は §5 のとおり届いているにもかかわらず、ZBF がゾーン境界で ICMP を落としています。ゾーンにインターフェースを入れた瞬間、ゾーン間は塞がる ことがこの結果に表れています。
ゾーンの状態を show zone security で確認します。
show zone security
zone self 65535
Description: System defined zone
zone service 65534
Description: System defined zone
zone INSIDE 1
Description: trusted internal zone
Member Interfaces:
GigabitEthernet2
zone OUTSIDE 2
Description: untrusted external zone
Member Interfaces:
GigabitEthernet3
R2#上部の zone self 65535 と zone service 65534 は、いずれも System defined zone で、システムが最初から用意している既定のゾーンです。self ゾーンがルータ自身を表すゾーンで、§4 の第四の規則および §10 で扱います。その下の zone INSIDE 1 に Member Interfaces として GigabitEthernet2、zone OUTSIDE 2 に GigabitEthernet3 が所属しており、管理者が定義した 2 つのゾーンにインターフェースが割り当てられています。ゾーンとメンバーは設定されているものの、ゾーン間を通すゾーンペアがまだ無いため、§4 の既定 deny が働いて ping が落ちた、という状態です。
7. zone-pair と policy-map type inspect — inside 発を検査して通す
INSIDE から OUTSIDE への通信を許可します。§3 の 3 段構造 (分類 → アクション → 適用先) に従い、class-map で通信を分類し、policy-map で inspect アクションを指定し、それを INSIDE→OUTSIDE のゾーンペアに適用します。R2 に投入する設定は以下のとおりです。
class-map type inspect match-any CM-IN-OUT
match protocol icmp
match protocol tcp
policy-map type inspect PM-IN-OUT
class type inspect CM-IN-OUT
inspect
class class-default
drop
zone-pair security IN-OUT source INSIDE destination OUTSIDE
service-policy type inspect PM-IN-OUT設定の意味を段に分けて整理します。class-map type inspect match-any CM-IN-OUT は、ICMP または TCP を対象とする分類です。match-any なので、ICMP か TCP のいずれかに一致すればこの class-map にヒットします。policy-map type inspect PM-IN-OUT は、その CM-IN-OUT に一致した通信を inspect (ステートフル検査して通す) し、それ以外のすべて (class-default) を drop します。最後の zone-pair security IN-OUT source INSIDE destination OUTSIDE が INSIDE から OUTSIDE への向きを定義するゾーンペアで、そこに service-policy type inspect PM-IN-OUT でポリシーを適用しています。
この設定を投入した後、R1 (INSIDE) から R3 (OUTSIDE) へ ping します。
ping 10.0.20.1 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.20.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/8 ms
R1#§6 で 0 percent だった ping が、Success rate is 100 percent (5/5) に戻りました。IN-OUT ゾーンペアの inspect が ICMP を検査して通し、その戻り (R3 からの echo reply) も自動で許可されたため、往復が成立しています。ここで注目すべきは、R3 から R1 への戻りを許可するゾーンペア (OUTSIDE→INSIDE) を作っていない にもかかわらず、戻りが通っている点です。これがステートフルインスペクションの効果で、§8・§9 で詳しく確認します。
TCP も同じゾーンペアで検査されます。R1 から R3 の TCP ポート 80 へ telnet して、TCP セッションが確立するかを確認します。
telnet 10.0.20.1 80 /source-interface Loopback10
Trying 10.0.20.1, 80 ... OpenTrying 10.0.20.1, 80 ... Open の Open は、TCP の 3 ウェイハンドシェイクが成立し、ポート 80 へのコネクションが確立したことを示します。ICMP と同様に、IN-OUT ゾーンペアの inspect が TCP を検査して通し、R3 からの戻り (SYN-ACK 以降) を自動で許可した結果、コネクションが Open になっています。ステートフル検査が ICMP だけでなく TCP でも働き、戻りを自動許可していることが確認できます。
8. ステートフルの証跡 — 検査統計とセッションカウンタ
inspect が「通した通信を覚えている」ことを、R2 の検査統計で確認します。まず、IN-OUT ゾーンペアに適用したポリシーの構造を show policy-map type inspect zone-pair IN-OUT sessions で確認します。
show policy-map type inspect zone-pair IN-OUT sessions
Zone-pair: IN-OUT
Service-policy inspect : PM-IN-OUT
Class-map: CM-IN-OUT (match-any)
Match: protocol icmp
Match: protocol tcp
Inspect
Class-map: class-default (match-any)
Match: any
Drop
0 packets, 0 bytes
R2#IN-OUT ゾーンペアに PM-IN-OUT が適用され、CM-IN-OUT が ICMP と TCP を Inspect、class-default が残りを Drop する構造が表示されています。この出力は、§7 で投入したポリシーが正しくゾーンペアに適用されていることを確認するものです。個々のセッション行は、キャプチャ時点でセッションが短命に消えていたため表示されていません。
セッションの生存を示す証跡は、カウンタ付きの show policy-map type inspect zone-pair IN-OUT で確認できます。
show policy-map type inspect zone-pair IN-OUT
Zone-pair: IN-OUT
Service-policy inspect : PM-IN-OUT
Class-map: CM-IN-OUT (match-any)
Match: protocol icmp
Match: protocol tcp
Inspect
Packet inspection statistics [process switch:fast switch]
tcp packets: [0:22]
icmp packets: [0:20]
Session creations since subsystem startup or last reset 4
Current session counts (estab/half-open/terminating) [1:0:0]
Maxever session counts (estab/half-open/terminating) [2:0:0]
Last session created 00:00:09
Last statistic reset never
Last session creation rate 1
Last half-open session total 0
Class-map: class-default (match-any)
Match: any
Drop
0 packets, 0 bytes
R2#この出力がステートフル動作の中心的な証跡です。読み取れる点を整理します。
tcp packets: [0:22]/icmp packets: [0:20]—inspectが実際に検査した TCP・ICMP のパケット統計です。§7 の ping と telnet で流れた通信が検査対象になっています。Session creations since subsystem startup or last reset 4— ZBF が起動以降に作成したセッションが累計 4 つあることを示します。inspectが通信ごとにセッションを作り、その通信の戻りを許可するための記憶を持ったことを表します。Current session counts (estab/half-open/terminating) [1:0:0]— この瞬間に確立中 (estab) のセッションが 1 つあることを示します。inspectが「今この通信を追跡中で、戻りが来たら通す」という状態を保持しています。この確立中セッションの存在こそが、ステートフルの実体です。class-default: Drop 0 packets, 0 bytes— CM-IN-OUT に一致しなかった通信を破棄するカウンタですが、0 のままです。INSIDE から OUTSIDE への通信は ICMP と TCP しか流していないため、class-default に落ちる通信が無かったことを示します。
ステートフルインスペクションの全体像を、時間を追って整理します。
図の各ステップは、①がゾーンを割り当てただけで通らない §6、②③がゾーンペアと inspect で通る §7・§8、④が戻りの自動許可、⑤が非要求の遮断に対応します。④⑤の非対称を §9 で実機の drop として確認します。
9. 戻りは自動、非要求は遮断 — ステートフルの非対称性
ステートフルインスペクションの本質は、inside 発の戻りは通るのに、outside 発の新規通信は入れない という非対称にあります。これを実機で確認します。
まず、対照として R1 (INSIDE) から R3 (OUTSIDE) への ping がまだ通ることを確認します。
ping 10.0.20.1 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.20.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/2 ms
R1#Success rate is 100 percent (5/5) で、inside 発の通信は §7 と同じく通ります。IN-OUT ゾーンペアの inspect が inside 発を検査して通し、その戻りを自動許可しているためです。
次に、逆向きに R3 (OUTSIDE) から R1 (INSIDE) の 10.0.10.1 へ、outside 発の新規 ping を送ります。
ping 10.0.10.1 source Loopback20 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.10.1, timeout is 2 seconds:
Packet sent with a source address of 10.0.20.1
.....
Success rate is 0 percent (0/5)
R3#同じ R3→R1 の ping が、§5 のベースラインでは 100 percent だったのに、Success rate is 0 percent (0/5) に変わりました。inside 発は通り (100 percent)、outside 発は落ちる (0 percent) という非対称がここに表れています。
ここで、outside 発が落ちる 原因を正確に理解する ことが重要です。この drop は、IN-OUT ゾーンペアの class-default が破棄したものでは ありません。§8 の出力で、IN-OUT の class-default は Drop 0 packets, 0 bytes のままでした。outside 発の通信は IN-OUT ゾーンペア (INSIDE→OUTSIDE の向き) には流れないため、そもそも IN-OUT のポリシーで判定されていません。
outside 発が落ちる本当の原因は、OUTSIDE→INSIDE のゾーンペアが存在しない ことです。§4 の第一の規則のとおり、ゾーン間の通信はゾーンペアが無ければ既定 deny です。R3 (OUTSIDE) から R1 (INSIDE) への新規通信は OUTSIDE→INSIDE の向きであり、この向きのゾーンペアを作っていないため、class-default 以前に「ゾーンペア不在の暗黙 deny」で破棄されています。つまり、outside 発が落ちる主体は class-default の drop アクションではなく、ゾーンペアそのものの不在 です。
一方で、inside 発に対する R3 からの 戻り (echo reply) は、なぜ通るのでしょうか。戻りは新規通信ではなく、§8 でセッションテーブルに記録された「inside 発 ICMP の戻り」に一致するため、inspect がステートフルに自動許可します。同じ「outside から inside への向き」でも、セッションに一致する戻りは自動許可され、セッションに無い新規通信は既定 deny で落ちる、という区別が働いています。
この非対称こそが、4-1 ACL との決定的な違いです。ステートレスな ACL では、inside 発の戻りを通すために established や reflexive ACL で戻り穴を手で開ける必要がありました (§2)。ZBF の inspect は、通した通信のセッションを記憶し、その戻りだけを自動で開けます。戻り穴を書かずに戻りが通り、書いていない新規通信は自動的に塞がる、というのがステートフルファイアウォールが守る境界の形です。
10. self ゾーン — ルータ自身への通信を制御する
ここまでは INSIDE と OUTSIDE という 2 つのゾーンの間の通信を扱いました。ここでは、ルータ自身を表す self ゾーン への通信を制御します。self ゾーンは、ルータへの SSH・SNMP のような管理アクセスや、ルータ自身が終端する通信を表す特別なゾーンです。
まず、§4 の第四の規則 (self ゾーンは既定 permit) を実機で確認します。§7 の時点で INSIDE→SELF のゾーンペアはまだ作っていません。この状態で、R1 (INSIDE) から R2 自身の Gi2 アドレス 10.12.0.2 へ ping します。
ping 10.12.0.2 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.12.0.2, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms
R1#Success rate is 100 percent (5/5) で、INSIDE→SELF のゾーンペアが無い状態でも、R1 からルータ自身への ping は通っています。Gi2 は INSIDE ゾーンに割り当て済みですが、そのインターフェースからルータ自身 (self) への通信は、self へのゾーンペアが無い限り既定 permit で通る、という §4 の規則が確認できます。「インターフェースをゾーンに入れると self 宛も deny になる」という誤解が、この実機結果で否定されます。
次に、self ゾーンへのゾーンペアを作ります。INSIDE から self への ICMP だけを許可する設定を R2 に投入します。
class-map type inspect match-any CM-IN-SELF
match protocol icmp
policy-map type inspect PM-IN-SELF
class type inspect CM-IN-SELF
inspect
class class-default
drop
zone-pair security IN-SELF source INSIDE destination self
service-policy type inspect PM-IN-SELFINSIDE→self のゾーンペア IN-SELF を作り、ICMP を inspect し、それ以外を drop するポリシーを適用しています。この設定を投入した後、再び R1 から R2 自身の 10.12.0.2 へ ping します。
ping 10.12.0.2 source Loopback10 repeat 5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.12.0.2, timeout is 2 seconds:
Packet sent with a source address of 10.0.10.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms
R1#ICMP を inspect しているため、ping は Success rate is 100 percent (5/5) で通ります。ここまでは前と同じ結果ですが、ゾーンペアを作ったことで挙動が変わった部分は、次の検査統計に表れます。
作成したゾーンペアが 2 つあることを show zone-pair security で確認します。
show zone-pair security
Zone-pair name IN-OUT 1
Source-Zone INSIDE Destination-Zone OUTSIDE
service-policy PM-IN-OUT
Zone-pair name IN-SELF 2
Source-Zone INSIDE Destination-Zone self
service-policy PM-IN-SELF
R2#IN-OUT (INSIDE→OUTSIDE) と IN-SELF (INSIDE→self) の 2 つのゾーンペアが表示されています。Destination-Zone self が、宛先がルータ自身であることを示します。
IN-SELF ゾーンペアの検査統計を show policy-map type inspect zone-pair IN-SELF で確認します。ここに self ゾーン制御の核心が現れます。
show policy-map type inspect zone-pair IN-SELF
Zone-pair: IN-SELF
Service-policy inspect : PM-IN-SELF
Class-map: CM-IN-SELF (match-any)
Match: protocol icmp
Inspect
Packet inspection statistics [process switch:fast switch]
icmp packets: [0:10]
Session creations since subsystem startup or last reset 1
Current session counts (estab/half-open/terminating) [0:0:0]
Maxever session counts (estab/half-open/terminating) [1:0:0]
Last session created 00:00:19
Last statistic reset never
Last session creation rate 1
Last half-open session total 0
Class-map: class-default (match-any)
Match: any
Drop
5 packets, 460 bytes
R2#読み取るべき点は 2 つです。第一に、CM-IN-SELF の icmp packets: [0:10] と Session creations ... 1 で、inspect した ICMP (先ほどの ping) がセッションを作って通ったことが分かります。第二に、注目すべきは class-default: Drop 5 packets, 460 bytes です。これは、ICMP 以外の self 宛通信が 5 パケット破棄された実績を示します。
この class-default の drop が、self ゾーン制御の 諸刃の性質 を表しています。INSIDE→self のゾーンペアを作った瞬間、INSIDE ゾーンからルータ自身への通信は、そのゾーンペアのポリシー支配下に入ります。inspect した ICMP は通りますが、class-map に書かれていないほかのプロトコル (SSH・SNMP など) は class-default に落ちて drop されます。§4 では self 宛は既定 permit でしたが、INSIDE→self のゾーンペアを作った瞬間に、その向きは default deny (class-default drop) に切り替わる のです。self ゾーンへのゾーンペアは「ルータ自身を守る」ために使いますが、同時に「許可を明示しない管理プロトコルを意図せず塞ぐ」ことになります。管理アクセスを維持したい場合は、class-map に SSH などのプロトコルを明示的に追加する必要があります。
なお、本ラボでは R2 への管理 SSH は Mgmt-vrf の管理インターフェース (ゾーン未所属) を経由しているため、この IN-SELF ゾーンペアの影響を受けず、ロックアウトは起きていません。ゾーンに割り当てたデータ用インターフェース経由の管理アクセスを ZBF で制御する場合は、この諸刃の性質に特に注意します。
11. 落とし穴・補足
本節で扱った ZBF の実機挙動と、設計上の注意点を整理します。
inspectとpassの違い —policy-map type inspectで指定できる通過アクションにはinspectとpassの 2 つがあります。inspectはステートフルに検査し、通した通信の戻りを自動で許可します (§8・§9)。これに対しpassはステートレスに通すだけで、戻りは許可しません。passを使った向きの戻りを通すには、逆向きのゾーンペアを別途作る必要があります。戻りの自動許可が欲しい通常の通信にはinspectを使い、戻りを持たない一方向の通信 (一部の UDP など) や、明示的に両方向を制御したい場合にpassを使い分けます。dropは既定なので明示不要だが log で可視化できる — ゾーンペアの class-default は既定でdropされるため、拒否そのものを書く必要はありません (§7)。ただし、破棄された通信を記録したい場合はdrop logとすることで、drop を syslog に出力して可視化できます。既定 deny は静かに落とすため、意図せぬ通信断のトラブルシューティングでは log 付き drop が役立ちます。ゾーン未所属インターフェースとの通信は成立しない — §4 の第三の規則のとおり、ゾーンに割り当てたインターフェースと、ゾーンに割り当てていないインターフェースの間の通信は、ゾーンペアを作れないため通りません。管理用インターフェースをゾーンに入れない設計にすると、そのインターフェースからゾーン所属インターフェースへの通信が塞がれます。管理系を独立させたい場合は、Mgmt-vrf の管理インターフェース (§10 の R2 がこの構成) を使い、データプレーンのゾーンと分離するのが定石です。
outside 発の drop 主体はゾーンペア不在 — §9 で確認したとおり、outside 発の新規通信が落ちる主体は、IN-OUT ゾーンペアの class-default ではなく、OUTSIDE→INSIDE のゾーンペアそのものの不在 です。class-default の drop カウンタが 0 のままでも、ゾーン間の通信は既定 deny で落ちます。ZBF のトラブルシューティングでは、「どのゾーンペアが存在し、どのゾーンペアが存在しないか」を先に確認することが、通信可否を判断する起点になります。
self ゾーンペアは管理アクセスを巻き込む — §10 で確認したとおり、INSIDE→self のゾーンペアを作ると、その向きの self 宛通信は class-default で default deny になります。ICMP だけを許可すると SSH・SNMP が落ちるため (class-default drop 5 packets, 460 bytes)、管理アクセスを維持するには class-map に管理プロトコルを明示追加します。self ゾーンの制御は「ルータを守る」利点と「管理を塞ぐ」リスクが表裏の関係にあります。
CBAC は ZBF の前身 — ZBF の前に使われていたステートフル検査の方式が CBAC (Context-Based Access Control) で、
ip inspectコマンドでインターフェース単位に検査を設定するものでした。CBAC はインターフェースごとに検査ルールを当てるため、ゾーンという抽象がなく、インターフェースが増えると管理が煩雑でした。ZBF は CBAC の後継であり、ゾーンという単位で検査ポリシーを整理できる現行の推奨方式です。新規に IOS ルータでステートフルファイアウォールを構成する場合は ZBF を使います。
12. ACL / CBAC / ZBF の位置づけ
本章で扱ったステートレスな ACL、旧方式の CBAC、本節の ZBF を対比して、それぞれの位置づけを整理します。
| 項目 | ACL | CBAC (ip inspect) | ZBF (Zone-Based Firewall) |
|---|---|---|---|
| 検査の性質 | ステートレス | ステートフル | ステートフル |
| 戻りトラフィック | established / reflexive ACL で手動許可 | セッションを記憶し自動許可 | inspect がセッションを記憶し自動許可 |
| 適用の単位 | インターフェース + 方向 (in / out) | インターフェース単位 | ゾーン単位 (zone-pair の向き) |
| 分類の基準 | アドレス・ポート番号 | プロトコル (アプリケーション認識) | プロトコル (class-map type inspect) |
| 既定動作 | 末尾の暗黙 deny | 検査ルールに従う | ゾーン間は default deny・self は既定 permit |
| 設定構造 | 許可・拒否行の列挙 | ip inspect ルール + ACL | MPF (class-map → policy-map → zone-pair) |
| 位置づけ | L3/L4 の基本フィルタ | 旧方式 (ZBF に置き換え) | 現行の推奨方式 |
ACL は L3/L4 のアドレスとポート番号で許可・拒否を書く基本のフィルタで、パケット単位のステートレスな判定を行います。戻りを通すには手で穴を開ける必要があり、この手間がステートフル方式を生みました。CBAC はインターフェース単位でステートフル検査を導入した旧方式で、ZBF に置き換えられています。ZBF は、複数のインターフェースをゾーンにまとめ、ゾーン間の向き (zone-pair) ごとに MPF でポリシーを組み、inspect でステートフルに戻りを自動許可する、現行の推奨方式です。ステートレスな ACL とステートフルな ZBF は排他ではなく、ACL でアドレス・ポート単位の粗いフィルタを、ZBF でゾーン単位のステートフル制御を、というように役割を分けて併用します。
13. 次節
本節では、インターフェースをゾーンにまとめてステートフルに検査する ZBF を扱いました。ゾーンにインターフェースを割り当てるとゾーン間は default deny になり (§6)、zone-pair と policy-map type inspect でその向きを開き (§7)、inspect が通した通信をセッションとして記憶することで戻りが自動許可される (§8) こと、inside 発は戻れて outside 発の新規は入れない非対称 (§9)、そして self ゾーンは既定 permit だが INSIDE→self のゾーンペアを作った瞬間に管理アクセスを巻き込む諸刃の性質 (§10) を、csr1000v の show 出力で確認しました。とりわけ、outside 発が落ちる主体は class-default の drop ではなく ゾーンペアそのものの不在 である点と、inspect が 4-1 ACL の established / reflexive ACL を書かずに戻り穴を自動で開ける点が、ステートレス ACL とステートフル ZBF を分ける勘所です。
参考として、本節の検証で R2 に構築した ZBF 構成の全体を示します。
show running-config | section zone|class-map|policy-map|zone-pair
class-map type inspect match-any CM-IN-SELF
match protocol icmp
class-map type inspect match-any CM-IN-OUT
match protocol icmp
match protocol tcp
policy-map type inspect PM-IN-SELF
class type inspect CM-IN-SELF
inspect
class class-default
drop
policy-map type inspect PM-IN-OUT
class type inspect CM-IN-OUT
inspect
class class-default
drop
zone security INSIDE
zone security OUTSIDE
zone-pair security IN-OUT source INSIDE destination OUTSIDE
service-policy type inspect PM-IN-OUT
zone-pair security IN-SELF source INSIDE destination self
service-policy type inspect PM-IN-SELF
description to R1 (INSIDE zone member)
zone-member security INSIDE
description to R3 (OUTSIDE zone member)
zone-member security OUTSIDE
R2#class-map 2 つ・policy-map 2 つ・ゾーン 2 つ・ゾーンペア 2 つと、末尾のインターフェースの zone-member security 行で、本節の ZBF が構成されています (この出力は | section で該当行を抽出したもので、末尾の description と zone-member 行がそれぞれ Gi2・Gi3 の所属を表します)。
次節 4-6 NAT/PAT では、プライベートアドレスとグローバルアドレスを変換する NAT (Network Address Translation) と PAT (Port Address Translation) を扱います。NAT でも、本節で扱った inside と outside という向きの概念 が中心になります。ZBF がゾーン間の通信可否を向きで制御したのと同じように、NAT は inside から outside へ出る通信でアドレスをどう変換するかを向きで定義します。ステートフルに通信を追跡する点も NAT と ZBF に共通しており、両者がどう連携するかを次節で見ていきます。
established キーワードとか reflexive ACL でやるんだが、煩雑だし TCP 以外だと扱いづらい。inspect アクションがこれを担うんだ。今日はこの「ステートレス ACL とステートフル ZBF」の対比が背骨になる。zone security <名前>)── 同じセキュリティレベルのインターフェースを束ねる論理グループ。② ゾーンメンバー(インターフェース側で zone-member security <ゾーン名>)── そのインターフェースをどのゾーンに入れるか。③ ゾーンペア(zone-pair security <名前> source <送信元ゾーン> destination <宛先ゾーン>)── 通信の向きを定義する器。④ 検査ポリシー(policy-map type inspect)── そのゾーンペアを流れる通信を「どう扱うか」を決める。class-map type inspect で通信を分類し、policy-map type inspect でアクションを決め、それを zone-pair に service-policy で適用する ── 「分類 → アクション → 適用先」の 3 段構えだ。ACL みたいに許可・拒否行を並べるんじゃなくてな。分類は match protocol icmp みたいにプロトコル名(アプリ認識)でできるのが ZBF の特徴だ。ちなみに match access-group で ACL を参照して送信元/宛先 IP・L4 ポートで絞ることもできる ── プロトコル名だけしか使えないわけじゃない。ただ ZBF の主役はプロトコルを認識して戻りを自動で開ける点で、そこがアドレス・ポートだけで判定する ACL との違いだ。Gi2 を INSIDE、Gi3 を OUTSIDE に割り当てる。ゾーンペアはまだ作らない。この状態で R1 から R3 へ ping すると……
ping 10.0.20.1 source Loopback10 repeat 5
Sending 5, 100-byte ICMP Echos to 10.0.20.1, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)さっき 100% だった同じ ping が 0% に落ちた。ゾーンペアを 1 つも作ってないから、さっきの規則①(異なるゾーン間はゾーンペアが無ければ deny)で全部破棄されてる。OSPF の経路は届いてるのにな。ゾーンにインターフェースを入れた瞬間、ゾーン間は塞がるんだ。
さっきの 3 段構えだ。INSIDE→OUTSIDE を許可する。
class-map type inspect match-any CM-IN-OUT
match protocol icmp
match protocol tcp
policy-map type inspect PM-IN-OUT
class type inspect CM-IN-OUT
inspect
class class-default
drop
zone-pair security IN-OUT source INSIDE destination OUTSIDE
service-policy type inspect PM-IN-OUTclass-map で ICMP か TCP を分類し、policy-map でそれを inspect(ステートフル検査して通す)、それ以外(class-default)は drop。最後の zone-pair が INSIDE→OUTSIDE の向きを作って、そこにポリシーを当ててる。これで ping を撃つと 0% → 100% に戻るんだ。
inspect が INSIDE 発の ICMP を通したとき、その通信を「送信元・宛先・プロトコル・状態」としてセッションテーブルに記録する。R3 からの戻りは、そのセッションに一致するから自動で許可される。OUTSIDE→INSIDE のゾーンペアなんか要らない。TCP も同じで、R1 から R3 の 80 番に telnet すると Open になる ── SYN-ACK 以降の戻りも自動許可されてるんだ。見える。R2 の検査統計だ。
show policy-map type inspect zone-pair IN-OUT
Class-map: CM-IN-OUT (match-any)
Inspect
tcp packets: [0:22]
icmp packets: [0:20]
Session creations since subsystem startup or last reset 4
Current session counts (estab/half-open/terminating) [1:0:0]Session creations ... 4 は起動以降に作ったセッションが累計 4 つ。Current session counts ... [1:0:0] は今この瞬間に確立中のセッションが 1 つあることを示す。この「確立中セッションが居る」ことこそがステートフルの実体だ。inspect が「今この通信を追跡中で、戻りが来たら通す」状態を保持してる。
class-default の drop が効いてるりん?class-default の drop カウンタは、さっきの出力で 0 のままだったろ。OUTSIDE 発の通信は INSIDE→OUTSIDE の向きには流れないから、そもそも IN-OUT のポリシーで判定すらされてない。じゃあ何で落ちてるか ── OUTSIDE→INSIDE のゾーンペアが存在しないからだ。規則①のとおり、ゾーン間はゾーンペアが無ければ既定 deny。class-default の drop 以前に、「ゾーンペア不在の暗黙 deny」で捨てられてる。落とす主体は drop アクションじゃなく、ゾーンペアそのものの不在なんだ。established や reflexive ACL で手で穴を開けたが、inspect は通した通信のセッションを覚えて、その戻りだけ自動で開ける。書いてない新規は自動で塞がる。これがステートフルファイアウォールの守る境界の形だ。10.12.0.2 へ ping すると……100% で通る。Gi2 は INSIDE に入れたのに、そこからルータ自身(self)への通信は、self へのゾーンペアが無いから既定 permit で通る。規則④のとおりだ。「ゾーンに入れたら self 宛も deny」って誤解が、この実機結果で否定される。INSIDE→self のゾーンペアを作って、ICMP だけ inspect、それ以外を drop、にする。すると ICMP の ping は変わらず 100% で通る。でも検査統計を見ると ── ここに諸刃の性質が出る。
show policy-map type inspect zone-pair IN-SELF
Class-map: CM-IN-SELF (match-any)
Inspect
icmp packets: [0:10]
Class-map: class-default (match-any)
Drop
5 packets, 460 bytesclass-default: Drop 5 packets, 460 bytes ── ICMP 以外の self 宛通信が 5 パケット破棄されてる。SSH や SNMP がここに落ちてる。
inspect と pass の違い。inspect はステートフル検査して戻りを自動許可するが、pass はステートレスに通すだけで戻りは許可しない。pass の戻りを通したいなら逆向きのゾーンペアを別に作る。戻りが欲しい普通の通信は inspect、一方向だけの通信や両方向を明示制御したいときに pass だ。次に、class-default の drop は既定だから拒否を書く必要はないが、drop log にすると破棄を syslog に出せる。既定 deny は静かに落とすから、意図せぬ通信断の調査で log 付き drop が役立つ。ip inspect をインターフェース単位に当てる方式だった。ゾーンという抽象が無くてインターフェースが増えると煩雑だった。ZBF はその後継で、いまの推奨方式だ。新規に IOS でステートフルファイアウォールを組むなら ZBF を使う。inspect でステートフルに戻りを自動許可する現行方式だ。ACL と ZBF は排他じゃない ── ACL でアドレス・ポート単位の粗いフィルタ、ZBF でゾーン単位のステートフル制御、と役割を分けて併用する。