背景
この記事にある「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,
)
ポイント解説:
website_index_document
を指定すると、S3の静的ウェブサイトホスティングが有効になります。block_public_access
はドキュメントなどを参考にするとこの設定を無効化する流れですが、無効にするパラメーターがないのでBLOCK_ACLS
を選択しています。public_read_access
は、バケットポリシーを設定し、パブリックなアクセスが可能にします。- 静的ウェブサイトは
custom_origin_source
で設定し、bucket_website_domain_name
パラメーターをセットします。 - CloudFrontとS3静的ウェブサイト間の通信はHTTPのみなので、
origin_protocol_policy
はHTTP_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 ヘッダーでアクセスを制限する」を参考にすれば、ある程度は制限できると思います。