Gemcook Tech Blog
🎨

【AWS CLI】S3 + CloudFront + Route 53のハンズオン (静的ウェブサイトホスティング)

2025/03/11に公開

はじめに

知人のWebサイトを作る機会がありAWSの静的ウェブサイトホスティングを調べていたのですが、AWS CLIでの作り方を紹介している記事が少なかったので、せっかくなのでハンズオン形式で1本にまとめてみました!

今回のハンズオンでは3つの章で構成しています。S3バケットにアクセスするだけのシンプルな構成に始まり、章が進むにつれて一般公開向きの静的ウェブサイトホスティングの構成が出来上がります。

章単位で区切り良く作成しているのでご自身の必要な範囲で進めたり、またはストップしたりできるので是非お気軽にお試しください🙌

アイコンの意味
👤ユーザー / 🪣S3 / 📡CloudFront / 🪯Route 53 / 🔥WAF

リクエスト先 通信経路
【1】
S3の静的ウェブサイトホスティング
S3のウェブサイトエンドポイント 👤 → 🪣
【2】
CloudFront経由
CloudFrontディストリビューションドメイン 👤 → 📡 → 🪣
【3】
Route 53経由
独自ドメイン 👤 → 🪯 → 📡 → 🪣
【おまけ】
WAFの設定
独自ドメイン 👤 → 🪯 → 📡🔥 → 🪣

※「【3】Route 53経由」まで進める方は事前にドメイン取得が必要です。

進め方のヒント

AWS CLIは入出力がテキストデータです。そのため、コマンドや出力結果のjsonファイルはChatGPTにききながら進めると、ハンズオン以外の設定についても幅広く情報を得られるのでおすすめです。(実データの取り扱いにはお気をつけください)

💡Tips💡
先にAWS CLIの設定やメリットを知りたい方はこちら

https://zenn.dev/kompeito/articles/0400005bc7a3e5

前置き

  • AWS CLIのバージョンはv2です。
  • AWS CLIで操作するリソースのリージョンは東京を指定しています。
入力
cat ~/.aws/config
出力
[default]
region = ap-northeast-1
output = json
...

【1】🪣 S3の静的ウェブサイトホスティング

それでは早速S3の静的ウェブサイトホスティングを作っていきましょう!

構成図

リクエスト先 通信経路
S3のウェブサイトエンドポイント 👤 → 🪣

準備

バケット名はsample-app-20250215-s3です。頻繁に登場するのでプロセスの環境変数に入れておきましょう。

バケット名はAWSの全リージョンにわたってユニークにする必要があるためバケット名にはご自身のニックネームやプロジェクト固有の文字列などを盛り込んでおくと被りにくいかもしれません。

入力
BUCKET_NAME=sample-app-20250215-s3
echo $BUCKET_NAME
出力
sample-app-20250215-s3

バケットを作成

入力
aws s3 mb s3://$BUCKET_NAME

アプリをアップロード

先に今回ホスティングするウェブサイトをアップロードしておきましょう。今回はReactのアプリをアップロードします。

※ここは本筋でないのでコマンドだけまとめて載せます。

入力
# プロジェクト作成
npm create vite@latest sample_app

# reactとTypeScriptを選択
✔ Select a framework: › React
✔ Select a variant: › TypeScript

# パッケージをインストール
cd sample_app
npm install

# サーバ起動 (ローカルで一度動作確認したい場合)
npm run dev

# ビルド
npm run build

ビルドしたアプリをS3へアップロードします。

入力
aws s3 sync ./dist s3://$BUCKET_NAME/

AWSコンソールで以下の表示になっていればOKです!

パブリックアクセスをブロックする設定をOFF

紛らわしいですが「ブロックする設定を解除する」ため、パブリックアクセスできる状態に切り替わります。

入力
aws s3api put-public-access-block \
--bucket $BUCKET_NAME \
--public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

パブリックアクセスの設定を確認します。

入力
aws s3api get-public-access-block --bucket $BUCKET_NAME
出力
{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": false,
        "IgnorePublicAcls": false,
        "BlockPublicPolicy": false,
        "RestrictPublicBuckets": false
    }
}

以下プロパティの説明です。

プロパティ 説明 効果
BlockPublicAcls バケットやオブジェクトに設定されたパブリックACLをブロックするかどうか falseの場合、設定されたACLがそのまま有効となる
IgnorePublicAcls オブジェクトのパブリックACLを無視するかどうか falseの場合、ACLの設定が無視されず、適用される
BlockPublicPolicy バケットポリシーによるパブリックアクセスをブロックするかどうか falseの場合、ポリシーで許可されたアクセスも有効となる
RestrictPublicBuckets バケット全体へのパブリックアクセスを厳格に制限するかどうか falseの場合、アクセス制限が緩くなり、柔軟にアクセス可能となる

これでパブリックアクセスできるバケットポリシーが適用されるようになったので、まずはバケットポリシー用のjsonファイルを作成していきます。

bucket-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::$BUCKET_NAME/*" //バケット名に置き換えてください
        }
    ]
}

ポリシーを反映します。

入力
aws s3api put-bucket-policy \
--bucket $BUCKET_NAME \
--policy file://bucket-policy.json

AWSコンソールで以下の表示になっていればOKです!

ウェブサイトエンドポイントを割り当て

静的ウェブサイトホスティングの設定を有効化します。

--index-documentには先ほどアップロードしたReactのアプリのルートファイルを指定します。

入力
aws s3 website s3://$BUCKET_NAME/ --index-document index.html

AWSコンソールのウェブサイトエンドポイントにアクセスして、スタート画面が表示されればOKです!

【2】📡 CloudFront経由

前の章で作成したウェブサイトエンドポイントは手軽に公開できる反面、ユーザーへ提供するウェブサイトとしては避けたい特徴もあります。

  • HTTPS通信に非対応
  • 毎回S3バケットにデータを取得しに行く(キャッシュの機構がない)

そのためこの章では以下2点を対応します。

  • CloudFrontディストリビューションの作成
    • HTTPS通信できるようになる
    • リクエストに近いエッジロケーションにキャッシュして配信するキャッシュ機構ができる
  • OAC(Origin Access Control)の設定
    • S3のバケットへの通信が特定のCloudFront経由からのみに制限されてセキュアになる

構成図

現在:

リクエスト先 通信経路
S3のウェブサイトエンドポイント 👤 → 🪣

変更後:

リクエスト先 通信経路
CloudFrontディストリビューションドメイン 👤 → 📡 → 🪣

準備

ウェブサイトエンドポイントを削除

前回の章で対応したS3の静的ウェブサイトホスティングの設定を無効化し、ユーザーが直接S3バケットにアクセスできない状態に戻します。

入力
aws s3api delete-bucket-website --bucket $BUCKET_NAME

パブリックアクセスをブロックする設定をON

続いて、パブリックアクセスをブロックする設定をONに戻します。

入力
aws s3api put-public-access-block \
--bucket $BUCKET_NAME \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

ポリシーも削除しておきます。

入力
aws s3api delete-bucket-policy --bucket $BUCKET_NAME

これでS3バケットの設定は、元のパブリックアクセスをブロックする状態に戻りました。

それでは次のセクションから本格的にCloudFront経由でアクセスする設定にしていきましょう!

CloudFrontディストリビューションを作成

CloudFrontディストリビューションを作成します。

CloudFrontディストリビューションが配信するリソースの元データ(origin)に、S3バケットを指定します。

入力
aws cloudfront create-distribution \
--origin-domain-name $BUCKET_NAME.s3.amazonaws.com \
--default-root-object index.html

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/create-distribution.html

AWSコンソールでCloudFrontディストリビューションが作成できれいればOKです!

補足

なぜSSL証明書を発行していないのにHTTPS通信できてるの?

CloudFrontディストリビューションドメイン(例: xxxxxx.cloudfront.net)は、AWSが提供するSSL証明書が設定されているため、新たなSSL証明書の発行は不要でHTTPS通信が可能になっています。

CloudFrontディストリビューションドメインは自由に変えられないの?

CloudFrontディストリビューションドメインはAWS側で提供されるため変更できません。自由に変更したい場合は次の章の「【3】🪯 Route 53経由」の対応が必要です。

OACを作成し、S3のバケットポリシーを更新

作成したOAC(Origin Access Control)をCloudFront側とS3側の両方で設定することで、S3のバケットに対し特定のCloudFrontからのみアクセスできる仕組みになり、S3バケットのデータ保護を強化することができます。

https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/

まずは、今回はsample-app-20250215-s3-oacというOACを作成します。

入力
aws cloudfront create-origin-access-control \
--origin-access-control-config Name="sample-app-20250215-s3-oac",SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 \
--query 'OriginAccessControl.Id' --output text
出力
E10I7FMDP8WWQZ

出力されたOACのidは後で使います。

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/create-origin-access-control.html

CloudFrontディストリビューション側の修正

CloudFrontディストリビューションの設定を修正します。

CloudFrontディストリビューションの設定ファイルの修正はローカルで行うため、出力内容をファイルにリダイレクトさせます。

--idはAWSコンソールから確認できるCloudFrontディストリビューションのidを指定してください。

入力
aws cloudfront get-distribution-config --id E38CJMA44PEL28 > dist-config.json

以下の修正を対応します。

  • OACの設定
  • HTTPリクエストをhttpsにリダイレクトする設定(ついでに)
dist-config.json
{
-   "ETag": "E15DVVP1XE3M3S", //更新する際にETagの指定が必要なので控えておく (プロパティとしては不要なので削除)
-   "DistributionConfig": { //更新時に不要な階層のため削除しておく
    "CallerReference": "cli-1739612759-610898",
    "Aliases": {
      "Quantity": 0
    },
    "DefaultRootObject": "index.html",
    "Origins": {
      "Quantity": 1,
      "Items": [
        {
          "Id": "sample-app-20250215-s3.s3.amazonaws.com-1739612759-135608",
          "DomainName": "sample-app-20250215-s3.s3.amazonaws.com",
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "ConnectionAttempts": 3,
          "ConnectionTimeout": 10,
          "OriginShield": {
            "Enabled": false
          },
-         "OriginAccessControlId": ""
+.        "OriginAccessControlId": "E10I7FMDP8WWQZ" //OACのidをセット
        }
      ]
    },
    "OriginGroups": {
      "Quantity": 0
    },
    "DefaultCacheBehavior": {
      "TargetOriginId": "sample-app-20250215-s3.s3.amazonaws.com-1739612759-135608",
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "TrustedKeyGroups": {
        "Enabled": false,
        "Quantity": 0
      },
-      "ViewerProtocolPolicy": "",
+      "ViewerProtocolPolicy": "redirect-to-https", //httpsへリダイレクト
      "AllowedMethods": {
        "Quantity": 2,
        "Items": ["HEAD", "GET"],
        "CachedMethods": {
          "Quantity": 2,
          "Items": ["HEAD", "GET"]
        }
      },

...省略

    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": true,
      "SSLSupportMethod": "vip",
      "MinimumProtocolVersion": "TLSv1",
      "CertificateSource": "cloudfront"
    },
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "WebACLId": "",
    "HttpVersion": "http2",
    "IsIPV6Enabled": true,
    "ContinuousDeploymentPolicyId": "",
    "Staging": false
  }
- }

修正した設定を反映します。

入力
aws cloudfront update-distribution \
--id "E38CJMA44PEL28" \
--if-match E15DVVP1XE3M3S \
--distribution-config file://dist-config.json

S3のバケット側の修正

次にS3バケットのポリシーを更新します。

AWSコンソールのCloudFrontページに、S3に設定するバケットポリシーを取得できる導線が表示されているのでコピーしてから、前の章で作成したbucket-policy.jsonに貼り付けてください。

S3のポリシーを更新します。

入力
aws s3api put-bucket-policy \
--bucket $BUCKET_NAME \
--policy file://bucket-policy.json

これでこのS3バケットは特定のCloudFront以外からのアクセスを受け付けない状態になり、S3バケットのデータの保護が強化されました。

CloudFrontディストリビューションドメインでアクセスして表示できればOKです!

【3】🪯 Route 53経由

前の章でCloudFrontディストリビューションドメインにHTTPS通信できる状態になりました。

この章では主に以下の対応をします。

  • 独自ドメインのDNSの設定を調整
    • AWS以外のレジストラ(今回は「お名前.com」)で取得した独自ドメインを使用するため、DNSレコードの設定をします
  • 独自ドメイン用のSSL証明書を発行
    • CloudFrontディストリビューションドメインの場合はAWS側が用意してくれてましたが、今回は独自ドメイン用のSSL証明書を自分で作成します

構成図

現在:

リクエスト先 通信経路
CloudFrontディストリビューションドメイン 👤 → 📡 → 🪣

変更後:

リクエスト先 通信経路
独自ドメイン 👤 → 🪯 → 📡 → 🪣

準備

環境変数にセット

ドメイン名はsample.comです(ここは取得したドメインに置き換えてください)。頻繁に登場するのでプロセスの環境変数に入れておきましょう。

入力
DOMAIN_NAME=sample.com
echo $DOMAIN_NAME
出力
sample.com

ドメインのステータスを確認

念のため現在のドメインのステータスを確認しておきましょう。

入力
whois $DOMAIN_NAME | grep status
status:       ACTIVE

💡Tips💡
また、以降のセクションでDNSレコードを変更、CNAMEレコードを追加、Aレコードを追加する工程があるので各種レコードについて気になる方はこちら

https://hp-shizuoka.jp/column/2016/10/7515/

Route 53のホストゾーンを作成

Route 53のホストゾーンを作成します。

ホストゾーンを作成することで、独自ドメイン用のDNSレコードを変更することができます。また、その独自ドメインをインターネット全体からアクセス可能なドメインとするか(パブリックホストゾーン)、もしくはVPC内部など、限定されたネットワーク内でのみ名前解決を行うか(プライベートホストゾーン)などの選択もできます。

今回は外部に公開するドメインのためパブリックホストゾーンになります。

入力
aws route53 create-hosted-zone \
--name $DOMAIN_NAME \
--caller-reference `date +%Y-%m-%d_%H-%M-%S`
出力
{
    "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/Z10195652RQP2MROZA9G9",
    "HostedZone": {
        "Id": "/hostedzone/Z10195652RQP2MROZA9G9",
        "Name": "sample.com.",
        "CallerReference": "2025-02-16_13-42-00",
        "Config": {
            "PrivateZone": false
        },
        "ResourceRecordSetCount": 2
    },
    "ChangeInfo": {
        "Id": "/change/C03241392KU1KV9VB31OQ",
        "Status": "PENDING",
        "SubmittedAt": "2025-02-16T04:42:02.260000+00:00"
    },
    "DelegationSet": {
        "NameServers": [
            "ns-***.awsdns-**.net", //この4つの値を控える
            "ns-***.awsdns-**.org",
            "ns-***.awsdns-**.com",
            "ns-***.awsdns-**.co.uk"
        ]
    }
}

NameServersの値を控えておきましょう。

次のセクションで、お名前.comの管理画面からドメインの名前解決に使うDNSサーバの設定をお名前.comのDNSサーバからAWSのDNSサーバに変更する際に使用します。

https://docs.aws.amazon.com/ja_jp/cli/v1/userguide/cli_route-53_code_examples.html

AWSコンソールで以下のように表示されていればOKです!(DNSサーバはこの画面でも確認できます)

DNSサーバを更新

お名前.comの管理画面でこちらの手順でDNSサーバ名を入力し確認ボタンを押下して更新します。

DNSサーバの切り替えには最大72時間[1]ほどかかる場合がありますが、数分で切り替わるケースも多いので数分経過したらDNSサーバが更新されたか確認してみましょう。

入力
nslookup -type=ns $DOMAIN_NAME
出力
...省略
Authoritative answers can be found from:
ns-***.awsdns-**.net   internet address = ***.***.***.***
ns-***.awsdns-**.org   internet address = ***.***.***.***
ns-***.awsdns-**.co.uk internet address = ***.***.***.***
ns-***.awsdns-**.com    internet address = ***.***.***.***

https://www.winserver.ne.jp/column/about_nslookup/#nslookuptype

次の「ACMでSSL証明書を発行」セクションに進む前に、DNSサーバの更新されていることを必ず確認してください!(DNSサーバの変更が反映される前だと、ACMで発行されるSSL証明書のDNS検証が通りません)

ACMでSSL証明書を発行

独自ドメインでHTTPS通信できるようにするためにACM(AWS Certificate Manager)で独自ドメイン用のSSL証明書を発行します。

💡Tips💡
前の章に登場した「AWSで発行されるSSL証明書」と「ACMで発行する独自ドメイン用のSSL証明書」の違いについて改めて知りたい方はこちら

https://envader.plus/article/424#CloudFrontに独自のSSL証明書を使う意味とは

オプションの説明

  • --region us-east-1
    • CloudFrontにアタッチするACMの発行は、2025年3月時点ではバージニアリージョンで作成する必要があるため
  • --validation-method DNS
    • SSL証明書が対応する独自ドメインが合っているかの照合方法としてDNSを指定
      • 具体的にはSSL証明書発行時に発行されるトークンをRoute 53のホストゾーンのCNAMEに追加される
      • 初回発行時と自動更新時にドメインの所有者があっているか、まだ使われているかなどを確認します
入力
aws acm request-certificate \
--domain-name $DOMAIN_NAME \
--validation-method DNS \
--region us-east-1
出力
{
    "CertificateArn": "arn:aws:acm:ap-northeast-1:************:certificate/d22d495f-a022-4a06-b25a-7027e3faf187"
}

💡Tips💡
ACMで発行するSSL証明書のDNS検証についてはこちら

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/dns-validation.html

Route 53にDNS検証用のCNAMEレコードを追加

ACMのAWSコンソールからRoute 53でレコードを作成ボタンを押下してください。

(ACMでSSL証明書を発行すると自動で行われる場合もありますが、操作も簡単ですので手動で行っておくと安心です)

Route 53のAWSコンソールにCNAMEレコードが追加されていればOKです!

これでACMで発行したSSL証明書が独自ドメインの所有権を確認することができるようになりました。しばらくして、作成したACMのステータスが「成功」に切り替わればOKです。

CloudFrontディストリビューションのSSL証明書を変更

現在CloudFrontディストリビューションには、前の章で自動で作成されていたAWSのSSL証明書がアタッチされている状態です。そのため、独自ドメイン用のSSL証明書に変更する対応していきます。

CloudFrontディストリビューションの設定変更

  • 代替ドメインの設定
    • 「ユーザーがリクエストしたドメイン名」と「SSL証明書のドメイン名」は一致している必要があるため、CloudFrontディストリビューションには、代替ドメインとして独自ドメインを設定します
  • SSL証明書(Certificate)の変更
    • デフォルトのCloudFrontの証明書からACMで作成した証明書に変更します

💡Tips💡
CloudFrontの代替ドメインの詳細が気になる方はこちら

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/CreatingCNAME.html

まずは、CloudFrontディストリビューションの設定を取得します。

入力
aws cloudfront get-distribution-config --id E38CJMA44PEL28 > dist-config.json

設定を変更し、代替ドメインの設定とSSL証明書の差し替えを行います。

dist-config.json
{
-    "ETag": "E10PV3YI2O3MV7",
-    "DistributionConfig": {
        "CallerReference": "cli-1739612759-610898",
        "Aliases": {
-            "Quantity": 0,
+            "Quantity": 1,
+            "Items": [
+                "sample.com"
+            ]
        },
        "DefaultRootObject": "index.html",
        "Origins": {
            "Quantity": 1,
            "Items": [
                {
                    "Id": "sample-app-20250215-s3.s3.amazonaws.com-1739612759-135608",
                    "DomainName": "sample-app-20250215-s3.s3.amazonaws.com",
                    "OriginPath": "",
                    "CustomHeaders": {
                        "Quantity": 0
                    },
                    "S3OriginConfig": {
                        "OriginAccessIdentity": ""
                    },
                    "ConnectionAttempts": 3,
                    "ConnectionTimeout": 10,
                    "OriginShield": {
                        "Enabled": false
                    },
                    "OriginAccessControlId": "E10I7FMDP8WWQZ"
                }
            ]
        },
        "OriginGroups": {
            "Quantity": 0
        },
        "DefaultCacheBehavior": {
            "TargetOriginId": "sample-app-20250215-s3.s3.amazonaws.com-1739612759-135608",
            "TrustedSigners": {
                "Enabled": false,
                "Quantity": 0
            },
            "TrustedKeyGroups": {
                "Enabled": false,
                "Quantity": 0
            },
            "ViewerProtocolPolicy": "redirect-to-https",
            "AllowedMethods": {
                "Quantity": 2,
                "Items": [
                    "HEAD",
                    "GET"
                ],
                "CachedMethods": {
                    "Quantity": 2,
                    "Items": [
                        "HEAD",
                        "GET"
                    ]
                }
            },

...省略

        "PriceClass": "PriceClass_All",
        "Enabled": true,
        "ViewerCertificate": {
-            "CloudFrontDefaultCertificate": true,
+            "CloudFrontDefaultCertificate": false,
+            "ACMCertificateArn": "arn:aws:acm:us-east-1:************:certificate/045d218f-5dd3-4c9e-a58c-c7bda86cd8ee",
-            "SSLSupportMethod": "vip",
+            "SSLSupportMethod": "sni-only",
-            "MinimumProtocolVersion": "TLSv1",
+            "MinimumProtocolVersion": "TLSv1.2_2021",
+            "Certificate": "arn:aws:acm:us-east-1:************:certificate/045d218f-5dd3-4c9e-a58c-c7bda86cd8ee",
-            "CertificateSource": "cloudfront"
+            "CertificateSource": "acm"
        },
        "Restrictions": {
            "GeoRestriction": {
                "RestrictionType": "none",
                "Quantity": 0
            }
        },
        "WebACLId": "",
        "HttpVersion": "http2",
        "IsIPV6Enabled": true,
        "ContinuousDeploymentPolicyId": "",
        "Staging": false
    }
- }

設定を更新します。

入力
aws cloudfront update-distribution \
--id "E38CJMA44PEL28" \
--if-match E15DVVP1XE3M3S \
--distribution-config file://dist-config.json

CloudFrontディストリビューションのAWSコンソールで以下になっていればOKです!

これでCloudFrontディストリビューションにACMで発行したSSL証明書の設定を設定できました!

独自ドメインをCloudFrontディストリビューションにルーティング

最後に、ユーザーからの独自ドメインのリクエストをCloudFrontディストリビューションにルーティングされるように、Route 53にAレコードを追加していきます。

入力
aws route53 change-resource-record-sets \
  --hosted-zone-id Z10195652RQP2MROZA9G9 \
  --change-batch '{
    "Comment": "Point sample.com to CloudFront distribution",
    "Changes": [
      {
        "Action": "UPSERT",
        "ResourceRecordSet": {
          "Name": "sample.com",  //独自ドメイン
          "Type": "A", //レコードタイプ
          "AliasTarget": {
            "HostedZoneId": "Z2FDTNDATAQYW2",
            "DNSName": "d2bf00g4mnnvjx.cloudfront.net", //対応先のアドレス
            "EvaluateTargetHealth": false
          }
        }
      }
    ]
  }'

Route 53のAWSコンソール上でAレコードが追加されていればOKです!

独自ドメインでアクセスして表示できればOKです!

https://iret.media/70224

【おまけ】🔥 WAFの設定

現状把握

現時点でユーザーが独自ドメインにアクセスした場合、ざっくりですが以下の経路でアプリを閲覧することができます。

  1. 👤 ブラウザから独自ドメインでアクセス開始
  2. 👤 DNSクエリが発行
  3. 🪯 Route 53で以下の名前解決が行われる
    • sample.comxxxxxx.cloudfront.netIPアドレス
    • TCP/IPパケットのIPヘッダにIPアドレスが記述されてユーザーに返される
  4. 👤 HTTPリクエストをする
    • 通信先はIPアドレス
    • hostヘッダにsample.comがセットされている
  5. 📡 CloudFrontがS3バケットからアプリを取得してユーザーに配信
    • 2回目以降はキャッシュデータを返す

課題点

上記の工程自体に問題はないのですが、現時点だと「1. 👤 ブラウザから独自ドメインでアクセス開始」の工程で、ブラウザのアドレスバーにxxxxxx.cloudfront.netを打ち込んでもアプリを取得できてしまいます。

また、その場合は「3. 👤 HTTPリクエストをする」のhostヘッダーにはxxxxxx.cloudfront.netが入ります

方針

今回はWAF(Web Application Firewall)を使用して、HTTPリクエストのヘッダ情報を検査し、「あるルールに一致したHTTPリクエストだけ」ブロックする方針を取ります。

💡Tips💡
WAFとファイアウォールの用途や検査内容の違いはこちら

https://www.shadan-kun.com/waf_websecurity/waf_firewall_difference/#03_2:~:text=WAFも紹介!-,検査する内容が違う,-WAFもファイア

構成図

Web ACLを作成

それでは、HTTPリクエストのhostヘッダーにcloudfront.netという文字が含まれていた場合、ブロックするというルールを作成していきます。

create-web-acl.json
{
  "Name": "sample-acl",
  "Scope": "CLOUDFRONT",
  "DefaultAction": { "Allow": {} },
  "Description": "Block requests if host header contains cloudfront.net",
  "Rules": [
    {
      "Name": "sample-acl-rule",
      "Priority": 0,
      "Statement": {
        "ByteMatchStatement": {
          "SearchString": "Y2xvdWRmcm9udC5uZXQ=", //「cloudfront.net」の文字列をBase64にエンコード
          "FieldToMatch": { "SingleHeader": { "Name": "host" } },
          "TextTransformations": [
            { "Priority": 0, "Type": "NONE" }
          ],
          "PositionalConstraint": "CONTAINS"
        }
      },
      "Action": { "Block": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "sample-acl-metric"
      }
    }
  ],
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "BlockCloudFrontDomainACL"
  }
}
入力
aws wafv2 create-web-acl \
--region us-east-1 \
--cli-input-json file://create-web-acl.json

CloudFrontのAWSコンソールからCloudFrontの設定を更新します。

WAFのAWSコンソールを確認し、Web ACLのリソースにCloudFrontが追加されていればOKです!

これでブラウザなどから直接CloudFrontディストリビューションドメインでアクセスしようとしているHTTPリクエストをブロックする仕組みができました!(独自ドメインではアクセスできることも併せてご確認ください)

💡Tips💡
WAFではなくCloudFront Functionsで制御する方法はこちら

https://dev.classmethod.jp/articles/cloudfornt-block-default-domain-access-using-cf2/

ハンズオンの片付け

AWSリソースは関連しているリソースが存在している場合は、削除できない場合があります。そのため、依存先の設定やリソースから削除していきましょう。

AWSコンソール上で以下の順番で設定やリソースの削除をしていくと、スムーズに削除できますので参考にしてください。

  1. お名前.com (※必要な方のみ)
    • お名前.comの管理画面でDNSを戻す
  2. WAF
    • 保護リソースからCloudfrontをデタッチ
    • リソース自体を削除
  3. Route 53
    • Aレコードを削除
  4. Cloudfront
    • 代替ドメインを削除
    • ACMをデタッチ
  5. Route 53
    • (ACM用の)CNAMEレコードを削除
  6. ACM
    • リソース自体を削除
  7. S3
    • ポリシーを削除
  8. Cloudfront
    • リソース自体を無効化
      • 最終変更日がタイムスタンプになるまで待つ
    • リソース自体を削除
    • OAC(オリジンアクセス)を削除
  9. S3
    • バケットの中身を空にする
    • リソース自体を削除
  10. Route 53
    • ホストゾーンを削除

まとめ

静的ウェブサイトホスティングの記事は多くありますが、AWS CLIの記事が少なかったので今回は「AWS CLIを使った静的ウェブサイトホスティングのハンズオン」を作成してみました。

リソース同士を関連づける設定などはAWSコンソールで操作するとワンアクションで処理してくれることも多いですが、AWS CLIは各リソースを個別に操作するためリソースを直接操作する感覚に近い気がしました。

AWS CLIで実装すると「あれ?ミニマムだとこの設定無くてもよかったんだ」とか「AWS側ではこの設定を一緒に書き換えていたのか」など小さいけど大切な発見がたくさんあるので、是非このハンズオンを機に積極的に取り入れてみてはどうでしょうか?

次回は、この静的ウェブサイトホスティングで公開しているアプリにフォーム機能を実装し、AWS CLIを使用してAmazon SESとLambdaとAPI Gatewayを構築することで、より実用的なWebアプリに仕上げる予定です✨

最後まで見ていただきありがとうございました。

脚注
  1. 【ドメイン】DNS設定の変更手続きをしてから有効になるまでの期間は? ↩︎

Gemcook Tech Blog
Gemcook Tech Blog

Discussion