- 公開日
- 最終更新日
【Network Firewall】AWS Network Firewall の最小構成を作り、SNI ログから許可ドメインを絞り込む
この記事を共有する
目次
はじめに
アウトバウンド通信をドメインベースで制御したい場合、最初から必要な FQDN をすべて洗い出すのは意外と難しいです。
実際の運用では、まず *.example.com のようなワイルドカードを含む広めの許可から始め、通信ログを見ながら必要な FQDN に絞っていく進め方のほうが現実的なことがあります。
今回は、AWS Network Firewall(以下 NFW)の最小構成を作成し、TLS通信がalert log に出力するSNI(Server Name Indicator ) event.tls.sni を CloudWatch Logs Insights で確認することで、ワイルドカード指定のドメイン ALLOWLIST を FQDN 単位に絞り込めるかを検証しました。
今回やりたいこと
今回のゴールは、次のような運用が現実的に回せるかを確かめることです。
- まずは ALLOWLIST をワイルドカードで粗く許可する
- 実通信ログの
event.tls.sniから、実際に使われた FQDN を洗い出す - その結果をもとに、ALLOWLIST をより具体的な FQDN 単位に絞る
つまり、最初から正解のドメインリストを作るのではなく、 実際に流れたTLS通信を観測しながらSNIで許可範囲を狭めていく ことが今回の主題です。
検証構成
今回検証した構成は以下です。
VPC
├ IGW
├ firewall subnet
│ └ AWS Network Firewall
├ protected subnet
│ ├ EC2 (検証用)
│ └ SSM Interface VPCE
└ route
protected → firewall → IGW
IGW → firewall → protected
protected subnet 上の EC2 からインターネットへ出る通信を、必ず NFW 経由にします。
また、検証用 EC2 には SSM で接続したかったため、ssm / ssmmessages / ec2messages の Interface VPC Endpoint も protected subnet に配置しました。
使用した主なリソース
| 要件 | リソース |
|---|---|
| VPC | aws_vpc.this (10.2.0.0/16) |
| IGW | aws_internet_gateway.this |
| firewall subnet | aws_subnet.firewall (10.2.2.0/24) |
| AWS Network Firewall | aws_networkfirewall_firewall.this + ドメイン ALLOWLIST |
| protected subnet | aws_subnet.protected (10.2.1.0/24) |
| EC2(検証用) | aws_instance.test(SSM ロール付き) |
| SSM Interface VPCE | ssm / ssmmessages / ec2messages |
| protected → firewall → IGW | aws_route.protected_default_to_nfw → aws_route.firewall_default_to_igw |
| IGW → firewall → protected | aws_route.igw_to_protected_via_nfw(IGW edge route) |

ルーティングのポイント
今回の構成で一番重要なのは、NFW を作ることそのものより、通信をきちんと NFW に通すルーティングを組むことです。
インターネットへの通信経路は次のようになります。
NFW を経由させるには、行きと戻りの両方で NFW エンドポイントを通す必要があります。
このため、ルートテーブルは 3 つ用意しました。
| ルートテーブル | アタッチ先 | ルート | 役割 |
|---|---|---|---|
| protected RT | protected subnet | 0.0.0.0/0 → NFW Endpoint |
EC2 からの外向き通信を NFW 経由にする |
| firewall RT | firewall subnet | 0.0.0.0/0 → IGW |
NFW を通過した通信をインターネットへ出す |
| IGW edge RT | IGW(Gateway Route Table) | 10.2.1.0/24 → NFW Endpoint |
インターネットからの戻り通信を NFW 経由で protected subnet に返す |
ポイントは以下です。
- protected subnet 側だけでなく、戻り通信も NFW を通す必要がある
- そのために、IGW にアタッチする Gateway Route Table を使う
- 今回は NAT Gateway を置かず、EC2 にパブリック IP を付与したため、Security Group と NFW の両方でインバウンドを意識する必要がある
通信の流れをまとめると、次のようになります。
【アウトバウンド 外向き通信(EC2 → Internet)】
EC2 → protected RT (0.0.0.0/0 → NFW) → NFW → firewall RT (0.0.0.0/0 → IGW) → IGW → Internet
【アウトバウンド 戻り通信(Internet → EC2)】
Internet → IGW → IGW edge RT (10.2.1.0/24 → NFW) → NFW → EC2
NFW ルールグループ構成
今回の狙いは、許可対象の通信について SNI を alert log に残しつつ、マネージドルールで脅威ドメインも先に評価することです。
そのため、ルールグループは以下の順で構成しました。
| Priority | ルールグループ | 動作 |
|---|---|---|
| 10 | ALERTLIST(alert tls のみ) |
SNI を alert log に記録し、次の priority へ進む |
| 20 | BotNetCommandAndControlDomainsStrictOrder |
既知のボットネット C2 ドメインを drop |
| 30 | AbusedLegitMalwareDomainsStrictOrder |
侵害された正規ドメイン(マルウェア)を drop |
| 40 | MalwareDomainsStrictOrder |
既知のマルウェアドメインを drop |
| 50 | AbusedLegitBotNetCommandAndControlDomainsStrictOrder |
侵害された正規ドメイン(ボットネット)を drop |
| 60 | ALLOWLIST(domain list) | 許可ドメインを pass |
| default | aws:drop_strict + aws:alert_strict |
どのルールにもマッチしない通信を drop + alert |
この構成にした理由は次の通りです。
STRICT_ORDER+stateful_default_actions = ["aws:drop_strict", "aws:alert_strict"]により、ALLOWLIST にマッチしない通信をすべて drop できる- ALERTLIST を最初に評価することで、ALLOWLIST に含めた通信でも
event.tls.sniを alert log に残せる - マネージドルールを ALLOWLIST より先に置くことで、仮に許可対象に含まれるドメインでも、既知の脅威ドメインであれば先にブロックできる
初期の ALLOWLIST
最初は広めに観測するため、以下のワイルドカードドメインを許可しました。
variable "allow_domains" {
default = [
".amazonaws.com",
".github.com",
".docker.io",
".docker.com",
".cloudflarestorage.com"
]
}
検証手順
1. Terraform で構成を作成する
検証用コードは以下のリポジトリに置きました。
https://github.com/Nka700/pub_aws_conf.git
ホストマシンで以下を実行します。
git clone https://github.com/Nka700/pub_aws_conf.git
cd pub_aws_conf/nfw_minimum_configuration/terraform
tfstate 用バケット名を任意のものに変更後、以下を実行します。
terraform -chdir=env/poc init
terraform -chdir=env/poc apply -auto-approve
2. EC2 に SSM 接続する
aws ssm start-session --target ${インスタンスID} --region ap-northeast-1
3. インターネットへの通信を発生させる(Docker Hub)
今回は docker run hello-world を実行し、Docker Hub 関連の通信を発生させました。
sudo dnf install -y docker
sudo systemctl enable --now docker
sudo usermod -aG docker ssm-user
newgrp docker
docker run hello-world
4. Logs Insights で SNI を確認する
NFW の alert log に対して、以下の Logs Insights クエリを実行します。
fields event.tls.sni as sni
| filter event.alert.action = "allowed"
| filter sni != ""
| stats count(*) as hits,
min(@timestamp) as first_seen,
max(@timestamp) as last_seen
by sni
| sort hits desc

このクエリにより、ワイルドカードで許可しているドメインへの通信が、実際にはどの FQDN(SNI)で行われていたかを確認できます。
観測結果をもとに ALLOWLIST を絞る
docker run hello-world の通信を確認したところ、ワイルドカード指定していた .docker.io や .cloudflarestorage.com の配下でも、実際に使われていたホスト名はかなり限定的でした。
そこで、ALLOWLIST を以下のように変更しました。
--- a/nfw_minimum_configuration/terraform/env/poc/var.tf
+++ b/nfw_minimum_configuration/terraform/env/poc/var.tf
@@ -49,9 +49,9 @@ variable "allow_domains" {
default = [
".amazonaws.com",
".github.com",
- ".docker.io",
- ".docker.com",
- ".cloudflarestorage.com"
+ "registry-1.docker.io",
+ "auth.docker.io",
+ "docker-images-prod.6aa30f8b08e16409b46e0173d6de2f56.r2.cloudflarestorage.com"
]
}
その後、前回取得したイメージとコンテナを削除し、再度 docker run hello-world でインターネットへの通信を発生させて、より狭い ALLOWLIST でも動作することを確認しました。
docker ps --all
docker rm ${前回のhello-worldのコンテナID}
docker image rm hello-world
docker run hello-world
以上で 厳密な FQDN を決め打ちするのではなく、まずは観測し、通信先として使われているSNIで絞っていく 進め方が有効であることを確認できました。
注意点
今回の検証で重要だったのは、 SNI をログに残したいなら pass ではなく alert を先に置く必要がある 点です。
| アクション | ログ記録 | 後続ルール評価 | 結果 |
|---|---|---|---|
alert tls |
✅ alert ログ出力 | ✅ 次の priority へ進む | マネージドルール → ALLOWLIST の順に評価される |
pass tls |
❌ alert ログ出力されない | ❌ そこで評価終了 | マネージドルールをバイパスしてしまう |
pass を使うと、次のような問題が起こります。
- ALLOWLIST ドメインへの通信が alert log に記録されず、観測目的を果たせない
- その場で評価が終了するため、後段のマネージドルールが適用されない
観測しながら許可対象を絞る ことが目的なら、alert tls を最初に置いて、その後にマネージドルールと ALLOWLIST を評価する構成が扱いやすいです。
実際の運用では、以下も考慮が必要です。
- 通信試験の内容によって観測できる FQDN は変わる
- CDN やリダイレクトにより、別のホスト名が追加で必要になる場合がある
- 一度見えた FQDN が常に固定とは限らない
そのため、ALLOWLIST の絞り込みは一回で終わらせず、一定期間ログを観測しながら進めるのが安全です。
まとめ
AWS Network Firewall の最小構成でも、alert log の event.tls.sni を使うことで、実際に通信したドメインをある程度把握できました。
今回の検証を通じて、次のような進め方が現実的だと分かりました。
- まずはワイルドカードを含む広めのドメインリストを ALLOWLIST に設定して通信を許可する
- alert log から SNI を収集し、実際に使われた FQDN を確認する
- その結果をもとに、ALLOWLIST を段階的に具体的な FQDN に絞る
最初から厳密な許可ドメインを設計するのが難しい場合でも、 NFW のログを使って段階的に精度を上げていく ことで、現実的に運用することができそうです。
この記事は私が書きました
kengo.yamashiro
記事一覧