DNS Firewallの評価はどこで行われるのかを実機の対照実験で確かめてみた

記事タイトルとURLをコピーする

こんにちは。クロスインダストリー第1本部クラウドコンサルティング2課の清水です。

複数のVPCを、外部通信をまとめて出す「共通(アウトバウンド)VPC」に集約する構成は、NAT GatewayやAWS Network Firewallの一元管理を目的によく採用されます。この構成に「Amazon Route 53 Resolver DNS Firewall」(以下、DNS Firewall)も乗せて、共通VPC側に1箇所だけルールグループを関連付ければ、配下の個別VPCすべての名前解決を一括でフィルタできるのではないかと考えて設計を進めていたのですが、実は機能しない可能性があることが分かってきました。

本記事では、検証を通じて見えてきた「DNS Firewallがどこで評価されるのか」という仕組みを、対照実験の結果とあわせて解説します。

検証したい内容

  • 個別のシステムVPCが複数あり、それぞれから外部への通信は共通VPC経由に統一されている
  • 共通VPC側にはすでにNAT Gatewayなど、外部通信を集約する仕組みがある
  • ここにDNS Firewallも追加し、外部ドメインへの名前解決を許可リスト方式で制限したい
  • ただし、個別VPCの数だけDNS Firewallルールグループを関連付けて回るのは運用上避けたい。共通VPC側に1箇所だけ関連付ければ済むようにしたい

この設計の前提には、「転送ルール+RAM共有で個別VPCから共通VPCへDNSクエリを転送しているのだから、DNS Firewallも共通VPC側に関連付ければ、そこを通過するすべてのクエリが評価されるはずだ」という理解がありました。この理解が正しいかどうかを、実際に手を動かして確かめたのが今回の検証です。

検証シナリオ・構成

検証環境はCloudFormationで構築しました。登場するリソースは以下の通りです。

  • 個別システムVPC役(VPC-A)には、DNSクエリを発生させるクライアントEC2を配置しました。
  • 共通(アウトバウンド)VPC役(VPC-B)には、Resolverアウトバウンドエンドポイントと検証用のモックDNSサーバーを配置しました。
  • 特定のテストドメイン(test.example.internal.)宛の転送ルールを作成し、VPC-Aに関連付けました(本来は別アカウントからのRAM共有を想定していますが、検証では同一アカウント内で構成を再現しています)。
  • DNS Firewallルールグループは該当ドメインをBLOCKする内容で、ブロック時の応答には明示的に NXDOMAIN を指定しました。無指定時のデフォルトは NODATA になるので、この設定次第で見た目の挙動が変わる点は注意が必要です。関連付け先はVPC-A・VPC-Bで切り替えられるようパラメータ化しています。
  • Resolverクエリログは、VPC-A・VPC-B双方に関連付けました。これにより vpc_id や resolver_endpoint といったフィールドを比較できます。

ポイントは、DNS Firewallルールグループの関連付け先をVPC-A(個別システムVPC=クエリの発生元)にするか、VPC-B(共通VPC=転送先)にするかを切り替えて、同じテストドメインへの問い合わせ結果とクエリログを比較できるようにしたことです。

手順

  1. DNS FirewallをVPC-B(共通VPC)に関連付けた状態でスタックをデプロイする
  2. VPC-Aのクライアントインスタンスに接続し、テストドメインへ dig または nslookup を実行して結果を確認する
  3. CloudFormationのスタック更新で、DNS Firewallの関連付け先パラメータをVPC-A(個別システムVPC)に切り替える
  4. 同じ問い合わせを再実行し、結果を比較する
  5. 両パターンについて、Resolverクエリログの内容(firewall_rule_action、resolver_endpoint などのフィールド)を確認する

結果

対照実験の結果は次のようになりました。

DNS Firewallの関連付け先 名前解決の結果 クエリログ
共通VPC(VPC-B、転送先) NOERROR で正常に解決される(ブロックされない) firewall_rule_action フィールド自体が存在しない(DNS Firewallが評価していない)
個別システムVPC(VPC-A、クエリ発生元) NXDOMAIN でブロックされる firewall_rule_action: "BLOCK"、firewall_rule_group_id、firewall_domain_list_id が記録される

共通VPC関連付け時に、VPC-Aのクライアントインスタンスから個別システムVPC自身のResolver(10.60.0.2)へ直接問い合わせた結果です(ドメイン名は汎用のものに置き換えています)。

$ dig +noall +answer +comments test.example.internal @10.60.0.2

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45433
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; ANSWER SECTION:
test.example.internal.  0 IN    A   10.61.0.10

status: NOERROR で、共通VPC側のモックDNSサーバーのIPが正常に返ってきています。続いて、実際に記録されたクエリログです。

{
  "version": "1.100000",
  "account_id": "123456789012",
  "region": "ap-northeast-1",
  "vpc_id": "vpc-0123456789abcdef0",
  "query_timestamp": "2026-08-13T03:48:09Z",
  "query_name": "test.example.internal.",
  "query_type": "A",
  "query_class": "IN",
  "rcode": "NOERROR",
  "answers": [
    {"Rdata": "10.61.0.10", "Type": "A", "Class": "IN"}
  ],
  "srcaddr": "10.60.0.17",
  "srcport": "39428",
  "transport": "UDP",
  "srcids": {
    "instance": "i-0123456789abcdef0",
    "resolver_endpoint": "rslvr-out-0123456789abcdef0"
  }
}

firewall_rule_action のようなフィールドが一切存在せず、rcode は NOERROR。DNS Firewallが評価に関与していないことが分かります。

DNS Firewallの関連付け先を個別システムVPCに切り替えた後、同じ問い合わせを実行した結果です。

$ dig +noall +answer +comments test.example.internal @10.60.0.2

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 22842
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096

status が NOERROR から NXDOMAIN に変わり、ANSWERセクションも空になりました。同じ問い合わせ先(10.60.0.2)に投げているにもかかわらず、DNS Firewallの関連付け先を変えるだけで応答がまったく変わることが、実機でも確認できました。

続いて、同じタイミングで記録されたクエリログです。

{
  "version": "1.100000",
  "account_id": "123456789012",
  "region": "ap-northeast-1",
  "vpc_id": "vpc-0123456789abcdef0",
  "query_timestamp": "2026-08-13T03:50:39Z",
  "query_name": "test.example.internal.",
  "query_type": "A",
  "query_class": "IN",
  "rcode": "NXDOMAIN",
  "answers": [],
  "srcaddr": "10.60.0.17",
  "srcport": "39966",
  "transport": "UDP",
  "srcids": {
    "instance": "i-0123456789abcdef0"
  },
  "firewall_rule_action": "BLOCK",
  "firewall_rule_group_id": "rslvr-frg-0123456789abcdef0",
  "firewall_domain_list_id": "rslvr-fdl-0123456789abcdef0"
}

今度は rcode が NXDOMAIN に変わり、firewall_rule_action: "BLOCK" をはじめとするDNS Firewall関連のフィールドが追加されています。一方で、共通VPC関連付け時のログにあった resolver_endpoint フィールドはここには付いていません。

興味深いのは、どちらのパターンでも vpc_id フィールドは常にクエリの発生元(VPC-A)を指していたことです。つまりログ上の vpc_id は「クエリがどこから来たか」を表しているだけで、「DNS Firewallがどこで評価されたか」とは別の話だと分かります。この点、私は最初「vpc_id がVPC-Aなら、DNS FirewallもVPC-A基準で評価されているはず」と早合点しそうになったのですが、実際に評価されるかどうかを決めるのは「関連付け先」であって、ログの vpc_id ではありませんでした。

なお、ブロックされた場合のログには resolver_endpoint フィールドが付きませんでした。これは、ブロック判定がアウトバウンドエンドポイントに到達する前、つまり転送処理そのものが行われる前に完了していることを示しています。

なぜこうなるのか(仕組み解説)

この結果を理解する鍵は、AWS公式ドキュメントの以下の一文にあります。

VPC Resolver routes the VPC's outbound DNS queries through DNS Firewall, and DNS Firewall filters the queries using the associated rule groups.

出典: Enabling Resolver DNS Firewall protections for your VPC

これを踏まえると、DNS Firewallの関連付けは「そのVPC自身が送出するDNSクエリ」を評価対象にする仕組みだと分かります。転送ルールは「特定ドメインのクエリをどこに転送するか」を定義するものであり、「転送されたクエリがどのVPCで評価されるか」までは制御していません。転送ルールとRAM共有によってクエリが物理的に共通VPC側のアウトバウンドエンドポイントに送られていくのは事実ですが、DNS Firewallの評価はそれよりも手前、つまりクエリが発生した個別システムVPC(VPC-A)自身のResolverの段階で完結してしまいます。共通VPC側のDNS Firewallまでクエリが「届く」ことはあっても、そこで待ち構えているDNS Firewallが個別VPC発のクエリを見にいくわけではない、という言い方もできます。

「転送ルールでクエリを送り込んでいるのだから、転送先でも評価されるはずだ」という直感がなぜ外れるのか。それは、DNS Firewallが評価するのが「クエリの転送経路」ではなく「クエリを送出したVPCのResolverインスタンス」だからです。転送ルールとDNS Firewallの関連付けは、見た目は同じ「VPCに何かを関連付ける」設定ですが、評価される主体がまったく別物である、という点が今回の検証で得られた一番の学びでした。

まとめ

  • DNS Firewallは、関連付けたVPC自身が送出するクエリしか評価しない。転送ルールで送り込んだ先のVPCでは評価されない
  • 転送ルールとRAM共有はクエリの転送経路を作るだけで、DNS Firewallの評価対象VPCには影響しない
  • 個別VPCの数だけDNS Firewallを関連付けたくない場合は、転送ルール方式とは別のアプローチを検討する必要がある

「共通VPCに1箇所だけ置けば楽ができる」という設計は魅力的ですが、DNS Firewallの評価がどこで行われるかという仕組みを理解しないまま進めると、実装後に「フィルタが効いていない」という事態になりかねません。設計段階でドキュメントの記述に違和感を覚えたら、面倒でも実機で対照実験をしてから先に進むことをおすすめします。

参考リンク

しみず (記事一覧)

クロスインダストリー第1本部 クラウドコンサルティング2課

腰痛と戦う駆け出しエンジニア