VPCオリジンを使ってCloudFrontとプライベートなALB接続を試すのをTerraformで設定してみた

https://aws.amazon.com/jp/blogs/news/introducing-amazon-cloudfront-vpc-origins-enhanced-security-and-streamlined-operations-for-your-applications/

というものが2024年11月に来ました。

で、TerraformのAWS Providerでも、つい最近実装されてこの機能が設定できるようになったので、試してみたいと思います。

※ v5.82.0で追加されました。

https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#5820-december-19-2024

細かい仕組みなどはドキュメントをご覧ください。

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-vpc-origins.html

AWS Providerのドキュメントはこちら。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_vpc_origin

今回、試してみる構成は

Cloudfront - vpc origin - alb(private subnet)

のような感じになります。

お試しなので最低限の設定しかしていません。ご了承ください。


## まず、ロードバランサーを作る

```tf
resource "aws_lb" "this" {
  name               = "vpc-origin-test"
  internal           = true
  load_balancer_type = "application"
  security_groups    = [aws_security_group.this.id]
  subnets            = var.subnet_ids
}


resource "aws_security_group" "this" {
  name   = "vpc_origin_test"
  vpc_id = var.vpc_id
}

resource "aws_vpc_security_group_egress_rule" "main" {
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = "10.0.0.0/8"
  from_port         = 1
  ip_protocol       = "tcp"
  to_port           = 65535
}
resource "aws_vpc_security_group_ingress_rule" "main" {
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = "10.0.0.0/8"
  from_port         = 80
  ip_protocol       = "tcp"
  to_port           = 80
}

# セキュリティグループの詳細は後で追記します。

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.this.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "Nekodaisuki!!!"
      status_code  = "200"
    }
  }
}

```

はい、できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947182/6ec50e7b-1cec-0354-fe5d-f7687a4210f0.png)

## VPCオリジンを作ってみる

CloudFrontディストリビューションのオリジンに紐づけると変更できなくなるんで、３つ作ります。テストのために入れ替えたいんです（と言いつつ２つしか使わなかったけど）。

```tf
resource "aws_cloudfront_vpc_origin" "https_only" {
  vpc_origin_endpoint_config {
    name                   = "https_only"
    arn                    = aws_lb.this.arn
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "https-only"

    origin_ssl_protocols {
      items    = ["TLSv1.2"]
      quantity = 1
    }
  }
}
resource "aws_cloudfront_vpc_origin" "http_and_https" {
  vpc_origin_endpoint_config {
    name                   = "http_and_https"
    arn                    = aws_lb.this.arn
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "match-viewer"

    origin_ssl_protocols {
      items    = ["TLSv1.2"]
      quantity = 1
    }
  }
}
resource "aws_cloudfront_vpc_origin" "http_only" {
  vpc_origin_endpoint_config {
    name                   = "http_only"
    arn                    = aws_lb.this.arn
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "http-only"

    origin_ssl_protocols {
      items    = ["TLSv1.2"]
      quantity = 1
    }
  }
}
```

```
aws_cloudfront_vpc_origin.https_only: Creation complete after 2m55s [id=vo_DZJH4ZRTpFpCy07MrMXXX3]
aws_cloudfront_vpc_origin.http_only: Creation complete after 2m55s [id=vo_5MYtzqRGd6CGEx9tXXXOIQ]
aws_cloudfront_vpc_origin.http_and_https: Creation complete after 3m15s [id=vo_1sRlzayRVWnEXXXRpbPngX]
```

完了までそこそこ時間かかります。


## CloudFrontを作ってみる

```tf
locals {
  origin_id = "example"
}
resource "aws_cloudfront_distribution" "main" {
  #provider = aws.global
  origin {
    domain_name = aws_lb.this.dns_name
    origin_id   = local.origin_id
    vpc_origin_config {
      vpc_origin_id = aws_cloudfront_vpc_origin.http_only.id
    }
  }
  enabled = true
  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations        = []
    }
  }

  default_cache_behavior {
    allowed_methods        = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = local.origin_id
    viewer_protocol_policy = "allow-all"
    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}
```


この時点ではアクセスしてもちゃんとつながりません。

```
(｀-´) < Tue Dec 24 23:47:48 $ curl -I https://d1XXXvzzbg9XXX.cloudfront.net
HTTP/2 504
(snip)
```


## セキュリティグループの設定

多分 VPCオリジンを作った時に自動で作られたSGをごにょっとしてあげる必要があります。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947182/3dd49a33-f9bf-8fe5-fdb6-f31d179b53a3.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947182/b15b9eb2-48ef-d3de-ed82-e868e1820209.png)



```tf
data "aws_security_group" "vpc_origin_sg" {
  name = "CloudFront-VPCOrigins-Service-SG"
}
resource "aws_vpc_security_group_ingress_rule" "vpc_origin" {
  security_group_id            = aws_security_group.this.id
  from_port                    = 80
  ip_protocol                  = "tcp"
  to_port                      = 80
  referenced_security_group_id = data.aws_security_group.vpc_origin_sg.id
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947182/d49ac79d-9ddc-e477-34c2-698ec53b71fa.png)

わーい、表示できたぁぁぁ～～～～！

※ てか、なぜここだけChromeでアクセスした……？！

試すだけならばここまでで終了。


けど、ちょっと疑問があるので試してみる。


## CloudFrontからALBへのアクセスをhttpsに出来るのか


以下、ざっと試したてみた結果を貼っておきます。

### ALBを別のドメインでアクセスできるようにする。ACMも設定する。

```tf
resource "aws_acm_certificate" "cert" {
  domain_name       = var.record_name
  validation_method = "DNS"
  lifecycle {
    create_before_destroy = true
  }
}

data "aws_route53_zone" "cert" {
  name = var.zone_name
}
resource "aws_route53_record" "cert" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.cert.zone_id
}

resource "aws_route53_record" "alb" {
  name = var.record_name
  alias {
    name                   = aws_lb.this.dns_name
    zone_id                = aws_lb.this.zone_id
    evaluate_target_health = false
  }
  type    = "A"
  zone_id = data.aws_route53_zone.cert.zone_id
}
```

### VPCオリジンを、https-onlyのに変更


```tf
resource "aws_cloudfront_distribution" "main" {
  origin {
    domain_name = var.record_name
    origin_id   = local.origin_id
    vpc_origin_config {
      vpc_origin_id = aws_cloudfront_vpc_origin.https_only.id
    }
  }
(snip)  
```
### セキュリティグループも443のみ通すように変更
```tf
resource "aws_vpc_security_group_ingress_rule" "main" {
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = "10.0.0.0/8"
  from_port         = 443
  ip_protocol       = "tcp"
  to_port           = 443
}
resource "aws_vpc_security_group_ingress_rule" "vpc_origin" {
  security_group_id            = aws_security_group.this.id
  from_port                    = 443
  ip_protocol                  = "tcp"
  to_port                      = 443
  referenced_security_group_id = data.aws_security_group.vpc_origin_sg.id
}
```

### ALBのリスナーも変更
```tf
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.this.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.cert.arn

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "Nekodaisuki!!!"
      status_code  = "200"
    }
  }
}
```

```
(｀-´) < Thu Dec 26 12:50:45 $ curl https://d3gsoddhvrfXXX.cloudfront.net
Nekodaisuki!!!
```


よっしゃ……！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947182/38700bde-a2ca-da82-29d4-378c2328c075.png)

リスナーはHTTPSしかないので、HTTPSで通信できてることがわかるかなと思います。

## 最後に

作ったリソースは削除しましょう


```
(｀-´) < Wed Dec 25 01:48:43 $ terraform destroy
```

なお、VPCオリジンの設定全部消しましたが `CloudFront-VPCOrigins-Service-SG` は残ってました。けど、翌日見たらいなくなっていました。

ENIはすぐ消えてくれてました。

それでは。


## 後で書き足すかも

### エラー集

### Terraform 構成コード全部