🌐

CloudFrontとWAFについてハンズオンで学んでみた

に公開
3

はじめに

  • CloudFront と AWS WAF を組み合わせてセキュアな静的サイト配信を試してみた。
  • OAC(Origin Access Control)を使えば、S3 をパブリックにせずに CloudFront 経由でのみ配信できる構成を作れる。
  • この記事はその学習用ハンズオンをまとめたもの。
  • 補足: CloudFront には AWS Shield Standard による L3/L4 DDoS 緩和が自動で有効になっているため、追加の設定や料金は不要。

ゴール

  • 東京リージョン(ap-northeast-1)の 非公開 S3 バケット を CloudFront 経由で配信する
  • AWS WAF (CloudFront スコープ, us-east-1) を設定し、IP 制限や SQL インジェクション検知を体験する
  • ブロック / 許可 の挙動を確認する

利用サービスと無料枠について

実際に構築を始める前に「どのサービスで料金が発生するのか」を整理しておく。

今回の構成では、CloudFront / WAF / CloudWatch Logs / S3 の 4 サービスを利用する。
それぞれ「どこで料金が発生するのか」「無料枠があるのか」を押さえておくと、検証時や本番運用時に無駄なコストを避けやすい。

特に CloudFront と S3 には無料枠が用意されており、軽い検証用途ならほとんど無料で実行できる。
WAF は無料枠がなく作成した時点で課金が始まる点に注意が必要。
CloudWatch Logs には少額の無料枠があるが、ログを無制限に出力するとすぐに超過するため保存期間や出力対象を設計しておくことが重要。

以下に各項目を整理した。

利用項目と無料枠(料金付き)

区分 項目 説明 無料枠(可能な限度) 超過時の単価目安
CloudFront 月間データ転送量 (GB) エッジからインターネットへ配信されたデータ量 毎月 1 TB まで無料 従量課金(Japan リージョン:約 $0.114/GB)
月間リクエスト数 処理された HTTP/HTTPS リクエスト数 毎月 1,000 万 リクエストまで無料 約 $0.01 / 10,000 リクエスト
Invalidation パス数 キャッシュ削除リクエスト数(例:/* は1パス) 毎月 1,000 パスまで無料 約 $0.005 / パス
WAF Web ACL 数 アクセス制御リストの数 —(無料枠なし) 約 $5 / Web ACL / 月
有効ルール数 作成したすべてのルール(カスタム + マネージド)数 —(無料枠なし) 約 $1 / ルール / 月
検査リクエスト数 WAF が評価したリクエスト数 —(無料枠なし) 約 $0.60 / 100 万リクエスト
CloudWatch Logs 取り込み量 (GB) ログを CloudWatch に送った量 毎月 5 GB まで無料 約 $0.50 / GB
保存容量 (GB・月) 保存しているログの平均容量 毎月 5 GB まで無料に含まれる 約 $0.03 / GB・月
Insights スキャン量 (GB) Logs Insights でスキャンしたログ量 毎月 5 GB まで無料に含まれる 約 $0.005 / GB スキャン
Live Tail 利用時間 リアルタイムログ確認機能の利用時間 毎月 1,800 分(約1時間/日)まで無料 約 $0.005 / 分
S3(オリジン) 平均ストレージ (GB) バケットに保存されているデータ量 毎月 5 GB まで無料 S3 標準:約 $0.023 / GB・月
GET リクエスト数 S3 に対する GET(読み取り)回数 毎月 200 万 リクエストまで無料 約 $0.0004 / 1,000 GET リクエスト
PUT/COPY/POST/LIST リクエスト数 S3 に対する書き込みや一覧取得などの回数 毎月 20 万 リクエスト(合算)まで無料 約 $0.005 / 1,000 リクエスト
  • CloudFront と S3 は無料枠が大きめ → 学習・検証であればほぼコストゼロで済む。
  • WAF は無料枠なし → 作成した時点で課金が始まる。
  • CloudWatch Logs は 5GB/月 の無料枠があるが、保存期間を長くしたり無闇に出力するとすぐに超過するため要注意。
  • WAF の「検査リクエスト数」はトラフィックに応じて従量課金されるため、アクセス数が多い本番環境では費用の見積もりが重要。

構築手順

Step 1. S3 バケット作成

下記のような設定でS3バケットを作成

  • リージョン: ap-northeast-1
  • バケット名: waf-handson-oac
  • パブリックアクセスブロックは有効のまま
  • 静的ウェブサイトホスティングは無効

以下は作成時のスクリーンショット


Step 2. オブジェクトアップロード

  • index.html をバケット直下に配置
<!DOCTYPE html>
<html>
<body>
  <h1>Hello AWS WAF + CloudFront (OAC)!</h1>
</body>
</html>
  • アップロード画面

Step 3. CloudFront ディストリビューション作成

下記のような設定でCloudFront ディストリビューション作成

  • Distribution name(必須): cf-waf-handson

  • Description(任意): CloudFrontとWAFのハンズオン学習用リソース

  • Distribution type(必須): Single website or app

  • Custom domain(任意): 設定なし

  • Tags(任意): 設定なし

  • Origin type(必須): Amazon S3

  • S3 origin(必須): waf-handson-oac.s3.ap-northeast-1.amazonaws.com

  • Origin path(任意): 設定なし

  • Settings(必須)

    • Allow private S3 bucket access to CloudFront(必須): True
    • オリジン設定(必須): Use recommended origin settings
    • Cache settings(必須): Use recommended cache settings tailored to serving S3 content
  • Security Protections(必須): セキュリティ保護を有効にする

    • Use monitor mode(必須): True

    最後に設定内容の確認画面で内容に間違いがないか確認

ディストリビューションが作成される


Step 4. S3 バケットポリシー設定

  • CloudFront Distribution ID を元に、OAC からのアクセスだけを許可するポリシーを追加
  • 直接 S3 にアクセスすると 403 Forbidden、CloudFront 経由なら OK になる
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalOAC",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::waf-handson-oac/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::************:distribution/************"
        }
      }
    }
  ]
}


Step 5. WAF Web ACL 作成

Global(Cloud Front)を選択すると自動生成されたACLが存在している

Step 3 で作成した Cloud Front ディストリビューション紐づいていることが確認できる


Step 6. デフォルトアクションの変更(全体ルールの最後の動作)

  • Block に変更することでルールに合致しないアクセスはすべて拒否する


Step 7. ルール追加

IP制限ルールを作成(指定したIP以外はブロック)

  1. IP set の作成

    • WAF コンソールで IP sets → Create IP set を選択
    • 以下の設定で作成
      • IP set name(必須): AllowMyIPv6
      • Description(任意): CloudFrontとWAFのハンズオン学習用リソース
      • Region(必須): `Global (CloudFront)
      • IP version(必須): IPv6
      • IP addresses(必須): 自宅ネットワークのIPアドレス範囲
  2. ルールの追加

    • Web ACL の Rules タブ → Add rules → Add my own rules and rule groups を選択

    • 以下の設定でルールを追加

      • Rule type(必須): IP set
      • Name(必須): AllowMyIpv6
      • IP address to use as the originating address(必須): Source IP address
      • Action(必須): Allow
  3. Set rule priority

    • ルール追加後にSet rule priorityの画面が表示されるが後ほど調整するため現時点では変更しない

AWS Managed Rules の追加(SQLi対策)

  1. ルールの追加

    • Web ACL の Rules タブ → Add rules → Add managed rule groups を選択

    • Rule group から以下を選択

      • AWS managed rule groups → SQL Database (AWSManagedRulesSQLiRuleSet)

  2. Set rule priority

    • priotrity を次のように設定する

Step 8. 動作確認

許可したIPからアクセス

  • 自宅のネットワークから下記にアクセスした
  • https://**************.cloudfront.net/index.html
    (※ * はCloudFront ディストリビューションのドメイン名)
  • 正常にページが表示された

許可していないIPからアクセス

  • スマートフォンのキャリア回線から下記にアクセスした
  • https://**************.cloudfront.net/index.html
    (※ * はCloudFront ディストリビューションのドメイン名)
  • 403 Forbidden(WAFがブロック)

SQLi対策の確認

  • 自宅のネットワークから下記にアクセスした
  • https://**************.cloudfront.net/index.html?id=1'OR'1'='1
    (※ * はCloudFront ディストリビューションのドメイン名)
  • 403 Forbidden(WAFがブロック)

Step 9. WAF ログの確認手順

1. CloudWatch Logs に移動

  1. リージョンを バージニア北部 (us-east-1) に切り替える
    (CloudFront スコープの WAF は us-east-1 管理のため、ログ出力先も us-east-1)
  2. CloudWatch → ロググループ を開く
  3. WAF 設定時に選んだロググループ名(例: aws-waf-logs-handson)をクリック

2. Logs Insights を開く

  1. ロググループ画面で 「Logs Insights」 をクリック
  2. 対象ロググループ(例: aws-waf-logs-handson)を選択して、クエリ実行画面へ
    クエリ実行画面(例)

3. クエリを実行(/index.html だけを見る)

fields @timestamp, httpRequest.uri, action, terminatingRuleId, httpRequest.clientIp
| filter httpRequest.uri = "/index.html"
| sort @timestamp desc
| limit 3

4. ログの確認(結果例)

@timestamp httpRequest.uri action terminatingRuleId httpRequest.clientIp
2025-08-25 15:09:02.516 /index.html ALLOW AllowMyIpv6 240b:10:bf0b:3600:****:****:****:****
2025-08-25 14:54:40.522 /index.html BLOCK AWS-AWSManagedRulesSQLiRuleSet 240b:10:bf0b:3600:****:****:****:****
2025-08-25 14:45:31.928 /index.html BLOCK Default_Action 240b:c010:0610:62f1:****:****:****:****

各カラムについて

  • terminatingRuleId:最終的に適用されたルール名(例: AWS-AWSManagedRulesSQLiRuleSetAllowMyIP など)
  • actionBLOCK または ALLOW
  • httpRequest.clientIp:リクエスト元 IP
  • httpRequest.uriパスのみ(例: /index.html
    ※ クエリ文字列は httpRequest.args に記録されます

備考:クエリ文字列を含めて見たい場合

  • httpRequest.argsid=1'OR'1'='1 のような クエリ部分が出る
  • SQLi の検知状況を見るときは、terminatingRuleId...SQLiRuleSet になっているかを確認する

SQL例

  fields @timestamp, httpRequest.uri, httpRequest.args, action, terminatingRuleId
| filter httpRequest.uri = "/index.html"
| sort @timestamp desc
| limit 3

取得ログ例

@timestamp httpRequest.uri httpRequest.args action terminatingRuleId
2025-08-25 15:09:02.516 /index.html ALLOW AllowMyIpv6
2025-08-25 14:54:40.522 /index.html id=1%27OR%271%27=%271` BLOCK AWS-AWSManagedRulesSQLiRuleSet
2025-08-25 14:45:31.928 /index.html BLOCK Default_Action

学び

  • OAC により S3 を非公開のまま CloudFront 経由で安全に配信できることを確認
  • AWS WAF を組み合わせることで IP制御やSQLi防御が可能
  • CloudWatch ログを利用すると、WAF がどのルールで判定したか可視化できる
  • 実運用にそのまま使うにはログ保持ポリシーやルール調整など追加設計が必要だが、ベースとして十分参考になる構成を体験できた

おわりに

  • CloudFront + WAF を学ぶのにちょうどいいハンズオンでした。
  • 学習用としてはもちろん、本番環境の設計に発展させるための土台としても参考になる構成となっているので、試したことがない方はぜひ一度挑戦してみてください。

Discussion

MizMiz

WAF と CloudWatch Logs は無料枠なし → 作成した時点で課金が始まる。

CloudWatch Logsには以下の無料枠があります。

5 GB データ (取り込み、ストレージのアーカイブ、Logs Insights クエリによってスキャンされたデータ)
1 か月あたり 1,800 分の Live Tail の使用 (約 1 時間/日)

https://aws.amazon.com/jp/cloudwatch/pricing/

ただし、ログの保存期間を無期限に設定していたり無闇にログ出力を行うようにしているとあっという間に5GBを超えてコストが大きくなるので、無料枠の有無に関係なくその点は要注意です。

mocchansunmocchansun

コメントありがとうございます!🙇‍♂️
後ほど記事のほうに反映させていただきます!