既存リバースプロキシのオリジンに CloudFront + WAF を後付けする実践パターン
Docker + Traefik で運用中の複数サービスを、無停止で CloudFront + WAF の保護下に移行した実装記録。オリジン分離用サブドメインの命名、共有 SG の落とし穴、WAF を COUNT モードから始める判断、SPARQL/API のキャッシュ設計など、よく出る論点を一通り整理しました。
awscloudfrontwafterraformtraefiksecurityperformance
台本(フルテキスト)
動画の掛け合いを書き起こしたものです。音声を再生しづらい場合はこちらをお読みください。
オープニング
- 既存リバースプロキシに CloudFront + WAF を後付け
- Docker + Traefik 構成を無停止で移行
- ずんだもん
- こんにちは。今日は既存の Web サービスに CloudFront と WAF を後付けする話なのだ!
- めたん
- ええ。Docker と Traefik で運用中の複数サービスを、CloudFront と AWS WAF の保護下に無停止で移行した実装記録よ。オリジン分離のサブドメイン設計や共有セキュリティグループの落とし穴など、よく出る論点をまとめたわ。
- ずんだもん
- 移行前の構成はどうなっていたのだ?
- めたん
- ブラウザが DNS で直接オリジンの IP に接続していたの。VPS 上の Traefik がホストヘッダで複数サービスに振り分けて、Let's Encrypt で TLS 終端して、CrowdSec で攻撃検知していたわ。
- ずんだもん
- 移行後はどうなったのだ?
- めたん
- ブラウザが CloudFront を経由してオリジンに届く構成に変わったの。オリジン用の別サブドメインを立てて、CloudFront からの通信にはカスタムヘッダで合言葉を仕込んでいるわ。WAF で OWASP ルールや IP 評判チェックも入れたの。
オリジン用サブドメインを別名で立てる理由
- DNS ループ防止と TLS 証明書の整合性のため
- origin.example.com を新設(1階層に収める)
- ずんだもん
- なぜオリジン用の別サブドメインが必要なのだ?
- めたん
- CloudFront の Alternate Domain Name に example.com を設定すると、オリジンにも同じ example.com を指定するわけにはいかないの。DNS ループになってしまうから別名が必要なのよ。
- ずんだもん
- サブドメインのレベルに注意が必要なのだ?
- めたん
- ワイルドカード証明書 *.example.com は1階層しかカバーしないの。origin.api.example.com は2階層だからカバーされない。origin-api.example.com のように1階層に収めると既存のワイルドカード証明書がそのまま使えるわ。
- ずんだもん
- この落とし穴を実際に踏んだのだ?
- めたん
- ええ。API ドメインだけ origin.api.example.com にしてワイルドカードでカバーしようとして TLS エラーになって、結局 origin-api.example.com 形式に揃え直したわ。時間ロスだったの。
ALB と EC2 の SG 分離の落とし穴
- 共有 SG の 0.0.0.0/0 を削除したら ALB→EC2 通信も遮断
- ALB SG と EC2 SG は必ず別物として作る
- ずんだもん
- 最大の事故ポイントは何だったのだ?
- めたん
- ALB と EC2 が同じセキュリティグループを共有していたの。ALB の受信を CloudFront のプレフィックスリスト限定に絞ろうとして 0.0.0.0/0 を削除したら、ALB と EC2 の内部通信まで巻き添えで遮断されてしまったわ。
- ずんだもん
- どうやって無停止で分離できるのだ?
- めたん
- 新しい EC2 用 SG を作って EC2 の ENI に両方の SG をアタッチして動作確認してから、古い SG を外すという手順よ。両方アタッチしている間は両方の設定が効くから無停止で切り替わるの。
- ずんだもん
- secret header でオリジンを保護する方法も教えてほしいのだ。
- めたん
- CloudFront 側の Terraform でカスタムヘッダに openssl rand -hex 32 で生成したシークレットを設定して、Traefik 側ではそのヘッダがマッチしないリクエストに 404 を返すルールを書くの。攻撃者がオリジン IP を特定して直接アクセスしても 404 しか返らないわ。
キャッシュ設計と WAF の COUNT モード運用
- SPARQL/API は全クエリ文字列をキャッシュキーに
- WAF は最初の1週間は COUNT モードで誤検知を確認
- ずんだもん
- キャッシュはどう設計したのだ?
- めたん
- 基本ポリシーはクエリ文字列をすべてキャッシュキーに含めて Cookie と Authorization ヘッダは含めない設計よ。SPARQL や検索 API のような GET リクエストは URL が完全一致すれば 24 時間ヒットするから、重いクエリが 20 倍以上高速化したわ。
- ずんだもん
- WAF の COUNT モードとは何なのだ?
- めたん
- ルールにマッチしてもブロックせずにログだけ残す状態よ。最初の1週間は全ルールを COUNT モードで運用して、誤検知があるルールを特定してから block に切り替えたの。
- ずんだもん
- CrowdSec をやめて WAF にした理由は何なのだ?
- めたん
- CrowdSec は振る舞い分析ベースで、ファセット検索や無限スクロールのように短時間に多数のリクエストを送る正規ユーザーを誤ってBAN してしまうことがあったの。WAF のマネージドルールはリクエストのパターンで判定するから、リクエスト数には反応しにくい構造よ。
段階的な切り替え手順と速度向上
- xxxx.cloudfront.net で DNS 切替前に本番相当を検証
- キャッシュヒット時 TTFB が 125ms → 45ms
- ずんだもん
- 無停止で切り替えるにはどうすればいいのだ?
- めたん
- オリジン用 DNS 追加・リバースプロキシのルータ追加・CloudFront 構築という順で進めて、DNS を切り替える前に xxxx.cloudfront.net ドメインで curl と /etc/hosts を使ってテストするの。
- ずんだもん
- 速度はどう変わったのだ?
- めたん
- 東京クライアントから東京オリジンの場合、オリジン直接で70ミリ秒・CloudFront の初回キャッシュミスで125ミリ秒・キャッシュヒット時は45ミリ秒になったわ。東京同士でもキャッシュヒット時は35〜60パーセント速くなるの。
- ずんだもん
- 月のコストはどれくらいなのだ?
- めたん
- WAF の Web ACL が月5ドル・マネージドルール3個で月3ドル・リクエスト課金が100万件あたり0.60ドルで、複数ドメインを1つの WAF ACL で共有できるから合計月10〜15ドル程度よ。
まとめ
- オリジン別名・secret header・COUNT モード・SG分離が要点
- Terraform の for_each で複数ドメインを簡単に拡張
- ずんだもん
- 今日のポイントを整理してほしいのだ。
- めたん
- CloudFront と WAF を後付けするときは、オリジン用ドメインを別名で立てること、secret header でオリジンを実質非公開化すること、WAF を COUNT モードで1週間運用してから block 化すること、ALB と EC2 の SG は最初から分離することが要点ね。
- ずんだもん
- Terraform を使うとスケールしやすいのだ?
- めたん
- ええ。sites という map を for_each で回す構成にしておくと、新ドメインを追加する際はエントリを1つ追加するだけで済むわ。IaC化してよかった点の一つね。
- ずんだもん
- ALB と EC2 の SG 分離が今回最大の教訓なのだ。
- めたん
- そうね。初期設計の段階で必ず押さえておくべきポイントよ。同様の事故を防げるよう記事として共有したわ。