Terraform個人メモ：S3 + CloudFront + Route 53 + ACM（バージニア北部）構成を整理してみた

## はじめに
本記事では、AWS の S3 バケットを CloudFront 経由で提供するための Terraform 設定について、各コードの詳細な解説を交えて紹介します。

S3 を単体で公開するとセキュリティリスクが高まるため、CloudFront を活用して安全かつ高速なコンテンツ配信を行う設定を紹介します。

## 事前準備

この設定を行う前に、以下の準備が必要です。

### 1. バージニア北部（us-east-1）で ACM 証明書の取得

CloudFront で HTTPS を利用するためには、証明書を バージニア北部（us-east-1） で取得する必要があります。

https://qiita.com/free-honda/items/0d16a3293bae45e5ae9f

### 2. Route 53 の設定

ACM 証明書の DNS 検証や CloudFront のエイリアス設定には、Route 53 にドメインを登録しておく必要があります。

ドメインのホストゾーンを作成後、続いて、ACM の検証レコードを追加します。


## 1. S3 バケットの作成

```hcl
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-cloudfront-bucket-tokyo"
  acl    = "private"

  tags = {
    Name        = "MyS3Bucket"
    Environment = "Production"
  }
}
```

この設定では、S3 バケット `my-cloudfront-bucket-tokyo` を作成し、`acl` を `private` に設定することで、直接アクセスできないようにします。タグを追加することで、環境識別が容易になります。

## 2. S3 のウェブホスティング設定

```hcl
resource "aws_s3_bucket_website_configuration" "my_bucket_website" {
  bucket = aws_s3_bucket.my_bucket.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}
```

この設定により、S3 のウェブサイトホスティング機能を有効にし、デフォルトの `index.html` および `error.html` を指定しています。

## 3. CloudFront オリジンアクセスアイデンティティ（OAI）の設定

```hcl
resource "aws_cloudfront_origin_access_identity" "my_oai" {
  comment = "OAI for S3 bucket"
}
```

CloudFront から S3 にアクセスするための OAI（オリジンアクセスアイデンティティ）を作成します。この OAI を S3 のバケットポリシーで使用します。

## 4. S3 バケットポリシーの設定

```hcl
resource "aws_s3_bucket_policy" "my_bucket_policy" {
  bucket = aws_s3_bucket.my_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect    = "Allow",
        Principal = {
          AWS = aws_cloudfront_origin_access_identity.my_oai.iam_arn
        },
        Action   = "s3:GetObject",
        Resource = "${aws_s3_bucket.my_bucket.arn}/*"
      }
    ]
  })
}
```

このバケットポリシーでは、CloudFront の OAI のみが S3 からオブジェクトを取得できるように設定します。これにより、S3 への直接アクセスを防ぎ、安全性を確保できます。

## 5. HTML ファイルの作成とアップロード

```hcl
resource "local_file" "index_html" {
  content  = "<html><body><h1>React in S3</h1></body></html>"
  filename = "index.html"
}

resource "aws_s3_object" "index_file" {
  bucket       = aws_s3_bucket.my_bucket.id
  key          = "index.html"
  source       = local_file.index_html.filename
  content_type = "text/html"
  acl          = "private"
}
```

ローカルで `index.html` を作成し、Terraform を使って S3 にアップロードします。`acl` を `private` にすることで、CloudFront 経由でのみアクセス可能になります。

## 6. CloudFront ディストリビューションの作成

```hcl
resource "aws_cloudfront_distribution" "my_distribution" {
  origin {
    domain_name = aws_s3_bucket.my_bucket.bucket_regional_domain_name
    origin_id   = "S3-my-cloudfront-bucket"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.my_oai.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "CloudFront Distribution for S3 bucket in Tokyo region"
  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-my-cloudfront-bucket"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  price_class = "PriceClass_100"

  viewer_certificate {
    acm_certificate_arn      = "arn:aws:acm:us-east-1:xxx"
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  aliases = ["xxx.xxx"]

  tags = {
    Name        = "MyCloudFrontDistribution"
    Environment = "Production"
  }
}
```

この設定では、CloudFront を有効化し、S3 をオリジンとして設定します。HTTPS のリダイレクトや、独自ドメイン `honda333.blog` のエイリアスも指定しています。

## 7. Route 53 の DNS 設定

```hcl
resource "aws_route53_record" "cloudfront" {
  zone_id = "xxx"
  name    = "xxx"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.my_distribution.domain_name
    zone_id                = aws_cloudfront_distribution.my_distribution.hosted_zone_id
    evaluate_target_health = false
  }
}
```

## 実際に完成したコード

```main.tf
# S3 バケットの定義（CloudFront 経由でアクセスするためのバケット）
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-cloudfront-bucket-tokyo"
  acl    = "private" # バケットは非公開に設定（CloudFront からのアクセスのみ許可）

  tags = {
    Name        = "MyS3Bucket"
    Environment = "Production"
  }
}

# S3 バケットのウェブホスティング設定（index.html と error.html を指定）
resource "aws_s3_bucket_website_configuration" "my_bucket_website" {
  bucket = aws_s3_bucket.my_bucket.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

# CloudFront 用オリジンアクセスアイデンティティ（OAI）の定義（S3 へのセキュアなアクセスを許可）
resource "aws_cloudfront_origin_access_identity" "my_oai" {
  comment = "OAI for S3 bucket"
}

# S3 バケットポリシーの設定（CloudFront OAI のみ S3 へのアクセスを許可）
resource "aws_s3_bucket_policy" "my_bucket_policy" {
  bucket = aws_s3_bucket.my_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect    = "Allow",
        Principal = {
          AWS = aws_cloudfront_origin_access_identity.my_oai.iam_arn
        },
        Action   = "s3:GetObject",
        Resource = "${aws_s3_bucket.my_bucket.arn}/*"
      }
    ]
  })
}

# 簡易的な HTML ファイル（index.html）の作成
resource "local_file" "index_html" {
  content  = "<html><body><h1>React in S3</h1></body></html>"
  filename = "index.html"
}

# index.html を S3 バケットにアップロード
resource "aws_s3_object" "index_file" {
  bucket       = aws_s3_bucket.my_bucket.id
  key          = "index.html"
  source       = local_file.index_html.filename
  content_type = "text/html"
  acl          = "private" # オブジェクトは非公開（CloudFront 経由でのみアクセス可能）
}

# CloudFront ディストリビューションの定義（S3 バケットをオリジンとする）
resource "aws_cloudfront_distribution" "my_distribution" {
  origin {
    domain_name = aws_s3_bucket.my_bucket.bucket_regional_domain_name
    origin_id   = "S3-my-cloudfront-bucket"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.my_oai.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "CloudFront Distribution for S3 bucket in Tokyo region"
  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-my-cloudfront-bucket"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  price_class = "PriceClass_100"

  viewer_certificate {
    acm_certificate_arn      = "arn:aws:acm:us-east-1:xxx"
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  aliases = ["xxx.xxx"]

  tags = {
    Name        = "MyCloudFrontDistribution"
    Environment = "Production"
  }
}

# Route 53 に CloudFront へのエイリアスレコードを追加
resource "aws_route53_record" "cloudfront" {
  zone_id = "xxx"
  name    = "xxx.xxx"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.my_distribution.domain_name
    zone_id                = aws_cloudfront_distribution.my_distribution.hosted_zone_id
    evaluate_target_health = false
  }
}
```


Route 53 に CloudFront のエイリアスレコードを追加し、独自ドメインでのアクセスを可能にします。

## まとめ
本記事では、S3 バケットを CloudFront 経由で提供するための Terraform 設定を詳しく解説しました。

:::note info
***CloudFront の OAI を利用することで、S3 への直接アクセスを防ぎ、安全なウェブホスティング環境を構築できます。***
:::

Terraform を活用して AWS リソースを効率的に管理し、セキュアなシステムを構築してみましょう！