2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TerraformでCloudFront + S3静的サイトの構成テンプレート

Posted at

CloudFront + S3での静的サイトをTerraformで実装するためのテンプレートです。

以前に似た記事を書きました。

CloudFrontのログのためのS3バケット作成も含めています。

Terraformのファイル

main.tf
variable aws_profile {}
variable aws_region {}
variable resource_prefix {}

provider "aws" {
   profile = var.aws_profile
   region = var.aws_region
}

data "aws_canonical_user_id" "current" {}

################################################################################
# S3 for static website
################################################################################

resource "aws_s3_bucket" "static_website" {
  bucket = "${var.resource_prefix}-static-website"
}

resource "aws_s3_bucket_policy" "static_website" {
    bucket = aws_s3_bucket.static_website.id
    policy = data.aws_iam_policy_document.static_website.json
}

data "aws_iam_policy_document" "static_website" {
  statement {
    sid = "Allow CloudFront"
    effect = "Allow"
    principals {
        type = "AWS"
        identifiers = [
          aws_cloudfront_origin_access_identity.static_website.iam_arn,
        ]
    }
    actions = [
        "s3:GetObject"
    ]

    resources = [
        "${aws_s3_bucket.static_website.arn}/*"
    ]
  }
}

################################################################################
# S3 for CloudFront logs
################################################################################

resource "aws_s3_bucket" "logs" {
  bucket = "${var.resource_prefix}-cloudfront-log"
}

# 今S3のIaCで「AccessControlListNotSupported: The bucket does not allow ACLs」というエラーが出たならそれは2023年4月に行われたS3の仕様変更が原因かもしれない | DevelopersIO
# https://dev.classmethod.jp/articles/s3-acl-error-from-202304/
resource "aws_s3_bucket_ownership_controls" "logs" {
  bucket = aws_s3_bucket.logs.id
  rule {
    object_ownership = "ObjectWriter"
  }
}

resource "aws_s3_bucket_acl" "logs" {
  bucket = aws_s3_bucket.logs.id

  access_control_policy {
    # This is the CloudFront log delivery group
    # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#access-logs-granting-permissions-to-cf-to-put-object-in-s3
    grant {
      grantee {
        id          = data.aws_canonical_user_id.current.id
        type        = "CanonicalUser"
      }
      permission = "FULL_CONTROL"
    }
    grant {
      grantee {
        id          = "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0" # awslogsdelivery 
        type        = "CanonicalUser"
      }
      permission = "FULL_CONTROL"
    }
    owner {
      id = data.aws_canonical_user_id.current.id
    }
  }
}

################################################################################
# CloudFront
################################################################################

resource "aws_cloudfront_distribution" "static_website" {
  origin {
    domain_name = aws_s3_bucket.static_website.bucket_regional_domain_name
    origin_id = aws_s3_bucket.static_website.id
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.static_website.cloudfront_access_identity_path
    }
  }

  enabled =  true

  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods = [ "GET", "HEAD" ]
    cached_methods = [ "GET", "HEAD" ]
    target_origin_id = aws_s3_bucket.static_website.id

    forwarded_values {
      query_string = true
      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl = 60
    default_ttl = 60
    max_ttl = 60
    compress = true

    # レスポンスのContent-Typeを書き換えるためのCloudFront Function
    function_association {
      event_type   = "viewer-response"
      function_arn = aws_cloudfront_function.response.arn
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations = []
    }
  }
  viewer_certificate {
    cloudfront_default_certificate = true
  }
  logging_config {
    bucket = aws_s3_bucket.logs.bucket_domain_name
  }
}

resource "aws_cloudfront_origin_access_identity" "static_website" {

}

# レスポンスのContent-Typeを書き換えるためのCloudFront Function
resource "aws_cloudfront_function" "response" {
  name    = "${var.resource_prefix}-response"
  runtime = "cloudfront-js-1.0"
  code    = file("response.js")
}

output "website_url" {
  value = "https://${aws_cloudfront_distribution.static_website.domain_name}/"
}

################################################################################

CloudFront Functions

ファイルをローカルからS3にawscliなどでアップすると、S3オブジェクトの Content-Type が binary/octet-stream になってしまいます。それをCloudFrontで配信すると、ブラウザはHTMLファイルであってもバイナリファイルと認識してしまうダウンロードする挙動になります。

S3にあるオブジェクトの Content-Type を text/html など正しい値に設定するのが正しいやり方でしょうが、それが面倒です。

そこで、CloudFront Functionsでレスポンスを加工する構成にしています。

加工処理は response.js に書きます。Terraformを実行するディレクトリに置きます。CloudFront FunctionsではJavaScriptが動きます。

response.js
function handler(event) {
  var response = event.response;
  var headers = response.headers;

  var path = event.request.uri;
  var p = path.lastIndexOf('.');
  var extension;
  if (p > 0) {
    extension = path.substring(p + 1);
  } else {
    extension = "";
  }
  if (path.endsWith("/") || extension == "html") {
    headers["content-type"] = {value: "text/html"};
  } else if (extension == "js") {
    headers["content-type"] = {value: "text/javascript"};
  } else if (extension == "css") {
    headers["content-type"] = {value: "text/css"};
  } else if (extension == "png") {
    headers["content-type"] = {value: "image/png"};
  } else if (extension == "jpg") {
    headers["content-type"] = {value: "image/jpeg"};
  } else if (extension == "gif") {
    headers["content-type"] = {value: "image/gif"};
  } else if (extension == "txt") {
    headers["content-type"] = {value: "text/plain"};
  }

  return response;
}

拡張子で Content-Type を判断してしまいます。すべての拡張子を網羅することはできませんので、場当たり的な解決策であることに注意してください。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?