はじめに
前回の記事(https://qiita.com/cho-tehu/items/555e9dc7ec08cbc3330d) では、S3単体で静的サイトをホスティングし、バケットポリシーで特定IPからのみアクセスを許可する方法を紹介しました。
今回はさらに一歩進んで、CloudFront + WAF を組み合わせて「HTTPS対応」「グローバル配信」「高度なアクセス制御」を実現する手順を紹介します。
なぜCloudFront + WAFなのか
S3単体ホスティングは簡単で低コストですが、次のような制約があります:
-
HTTPS(SSL)非対応(HTTPのみ)
-
IP制限はできるが、国別や攻撃検知などの柔軟な制御は不可
-
キャッシュや高速配信ができない
CloudFront + WAFを組み合わせることで、これらをすべて解消できます。
S3単体と CloudFront + WAF の違い
| 項目 | S3単体 | CloudFront + WAF |
|---|---|---|
| アクセス制御 | バケットポリシーでIP制限のみ | WAFで複雑な条件(IP・国・ボットなど)を設定可能 |
| HTTPS | CloudFrontなしでは使えない | 自動でHTTPS化(無料のACM証明書対応) |
| キャッシュ | なし | CloudFrontがCDNとしてグローバルキャッシュ |
| パフォーマンス | S3リージョンに依存 | 世界中のエッジロケーションから高速配信 |
| セキュリティ | IP制限・署名付きURL程度 | DDoS防御・SQLi/XSS対策・Bot制御可能 |
| コスト | 安い(数円~) | 若干高い(数十円~百円単位) |
| 運用規模 | 小規模向け | 中〜大規模・社内システム・商用向け |
手順
1. S3バケットを作成
2. ファイルをアップロード
配信する静的ウェブサイトのファイルをアップロードします。
前回と同様に、Reactのデフォルトアプリケーションを配置します。
Reactのデフォルトアプリケーションを作成
npx create-react-app my-app
ビルドして静的ファイルを作成
cd my-app
npm run build
build/配下のファイルをS3バケットにアップロード
3. CloudFrontディストリビューションを作成
S3 Origin に 1で作成したバケットのARNを指定する。

作成時点でオリジンアクセス制御(OAC)が有効化されていると思いますが、下記の流れで念のため確認
(この設定でS3バケットがCloudFront経由のアクセス以外を拒否するようになります)

S3の当該バケットのアクセス許可のタブからバケットポリシーが更新されているかも確認

{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::test-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<アカウントID>:distribution/<ディストリビューションID>"
}
}
}
]
}
SourceArnに指定するのはCloudFrontのARNです。

次に、デフォルトルートオブジェクトの設定を行います。
(デフォルトルートオブジェクトとはウェブサイトのトップページ(ホーム)として表示されるファイル名のことです)

4. WAFの設定
デフォルトでは下記のルールが適用されていますが、必要に応じて追加してください。
- AWS-AWSManagedRulesAmazonIpReputationList
- AWS-AWSManagedRulesCommonRuleSet
- AWS-AWSManagedRulesKnownBadInputsRuleSet
追加方法
代表的なルールセット
| ルール名 | 対応する脅威・特徴 | 説明 |
|---|---|---|
| AWSManagedRulesCommonRuleSet | 一般的な脆弱性 | 最も基本的なルールセット。XSS、SQLi、ディレクトリトラバーサルなどを防御します。 |
| AWSManagedRulesKnownBadInputsRuleSet | 危険な入力パターン | 既知の悪意あるリクエスト(例:不正なヘッダやメタ文字列)をブロックします。 |
| AWSManagedRulesAmazonIpReputationList | 悪意あるIPアドレスからのアクセス | AWSが管理する「不正アクセス元」リストを基に、悪評高いIPを自動でブロックします。 |
| AWSManagedRulesSQLiRuleSet | SQLインジェクション | データベースを狙う攻撃(OR 1=1 など)を検知・遮断します。 |
| AWSManagedRulesBotControlRuleSet | Botアクセス | スクレイピングや自動化ツールによるアクセスを制限します。 |
これらを組み合わせることで、CloudFront + WAF 構成で 多層的なセキュリティ防御が実現できます。
AWS WAF の課金対象
-
Web ACL(アクセス制御リスト)
月額料金:1 Web ACL ごとに固定料金が発生
例:$5〜\$10/月(リージョンや AWS マネージドルールの有無で変動) -
ルールの課金
ルールの種類によって料金が異なる- AWS マネージドルール(AWS 提供のルールセット)
ルールごとに月額課金($1〜\$3/ルール/月程度) - カスタムルール(自分で作成した条件)
ルールごとに月額課金($1/ルール/月程度)
- AWS マネージドルール(AWS 提供のルールセット)
-
リクエスト数による課金
Web ACL 配下で処理された HTTP リクエストごとに課金
例:$0.60/100 万リクエスト(リージョンによって変動)
※ 時間単位で計算されるので、1時間利用した場合は その月の日数 × 24時間 で割った金額です。
5. ブロックパブリックアクセスの無効化
6. 念のためキャッシュクリア
S3 の中身を更新した場合は、キャッシュクリアを行わないと反映されません。
こちらの例では /* を指定し、すべてのファイルのキャッシュをクリアしています。
/index.html など指定すれば、特定のファイルのみキャッシュクリアすることができます。
CloudFrontはS3などの オリジン(元データ) からコンテンツを取得し、エッジロケーション(世界中のキャッシュサーバー)にキャッシュを保存して配信します。
このキャッシュは以下の条件でしか更新されません。
キャッシュが更新されるタイミング
- TTL(有効期限)が切れたとき
CloudFrontはオブジェクトをキャッシュした際に「TTL(Time To Live)」を設定します。TTLが切れると次のリクエストで新しいバージョンをオリジンから取得します。
TTLはHTTPヘッダの Cache-Control または Expires、またはCloudFrontのキャッシュポリシーで設定可能です。 - 手動で「無効化(Invalidation)」を実行したとき
管理コンソールやCLIからCreateInvalidationを行うことで、指定したパスのキャッシュを強制的に削除し、次のアクセス時に最新データを取得できます。 - オブジェクトの名称を変えたとき
S3のファイル名を変更した場合、CloudFrontは別オブジェクトとして扱うため自動的に新しいキャッシュを作ります。 - オリジン側の設定でキャッシュ無効化を制御したとき
S3やAPIのレスポンスでCache-Control: no-cacheなどを指定すると、CloudFrontは毎回オリジンに確認します。
7.動作確認
ディストリビューションドメイン名をコピーしてブラウザからアクセスする。
最後に
ドメイン買うと結構お金かかるんで、カスタムドメインの設定まで紹介することができませんでした。
ちなみに、ここまでセキュリティ固めて、個人用に配信する人いないと思いますが、WAFにカスタムルールで任意のIPを許可するルールとすべてのIPを拒否するルールを追加すれば、特定IPのみ許可する設定も可能です。
※ 必ず任意のIPを許可するルールを先に設定してください (順序が先のルールが優先されるため)。
要素が結構多くて散らかってしまいそうなので、細かい設定かなり無視してしまいました。
気になることがあったらコメントをお願いいたします。



















