はじめに
AWSで静的Webサイトを構築する際は、CloudFront + S3の構成が一般的かと思います。
S3のバケットポリシーとCloudFrontのカスタムエラーレスポンスを組み合わせて、
特定の期間内のみアクセスを受け付け、
期間外はエラーページを表示するサイトを構築しました。
AWS環境はTerraformで作成します。
参考:
実行環境
- macOS Ventura 13.2.1
- Terraform v1.5.2
- aws-cli 2.9.12
環境構築
構成はCloudFrontディストリビューションが1つに、S3バケットが2つです。
1つのS3バケット(公開用バケット)に公開用ページを格納し、
もう1つ(エラー用バケット)にエラーページを格納します。
公開用ページは公開用バケットのapp_page/index.html
に配置し、
エラーページはエラー用バケットのerror/error.html
に配置します。
CloudFrontのオリジンには両方を指定します。
CloudFrontをマルチオリジンにする際の注意点はこちらを参考に
S3
まずはS3バケットの作成です。2つ用意します。
# 公開用
resource "aws_s3_bucket" "s3_bucket" {
bucket = "open-bucket"
}
# エラー用
resource "aws_s3_bucket" "s3_standby_bucket" {
bucket = "error-bucket"
}
次にS3のバケットポリシーを作成します。
ここで公開用バケットのポリシーにアクセスを許可する日時を指定することで、
期間外にアクセスした場合は403 forbidden
が返るようになります。
# 公開用
resource "aws_s3_bucket_policy" "s3_bucket" {
bucket = aws_s3_bucket.s3_bucket.id
policy = data.aws_iam_policy_document.s3_policy.json
}
data "aws_iam_policy_document" "s3_policy" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.s3_bucket.arn}/*"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn]
}
# アクセスを許可する期間を指定
condition {
test = "DateGreaterThan"
variable = "aws:CurrentTime"
# 標準時なので公開開始の時間-9時間にする
values = ["2023-7-11T15:00:00Z"] # 公開開始 7/12 0:00
}
condition {
test = "DateLessThan"
variable = "aws:CurrentTime"
values = ["2023-7-12T06:00:00Z"] # 公開終了 7/12 15:00
}
}
}
# エラー用
resource "aws_s3_bucket_policy" "s3_standby_bucket" {
bucket = aws_s3_bucket.s3_standby_bucket.id
policy = data.aws_iam_policy_document.s3_standby_policy.json
}
data "aws_iam_policy_document" "s3_standby_policy" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.s3_standby_bucket.arn}/*"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn]
}
}
}
CloudFront
OriginAccessIdentity
resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
comment = "origin access identity for s3"
}
ディストリビューション
まずは公開用の設定
公開ファイルのパスがapp_page/index.html
なので、
default_root_object
をindex.html
に、
origin
ブロックのorigin_path
を/app_page
にします。
その他の設定はご自身の環境に合わせてください。
resource "aws_cloudfront_distribution" "cf_dist" {
comment = "cf-comment"
default_root_object = "index.html"
enabled = "true"
http_version = "http2"
is_ipv6_enabled = "true"
retain_on_delete = "false"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
compress = "false"
default_ttl = "86400"
forwarded_values {
cookies {
forward = "none"
}
query_string = "false"
}
max_ttl = "31536000"
min_ttl = "0"
smooth_streaming = "false"
target_origin_id = aws_s3_bucket.s3_bucket.id
viewer_protocol_policy = "redirect-to-https"
}
origin {
domain_name = aws_s3_bucket.s3_bucket.bucket_regional_domain_name
origin_id = aws_s3_bucket.s3_bucket.id
origin_path = "/app_page"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
# 以下にエラーページ表示用の設定を追加
次にエラーページ表示用の設定
custom_error_response
ブロックを追加し、
エラーコード403をキャッチしたら/error/error.html
を返すようにします。
また、ordered_cache_behavior
ブロックを追加します。
path_pattern
に/error/*
を設定し、
/error
へのアクセスはエラー用バケットを見に行くようにします。
resource "aws_cloudfront_distribution" "cf_dist" {
# 省略
custom_error_response {
error_caching_min_ttl = "0"
error_code = "403"
response_code = "200"
response_page_path = "/error/error.html"
}
origin {
domain_name = aws_s3_bucket.s3_standby_bucket.bucket_regional_domain_name
origin_id = aws_s3_bucket.s3_standby_bucket.id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path
}
}
ordered_cache_behavior {
allowed_methods = [
"GET",
"HEAD",
]
cached_methods = [
"GET",
"HEAD",
]
compress = false
default_ttl = 86400
max_ttl = 86400
min_ttl = 0
path_pattern = "/error/*"
smooth_streaming = false
target_origin_id = aws_s3_bucket.s3_standby_bucket.id
trusted_signers = []
viewer_protocol_policy = "https-only"
forwarded_values {
cookies {
forward = "none"
}
query_string = "false"
}
}
}
AWSの設定は以上です。
S3にコンテンツを配置
公開用バケットの/app_page/index.html
と
エラーバケットのerror/error.html
にそれぞれコンテンツを配置しましょう。
動作確認
CloudFrontのURLにアクセスすると、公開期間内には公開用バケットのコンテンツが表示され、
期間外はエラーページが表示されるはずです。
つまづきポイント
何回か失敗して試していると、CloudFrontのキャッシュが残っていて、
設定はあっているのに正常に表示されないことがあったので、
設定変更する度にキャッシュを削除することをおすすめします。
なお、公開開始前→開始後でキャッシュが残ってエラーページがずっと表示されてしまう、
といったことは心配しなくてよさそうです。
正常なレスポンスとエラーレスポンスのキャッシュは分けられているようなので、
デフォルトキャッシュのTTLに長い時間を設定していても問題ありません。
公開停止前→停止後の場合も同様です。
おわりに
イベントの期間だけアクセスできるようにしたい、
深夜の公開作業を自動で行いたい、など、
期間指定で公開したい場合って結構あるのではと思います。
参考になれば幸いです。