S3 静的ウェブサイトホスティングとCloudFrontを使ったwebサイトをCDKで実装する

AWS

背景

この記事にある「REST API エンドポイントをオリジンとして使用し、OAC または OAI (レガシー) でアクセスを制限する」に該当するCDK実装パターンが多い一方、「ウェブサイトのエンドポイントをオリジンとして使用し、匿名 (パブリック) アクセスを許可する」の実装パターンが見つけにくく、その点で躓いたため、記事を作成します。

S3の静的ウェブサイトホスティングを選んだ理由は、「インデックスドキュメント」を設定したかったからです。

実装

下記のコードを参照ください。(主題以外のコードも含まれています)

from aws_cdk import (
    Stack,
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
    aws_s3_deployment as s3_deployment,
    RemovalPolicy,
)
from constructs import Construct

class CdkS3StaticWebHostiong1Stack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        s3_b = s3.Bucket(
            self, "S3Bucket1",
            website_index_document="index.html",
            block_public_access=s3.BlockPublicAccess.BLOCK_ACLS,
            public_read_access=True,
            auto_delete_objects=True,
            removal_policy=RemovalPolicy.DESTROY,
        )

        website_cfwd = cloudfront.CloudFrontWebDistribution(
            self, "CloudFrontDistribution1",
            origin_configs=[
                cloudfront.SourceConfiguration(
                    custom_origin_source=cloudfront.CustomOriginConfig(
                        domain_name=s3_b.bucket_website_domain_name,
                        origin_protocol_policy=cloudfront.OriginProtocolPolicy.HTTP_ONLY,
                    ),
                    behaviors=[
                        cloudfront.Behavior(
                            path_pattern='s3_static_website/*',
                        )
                    ]
                ),
				# マルチオリジンでのパスパターンの内容を補足したいため記述している
                cloudfront.SourceConfiguration(
                    custom_origin_source=cloudfront.CustomOriginConfig(
                        domain_name='docs.aws.amazon.com',
                        origin_protocol_policy=cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
                    ),
                    behaviors=[
                        cloudfront.Behavior(
                            is_default_behavior=True,
                        )]
                ),
            ]
        )

        s3_deployment.BucketDeployment(
            self, "DeployWebsite",
            sources=[s3_deployment.Source.asset("website")],
            destination_bucket=s3_b,
            destination_key_prefix="s3_static_website",
            distribution=website_cfwd,
        )

ポイント解説:

  1. website_index_document を指定すると、S3の静的ウェブサイトホスティングが有効になります。
  2. block_public_access はドキュメントなどを参考にするとこの設定を無効化する流れですが、無効にするパラメーターがないので BLOCK_ACLS を選択しています。
  3. public_read_access は、バケットポリシーを設定し、パブリックなアクセスが可能にします。
  4. 静的ウェブサイトはcustom_origin_source で設定し、 bucket_website_domain_name パラメーターをセットします。
  5. CloudFrontとS3静的ウェブサイト間の通信はHTTPのみなので、 origin_protocol_policyHTTP_ONLY をセットします。

躓きポイント

ブロックパブリックアクセスを無効化するために、 block_public_access=None や未定義にして検証したときに、下記エラーが発生しました。

CdkS3StaticWebHostiong2Stack: deploying... [1/1]
CdkS3StaticWebHostiong2Stack: creating CloudFormation changeset...
6:11:11 PM | CREATE_FAILED        | AWS::S3::BucketPolicy       | S3Bucket1Policy97F49B82
Resource handler returned message: "Access Denied (Service: S3, Status Code: 403, Request ID: 4K7BXE0KG4A9V0EE, Extended Request ID: aHn/vhufNZQS+8/3AuH5mGnm6Pv3/uvS5FtDhmxffCE1za5ySlL0lRxzg
5Ac6ZDrmDnlFH87Sbc=)" (RequestToken: d15a6f88-eb6f-732e-ace7-a65b9ad2256e, HandlerErrorCode: AccessDenied)

未定義にすると、Cfnのデフォルトがセットされます(つまり現時点では全て有効化になります)。

その状態で public_read_access をセットすると、エラーが発生すると推測しています。

そのため既出のような実装になりました(参考情報)。

その他

今回のホスティング用のS3バケットであれば、下記の設定を掛けておくとゴミが残らないので良いと思います。

auto_delete_objects=True,
removal_policy=RemovalPolicy.DESTROY,

Behaviorの path_pattern='s3_static_website/*', とS3のキー destination_key_prefix="s3_static_website", が一致していないと正しく表示されません。当初、S3のprefixを設定せずに実装し、それで躓きました(まさにこの記事)。

BucketDeploymentのdistributionを設定すると、CDKデプロイ時に画像の通り適切なキャッシュを削除してくれます。

今回の構成では、S3の静的ウェブサイトのエンドポイントでもアクセスできます。これを制御したい場合は、ウェブサイトのエンドポイントをオリジンとして使用し、Referer ヘッダーでアクセスを制限するを参考にすれば、ある程度は制限できると思います。

無料相談実施中
AWSを使用したサーバーレスアプリケーションの構築
サーバーレス開発内製化、AWSへの移行等
様々な課題について無料でご相談お受けいたします。
最新情報をチェックしよう!