TerraformでCloudFront + Lambdaの構成テンプレート

CloudFront + LambdaでのAPIをTerraformで実装するためのテンプレートです。

LambdaにはそれぞれIAM Roleを作成してアタッチしています。

以前に似た記事を書きました。

https://qiita.com/suzuki-navi/items/6a896a6577deaa858210

今回の記事はAPI Gatewayの代わりにCloudFrontです。

以下はTerraformのファイルですが、前半のLambda部分は[前回記事](https://qiita.com/suzuki-navi/items/6a896a6577deaa858210)とほとんど同じで、後半のCloudFrontのところが今回のオリジナルです。

Lambda部分で前回と異なるのは `aws_lambda_function_url` が追加されていることです。CloudFrontのオリジンとしてLambdaを指定するにはLambda関数URLが必要なためです。

```main.tf
variable aws_profile {}
variable aws_region {}
variable resource_prefix {}

provider "aws" {
   profile = var.aws_profile
   region = var.aws_region
}

################################################################################
# LambdaにアタッチするIAM Role
################################################################################

resource "aws_iam_role" "lambda_role" {
  name               = "${var.resource_prefix}-lambda-role"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}

resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

data "aws_iam_policy_document" "lambda_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

################################
# Lambda
################################

# apiディレクトリにLambdaのソースコードがある前提
# apiディレクトリを api.zip という名前に固めて resource "aws_lambda_function" "api" から参照できるようにする
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "api"
  output_path = "api.zip"
}

resource "aws_lambda_function" "api" {
  depends_on       = [aws_iam_role.lambda_role]
  filename         = data.archive_file.lambda_zip.output_path
  function_name    = "${var.resource_prefix}-api"
  role             = aws_iam_role.lambda_role.arn
  handler          = "index.lambda_handler"
  runtime          = "nodejs18.x"
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256
}

resource "aws_lambda_function_url" "api" {
  function_name      = aws_lambda_function.api.function_name
  authorization_type = "NONE"
}

output "lambda_function_url" {
  value = aws_lambda_function_url.api.function_url
}

################################################################################
# CloudFront
################################################################################

resource "aws_cloudfront_distribution" "api" {
  origin {
    domain_name = "${aws_lambda_function_url.api.url_id}.lambda-url.${var.aws_region}.on.aws"
    origin_id   = "lambda"
    custom_origin_config {
      http_port = 80
      https_port = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
  }

  enabled =  true

  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods = [ "GET", "HEAD" ]
    cached_methods = [ "GET", "HEAD" ]
    target_origin_id = "lambda"
        
    forwarded_values {
      query_string = true
      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl = 60
    default_ttl = 60
    max_ttl = 60
    compress = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations = []
    }
  }
  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

output "cloudfront_url" {
  value = "https://${aws_cloudfront_distribution.api.domain_name}/"
}

################################################################################
```

`api/index.js` と `terraform.tfvars` は[前回記事](https://qiita.com/suzuki-navi/items/6a896a6577deaa858210)とまったく同じです。

以下はAPIの実行結果例です。

```plain
$ curl 'https://xxxxxxxx.cloudfront.net/path1?q=abc' | jq .
```

```json
{
  "message": "OK",
  "event": {
    "version": "2.0",
    "routeKey": "$default",
    "rawPath": "/path1",
    "rawQueryString": "q=abc",
    "headers": {
      "user-agent": "Amazon CloudFront",
      ...
    },
    "queryStringParameters": {
      "q": "abc"
    },
    "requestContext": {
      "http": {
        "method": "GET",
        "path": "/path1",
        "protocol": "HTTP/1.1",
        "userAgent": "Amazon CloudFront"
        ...
      },
      ...
    },
    "isBase64Encoded": false
  }
}
```