個人的備忘録：Terraform を使った AWS S3 と CloudFront を活用した静的ウェブホスティングのセットアップ

## はじめに
AWS の S3 と CloudFront を組み合わせることで、セキュアでスケーラブルな静的ウェブサイトホスティングを実現できます。

本記事では、Terraform を使用して S3 バケットの作成、CloudFront の設定、セキュアなアクセス制御を行う手順を解説します。

## インフラ構成概要
この構成では、次のような設定を行います。
- **S3 バケット**（`my-cloudfront-bucket-tokyo`）を作成し、CloudFront 経由でのみアクセス可能にする。
- **CloudFront Origin Access Identity（OAI）** を使用して、CloudFront 以外のアクセスを制限。
- **バケットポリシー** を設定し、CloudFront OAI のみ S3 へのアクセスを許可。
- **CloudFront ディストリビューション** を設定し、HTTPS でのアクセスを強制。
- **簡単な HTML ファイル（index.html）** を作成して S3 にアップロード。

## Terraform コード
以下のコードを Terraform で適用することで、AWS 上に必要なリソースを作成できます。

### S3 バケットの作成
```hcl
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-cloudfront-bucket-tokyo"
  acl    = "private" # バケットを非公開に設定

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

### 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（オリジンアクセスアイデンティティ）の作成
```hcl
resource "aws_cloudfront_origin_access_identity" "my_oai" {
  comment = "OAI for S3 bucket"
}
```

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

### 静的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 ディストリビューションの設定
```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 {
    cloudfront_default_certificate = true
  }

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

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

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

    # CloudFront が S3 にアクセスするための OAI を設定
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.my_oai.cloudfront_access_identity_path
    }
  }

  enabled             = true  # ディストリビューションを有効化
  is_ipv6_enabled     = true  # IPv6 を有効化
  comment             = "CloudFront Distribution for S3 bucket in Tokyo region"
  default_root_object = "index.html" # ルートにアクセスした際のデフォルトファイル

  # デフォルトのキャッシュ動作設定（GET/HEAD のみ許可し、HTTPS にリダイレクト）
  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" # HTTP を HTTPS にリダイレクト
  }

  # 地理的制限の設定（制限なし）
  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  # 料金クラスの設定（安価なリージョンのみ使用）
  price_class = "PriceClass_100"

  # CloudFront の SSL 証明書設定（デフォルトの CloudFront 証明書を使用）
  viewer_certificate {
    cloudfront_default_certificate = true
  }

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


## まとめ
この Terraform コードを適用することで、
- S3 バケットを作成し、CloudFront 経由のみでアクセス可能にする。
- CloudFront OAI を用いたセキュアなアクセス制御を設定。
- CloudFront ディストリビューションを構成し、HTTPS でのアクセスを強制。
- S3 バケットに静的 HTML をアップロードし、ウェブサイトとして提供可能。

この方法を活用すれば、安全かつスケーラブルなウェブサイトホスティングを AWS 上に構築できます！