Terraform で CloudFront の OriginAccessIdentity から OriginAccessControl に変更する

# Terraform で CloudFront の OriginAccessIdentity から OriginAccessControl に変更する

## はじめに

2022/8/26（日本時間）に CloudFront の OriginAccessControl なる機能が追加されました．

この機能は以下の Terraform AWS Provider のバージョン`4.29.0`にてTerraformで作成できるようになったので試してみました．

https://registry.terraform.io/providers/hashicorp/aws/4.29.0

OACに関しての説明はこちらの記事がわかりやすいと思いますのでそちらを参照してください．

クラスメソッドさんの記事
https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/

Amazon Web Services ブログ
https://aws.amazon.com/jp/blogs/news/amazon-cloudfront-introduces-origin-access-control-oac/

:::note warn
本記事の内容で実施すると，ダウンタイムが発生します．移行手順ではないのでご注意ください．
:::

## Terraform構成

参考まで，　試した時の構成は次の通りです

CloudFrontは自前moduleを作り，
S3は`terraform-aws-modules/s3-bucket/aws`を使用しています．

記事に関係ない部分は極力省略しています．

    ```
    ├── main.tf
    ├── outputs.tf
    ├── providers.tf
    ├── variables.tf
    ├── modules
    │   └── cdn
    │       ├── main.tf
    │       ├── outputs.tf
    │       ├── providers.tf
    │       └── variables.tf
    └── files
       └── index.html
    ```

## Terraformコード(Origin Access Identity)

お馴染みのOrigin Access Identity(以下，OAI) を使用して CloudFront と S3 を作成しておきます

### CloudFront

リソース`aws_cloudfront_origin_access_identity`を作成し， リソース`aws_cloudfront_distribution` の `s3_origin_config` を設定します．

#### aws_cloudfront_origin_access_identity
```terraform
resource "aws_cloudfront_origin_access_identity" "main" {
  comment = "oai-${var.name_suffix}"
}
```

#### aws_cloudfront_distribution

```terraform
resource "aws_cloudfront_distribution" "main" {
  origin {
    connection_attempts      = 3
    connection_timeout       = 10
    domain_name              = var.origin_domain_name
    origin_id                = "s3_one"
    # origin_access_control_id = aws_cloudfront_origin_access_control.main.id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path
    }
  }

  aliases = [
    "${var.subdomain}.${var.domain}"
  ]
  enabled         = true
  is_ipv6_enabled = true
  price_class     = "PriceClass_200"

  wait_for_deployment = true
  web_acl_id          = var.waf_arn

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "s3_one"

    viewer_protocol_policy = "redirect-to-https"
    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  viewer_certificate {
    acm_certificate_arn = var.acm_certificate_arn
    ssl_support_method  = "sni-only"
  }
}
```

### S3

また，　S3バケットポリシーに `aws_cloudfront_origin_access_identity` を `identifiers` として許可します．

#### aws_iam_policy_document

```terraform
data "aws_iam_policy_document" "s3_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${module.s3_bucket.s3_bucket_arn}/static/*"]

    principals {
      type        = "AWS"
      identifiers = [module.cdn.oai_iam_arn]
    }
  }
}
```

#### aws_s3_bucket_policy

```terraform
resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = module.s3_bucket.s3_bucket_id
  policy = data.aws_iam_policy_document.s3_policy.json
}
```

---

## Terraformコード(Origin Access Control)

先ほど作成したコードの一部を変更・追加して CloudFront から Origin Access Control （以下，OAC） を使用して S3 にアクセスを許可してみます．

リソース`aws_cloudfront_origin_access_control`を作成し， リソース `aws_cloudfront_distribution` の Origin Arguments にある `origin_access_control_id` を設定します． 
[Resource: aws_cloudfront_distribution #origin_access_control_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#origin_access_control_id:~:text=origin_access_control_id)
また， OAI で使用した`s3_origin_config`は不要なのでコメントアウト（or 削除）しておきます．

#### aws_cloudfront_origin_access_control

```terraform
resource "aws_cloudfront_origin_access_control" "main" {
  name                              = "oac-${var.name_suffix}"
  description                       = "Test migration OAI to OAC"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}
```

#### aws_cloudfront_distribution

```terraform
resource "aws_cloudfront_distribution" "main" {
  origin {
    connection_attempts      = 3
    connection_timeout       = 10
    domain_name              = var.origin_domain_name
    origin_id                = "s3_one"
    origin_access_control_id = aws_cloudfront_origin_access_control.main.id # これを追加

    # s3_origin_config {　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　# こちらは不要なのでコメントアウト
    #   origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path
    # }
  }

  aliases = [
    "${var.subdomain}.${var.domain}"
  ]
  enabled         = true
  is_ipv6_enabled = true
  price_class     = "PriceClass_200"

  wait_for_deployment = true
  web_acl_id          = var.waf_arn

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "s3_one"

    viewer_protocol_policy = "redirect-to-https"
    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  viewer_certificate {
    acm_certificate_arn = var.acm_certificate_arn
    ssl_support_method  = "sni-only"
  }
}
```

また， S3バケットポリシーは	CloudFront のARNを指定して CloudFront からのアクセスを許可します．

```terraform
data "aws_iam_policy_document" "s3_oac_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${module.s3_bucket.s3_bucket_arn}/static/*"]

    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }

    condition {
      test     = "ForAnyValue:StringEquals"
      variable = "AWS:SourceArn"
      values   = [module.cdn.cloudfront_distribution_arn]
    }
  }
}
```

```terraform
resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = module.s3_bucket.s3_bucket_id
  policy = data.aws_iam_policy_document.s3_oac_policy.json
}
```

### OAIからOACへの変更 plan

S3バケットポリシーとCloudFrontのOrigin部分が変更されるようですね．

```terraform
Terraform will perform the following actions:

  # aws_s3_bucket_policy.bucket_policy will be updated in-place
  ~ resource "aws_s3_bucket_policy" "bucket_policy" {
        id     = "cdn-test"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      + Condition = {
                          + "ForAnyValue:StringEquals" = {
                              + "AWS:SourceArn" = "arn:aws:cloudfront::xxxxxxxxxxxx:distribution/<ID>"
                            }
                        }
                      ~ Principal = {
                          - AWS     = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <ID>" -> null
                          + Service = "cloudfront.amazonaws.com"
                        }
                        # (4 unchanged elements hidden)
                    },
                ]
                # (1 unchanged element hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.cdn.aws_cloudfront_distribution.main will be updated in-place
  ~ resource "aws_cloudfront_distribution" "main" {
        id                             = "<ID>"
        tags                           = {}
        # (19 unchanged attributes hidden)


      - origin {
          - connection_attempts = 3 -> null
          - connection_timeout  = 10 -> null
          - domain_name         = "cdn-test.s3.ap-northeast-1.amazonaws.com" -> null
          - origin_id           = "s3_one" -> null

          - s3_origin_config {
              - origin_access_identity = "origin-access-identity/cloudfront/<ID>" -> null
            }
        }
      + origin {
          + connection_attempts      = 3
          + connection_timeout       = 10
          + domain_name              = "cdn-test.s3.ap-northeast-1.amazonaws.com"
          + origin_access_control_id = "<ID>"
          + origin_id                = "s3_one"
        }


        # (3 unchanged blocks hidden)
    }

Plan: 0 to add, 2 to change, 0 to destroy.
```

### OAIからOACへの変更 apply

planで問題がなければ apply して反映します．

:::note warn
本記事の手順で実施すると， apply中は以下のページになりコンテンツが表示されません．（ダウンタイム）
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269701/1e3ccd29-37b8-f534-3709-c4b11dafc2d6.png)

少し時間がかかりましたが， 無事変更できたようです　🎉

```terraform
Plan: 0 to add, 1 to change, 0 to destroy.
module.cdn.aws_cloudfront_distribution.main: Modifying... [id=xxxxxxxxxxx]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 10s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 20s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 30s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 40s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 50s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m0s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m10s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m20s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m30s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m40s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 1m50s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m0s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m10s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m20s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m30s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m40s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 2m50s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 3m0s elapsed]
module.cdn.aws_cloudfront_distribution.main: Still modifying... [id=xxxxxxxxxxx, 3m10s elapsed]
module.cdn.aws_cloudfront_distribution.main: Modifications complete after 3m17s [id=xxxxxxxxxxx]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
```

コンソールから確認します．
`Origin access control settings (recommended)`が選択されていますね．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269701/3eb8ce80-cd5f-52a3-6e29-a3661969d4bd.png)

S3バケットポリシーも大丈夫そうです．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269701/c32f5f6b-b927-77f6-e8f8-1df6c4608707.png)

コンテンツも見えています．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269701/0aeee7c0-fe4c-f855-2854-c34e7eb3098f.png)

#### 補足

:::note warn
Origin Access Controlへの変更と同時にリソース`aws_cloudfront_origin_access_identity`を削除試みようとすると使用中のエラーになりますので，変更が終わってから & 動作確認してから削除しましょう．
:::

```terraform
module.cdn.aws_cloudfront_origin_access_identity.main: Destroying... [id=xxxxxxxxxxxxxx]
╷
│ Error: CloudFrontOriginAccessIdentityInUse: The CloudFront origin access identity is still being used.
│       status code: 409, request id: xxxxxxxxxxxxxxx
```

## おわりに

AWSのリリースから一週間程度でTerraformモジュールが対応されているのはほんと素早くてすごいですね．

`terraform-aws-modules`の`terraform-aws-cloudfront`は記事公開時点（9/2)で対応がまだのようですが，対応されたら試してみたいと思います．
https://github.com/terraform-aws-modules/terraform-aws-cloudfront

本記事が何かの参考になれば幸いです． 誤字・訂正などあれば編集リクエストください．