Search code examples
aws-lambdaamazon-route53aws-api-gateway

Trying to expose aws_api_gatewayv2 to domain name, getting no route to host


I have created an API Gateway v2 which exposes a single AWS Lambda which I intend to use to expose my entire REST API through. So far I have this working just fine by following the hashicorp tutorial.

// S3 bucket for code release and updates.

resource "aws_s3_bucket" "lambda_zips" {
  bucket = "tdweb-lambda-zips"
  acl = "private"
  force_destroy = true
}

data "archive_file" "web_api_lambda" {
  type = "zip"
  source_dir  = "${path.module}/web-api"
  output_path = "${path.module}/web-api.zip"
}

resource "aws_s3_object" "web_api" {
  bucket = aws_s3_bucket.lambda_zips.id
  key    = "web-api.zip"
  source = data.archive_file.web_api_lambda.output_path
  etag = filemd5(data.archive_file.web_api_lambda.output_path)
}

// Lambda and associated IAM roles and permissions.

resource "aws_lambda_function" "web_api" {
  function_name = "web_api_lambda"

  s3_bucket = aws_s3_bucket.lambda_zips.id
  s3_key    = aws_s3_object.web_api.key

  runtime = "python3.9"
  handler = "lambda_function.lambda_handler"

  source_code_hash = data.archive_file.web_api_lambda.output_base64sha256

  role = aws_iam_role.web_api_lambda.arn
}

resource "aws_cloudwatch_log_group" "web_api_lambda" {
  name = "/aws/lambda/${aws_lambda_function.web_api.function_name}"
  retention_in_days = 30
}


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

resource "aws_iam_role" "web_api_lambda" {
  name = "web_api_lambda_role"
  assume_role_policy = data.aws_iam_policy_document.web_api_assume_role.json
}

data "aws_iam_policy_document" "web_api_lambda" {
  statement {
    effect = "Allow"
    actions = [
      "logs:CreateLogStream",
      "logs:CreateLogDelivery",
      "logs:PutLogEvents"
    ]
    resources = ["arn:aws:logs:*:*:*"]
  }

  statement {
    effect = "Allow"
    actions = [
      "dynamodb:*"
    ]
    resources = ["*"]
  }

  statement {
    effect = "Allow"
    actions = [
        "s3:*",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "web_api_lambda" {
  name = "web_api_lambda_policy"
  policy = data.aws_iam_policy_document.web_api_lambda.json
  role = aws_iam_role.web_api_lambda.id
}

// API Gateway

resource "aws_apigatewayv2_api" "web_api" {
  name = "web_api_gateway"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_stage" "web_api_prod" {
  api_id = aws_apigatewayv2_api.web_api.id

  name = "prod"
  auto_deploy = true

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.web_api_gateway.arn

    format = jsonencode({
      requestId               = "$context.requestId"
      sourceIp                = "$context.identity.sourceIp"
      requestTime             = "$context.requestTime"
      protocol                = "$context.protocol"
      httpMethod              = "$context.httpMethod"
      resourcePath            = "$context.resourcePath"
      routeKey                = "$context.routeKey"
      status                  = "$context.status"
      responseLength          = "$context.responseLength"
      integrationErrorMessage = "$context.integrationErrorMessage"
      }
    )
  }
}

resource "aws_apigatewayv2_integration" "web_api" {
  api_id = aws_apigatewayv2_api.web_api.id

  integration_uri = aws_lambda_function.web_api.invoke_arn
  integration_type = "AWS_PROXY"
  integration_method = "POST"
}

resource "aws_apigatewayv2_route" "web_api_prod" {
  api_id = aws_apigatewayv2_api.web_api.id

  route_key = "GET /"
  target = "integrations/${aws_apigatewayv2_integration.web_api.id}"
}

resource "aws_cloudwatch_log_group" "web_api_gateway" {
  name = "/aws/api_gw/${aws_apigatewayv2_api.web_api.name}"
  retention_in_days = 30
}

resource "aws_lambda_permission" "web_api_gateway" {
  statement_id = "AllowExecutionFromAPIGateway"
  action = "lambda:InvokeFunction"
  function_name = aws_lambda_function.web_api.function_name
  principal = "apigateway.amazonaws.com"
  source_arn = "${aws_apigatewayv2_api.web_api.execution_arn}/*/*"
}

output "base_url" {
  value = aws_apigatewayv2_stage.web_api_prod.invoke_url
}

Now I can do a simple curl of the output base_url and I see the response from my Lambda.

I would like for this API to be exposed to via my domain name. So I've add this:

resource "aws_apigatewayv2_domain_name" "web_api" {
  domain_name = "prod.${var.domain_name}"

  domain_name_configuration {
    certificate_arn = aws_acm_certificate.main.arn
    endpoint_type = "REGIONAL"
    security_policy = "TLS_1_2"
  }
}

resource "aws_apigatewayv2_api_mapping" "web_api" {
  api_id = aws_apigatewayv2_api.web_api.id
  domain_name = aws_apigatewayv2_domain_name.web_api.id
  stage = aws_apigatewayv2_stage.web_api_prod.id
}

The certificate is working with an existing web server running on EC2 and is working. However, then I try to curl prod.mydomainname.com I get an error of:

curl: (6) Could not resolve host: prod.mydomainname.com

I am not sure why it's not exposed.


Solution

  • I missed the route53 entry.

    resource "aws_route53_record" "web_api" {
      zone_id = aws_route53_zone.external.zone_id
      name = aws_apigatewayv2_domain_name.web_api.domain_name
      type = "A"
      alias {
        name = aws_apigatewayv2_domain_name.web_api.domain_name_configuration[0].target_domain_name
        zone_id = aws_apigatewayv2_domain_name.web_api.domain_name_configuration[0].hosted_zone_id
        evaluate_target_health = false
      }
    }