# [Terraform]
CloudFront のオリジンに Lambda Function URL をセットする


published: 2024-11-21

Lambda 関数を作成し、その Function URL を CloudFront のオリジンにセットしてみる


## 構成

・CloudFront
・CloudFront OAC
・Lambda (Web Adapter)
・アプリケーション (Go)


![architecture.svg](https://lab.enuesaa.dev/prototype/terraform-cloudfront-lambda/architecture.svg)

## アプリケーション

アプリケーションは Go で作り Lambda Web Adapter を被せる


```go
package main

import (
	"net/http"
	
	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello")
	})

	// Lambda Web Adapter はデフォルトで 8080 番ポートにリクエストを送る
	e.Logger.Fatal(e.Start(":8080"))
}

```

## Lambda Web Adapter

Lambda Web Adapter を使う際は、Docker で関数を作るのがメジャーだと思う。
だが、AWS は Lambda Web Adapter 用の Lambda Layer を公開しており、それをアタッチしたところ Zip 形式でも動いた。


- [https://github.com/awslabs/aws-lambda-web-adapter?tab=readme-ov-file#aws-commercial-regions](https://github.com/awslabs/aws-lambda-web-adapter?tab=readme-ov-file#aws-commercial-regions)
## Terraform

### `main.tf`

```tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      configuration_aliases = [ aws, aws.virginia ]
    }
  }
}

data "aws_caller_identity" "main" {}
data "aws_region" "main" {}

```

### `lambda.tf`

```tf
resource "aws_lambda_function" "main" {
  function_name = var.identifier

  s3_bucket     = var.lambda_s3_bucket
  s3_key        = var.lambda_s3_key

  handler       = "bootstrap"
  architectures = ["arm64"]
  runtime       = "provided.al2"
  role          = aws_iam_role.lambda.arn
  memory_size   = 128
  timeout       = 10

  layers = [
    // TODO:
    // AWS は Lambda Web Adapter 用の Lambda Layer を公開しているので、Arn をコピペする
    // see https://github.com/awslabs/aws-lambda-web-adapter?tab=readme-ov-file#aws-commercial-regions
    
    // "arn:aws:lambda:ap-northeast-1:xxx:layer:LambdaAdapterLayerArm64:23"
  ]
}

```

### `cloudfront_distribution.tf`

```tf
resource "aws_cloudfront_distribution" "main" {
  comment = var.identifier

  enabled = false
  price_class = "PriceClass_200"
  http_version = "http2and3"
  is_ipv6_enabled = true
  default_root_object = "index.html"

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  origin {
    origin_id   = "lambda"
    domain_name = "${aws_lambda_function_url.main.url_id}.lambda-url.${data.aws_region.main.name}.on.aws"
    origin_access_control_id = aws_cloudfront_origin_access_control.lambda.id

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  default_cache_behavior {
    target_origin_id       = "lambda"
    compress               = true
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD"]
    cache_policy_id            = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" // Managed-CachingDisabled
    response_headers_policy_id = "67f7725c-6f97-4210-82d7-5512b31e9d03" // Managed-SecurityHeadersPolicy
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

```

### `lambda_role.tf`

```tf
resource "aws_iam_role" "lambda" {
  name = "${var.identifier}-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "lambda_logs" {
  name = "logs"
  role = aws_iam_role.lambda.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Effect   = "Allow"
        Resource = "*"
      }
    ]
  })
}

```

### `variables.tf`

```tf
variable "identifier" {
  type = string
}

variable "lambda_s3_bucket" {
  type = string
}

variable "lambda_s3_bucket_virginia" {
  type = string
}

variable "lambda_s3_key" {
  type = string
}

```

### `cloudfront_origin_access_control.tf`

```tf
resource "aws_cloudfront_origin_access_control" "lambda" {
  name             = "${var.identifier}-lambda"

  description      = "OAC for Lambda Function URL"
  signing_behavior = "always"
  signing_protocol = "sigv4"
  origin_access_control_origin_type = "lambda"
}

```

### `lambda_url.tf`

```tf
resource "aws_lambda_function_url" "main" {
  function_name      = aws_lambda_function.main.function_name

  authorization_type = "AWS_IAM"
}

resource "aws_lambda_permission" "main" {
  statement_id  = "cloudfront"

  function_name = aws_lambda_function.main.function_name
  action        = "lambda:InvokeFunctionUrl"
  principal     = "cloudfront.amazonaws.com"
  source_arn    = "arn:aws:cloudfront::${data.aws_caller_identity.main.account_id}:distribution/${aws_cloudfront_distribution.main.id}"
}

```

### memos

- [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter)
## 注意: POST, PUT はできない

CloudFront OAC がリクエストボディのハッシュ計算に対応していない様子。
POST or PUT を叩くとエラーが返ってくる。

ワークアラウンドはいくつかあるが、プロダクションでの使用は要検討。


- [https://dev.classmethod.jp/articles/cloudfront-lambda-url-sigv4-signer/](https://dev.classmethod.jp/articles/cloudfront-lambda-url-sigv4-signer/)
