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 にドメインを登録しておく必要があります。**

https://qiita.com/free-honda/items/8fb5124ae36616d64dd1

https://qiita.com/free-honda/items/1d8810054b556739afaf#route-53-%E3%81%AE%E3%83%9B%E3%82%B9%E3%83%88%E3%82%BE%E3%83%BC%E3%83%B3%E3%81%AB-a-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0

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

### 3. GitHub Actions の設定

フロントエンドのプロジェクトを効率的にデプロイするために、GitHub Actions を活用して AWS S3 への自動デプロイを実現しています。

https://qiita.com/free-honda/items/107db9df49e38245462a

## S3 バケットの作成
まず、CloudFront 経由でアクセスするための S3 バケットを作成します。S3 のアクセスコントロールリスト（ACL）は `private` に設定し、直接のアクセスを防ぎます。

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

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

## S3 のウェブホスティング設定
`index.html` と `error.html` を定義し、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"
  }
}
```

## CloudFront 用オリジンアクセスアイデンティティ（OAI）の作成
CloudFront から S3 にセキュアにアクセスするために、OAI を作成します。

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

## S3 バケットポリシーの設定
S3 のポリシーを設定し、CloudFront OAI のみがアクセスできるようにします。

```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}/*"
      }
    ]
  })
}
```

## 静的 HTML ファイルの作成とアップロード
サンプルとして `index.html` を作成し、S3 にアップロードします。

```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"
}
```

## CloudFront ディストリビューションの作成
CloudFront の設定を行い、S3 をオリジンとして利用します。

```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"
    origin_path = "/build/client"

    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:881490128743:certificate/5ea024fe-07e4-4083-b3a6-0285be3d24a7"
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  aliases = ["honda333.blog"]

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

## Route 53 の設定
最後に、CloudFront を指す Route 53 のエイリアスレコードを作成します。

```hcl
resource "aws_route53_record" "cloudfront" {
  zone_id = "Z0931404175J5ID8KD7I5"
  name    = "honda333.blog"
  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"
    origin_path = "/client"  # 🔹 ここを追加

    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 = ["honda333.blog"]

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

# Route 53 に CloudFront へのエイリアスレコードを追加
resource "aws_route53_record" "cloudfront" {
  zone_id = "xxx"
  name    = "xxx.blog"
  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
  }
}
```


## まとめ
本記事では、Terraform を用いて S3 と CloudFront を組み合わせた静的サイトホスティングの設定手順を解説しました。

CloudFront を活用することで、S3 への直接アクセスを防ぎながら、安全かつ効率的なコンテンツ配信が可能になります。

## おまけ

独自ドメインを使い、ブラウザからアクセスするとこんな感じになります！

![Screenshot 2025-02-16 at 9.11.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/1f6aa78a-ea26-439b-90ee-35808463663b.png)