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