How to create a private API GW proxying Lambda?

I have a function in a vpc, and a GW V2 in front of it - but it’s on a public IP. I’d like the gateway to be restricted to just my internal network – primary driver being that we want all inbound connections coming through other content inspection/security proxies that are managed by a different team.

If I try to change the connection_type on the aws_apigatewayv2_integration from default ‘INTERNET’ to ‘VPC_LINK’ on the integration, I get this error:

Error: error updating API Gateway v2 integration: BadRequestException: For HTTP Apis with connection type VPC_LINK, integration type must be HTTP_PROXY. Current value is AWS_PROXY.

Is there any reasonable way to accomplish this?

Which value is set for the type?

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_integration#type

Turns out, this was much simpler problem resolved after opening case with amazon – it has to be a REST api, not an HTTP api - which means apigatewayv2 CANNOT be used. I was able to get one provisioned using the api_gateway instead.

Bit ugly, but full snippet below for reference/search benefit.

resource "aws_ecr_repository" "demo_tools" {
  name                 = "demo-tools"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = false
  }

  tags = local.common_tags
}

resource "aws_ecr_repository_policy" "demo_tools_policy" {
  repository = aws_ecr_repository.demo_tools.name

  policy = <<EOF
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "LambdaECRImageRetrievalPolicy",
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ]
    }
  ]
}
EOF
}

resource "aws_iam_role" "demo_tools_lambda_iam" {
  name = "demo-tools-lambda-iam"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

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

resource "aws_iam_role_policy_attachment" "demo_tools_lambda_iam_vpc" {
  role       = aws_iam_role.demo_tools_lambda_iam.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

resource "aws_lambda_function" "demo_tools_lambda" {
  function_name = "demo-tools-lambda"

  role         = aws_iam_role.demo_tools_lambda_iam.arn
  package_type = "Image"
  image_uri    = join(":", [aws_ecr_repository.demo_tools.repository_url, "latest"])

  vpc_config {
    security_group_ids = [aws_security_group.demo_lambda.id]
    subnet_ids         = data.terraform_remote_state.is.outputs.subnet_ids
  }

  environment {
    variables = {
      foo = "bar"
    }
  }
}


data "aws_vpc_endpoint_service" "demo_tools_lambda_vpc_endpoint_svc" {
  service = "execute-api"
}

resource "aws_vpc_endpoint" "demo_tools_lambda_vpc_endpoint" {
  vpc_id              = data.terraform_remote_state.cal.outputs.vpc_id
  service_name        = data.aws_vpc_endpoint_service.demo_tools_lambda_vpc_endpoint_svc.service_name
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true

  subnet_ids = data.terraform_remote_state.is.outputs.subnet_ids
  security_group_ids = [aws_security_group.demo_lambda.id]
  #security_group_ids = [aws_security_group.demo_tools_lambda_default_sg]
}


resource "aws_api_gateway_rest_api" "demo_tools_lambda_api_gw" {
  name = "demo-tools-lambda-api-gw"

  endpoint_configuration {
	types = [ "PRIVATE" ]
	vpc_endpoint_ids = [ aws_vpc_endpoint.demo_tools_lambda_vpc_endpoint.id ]
  }
}

resource "aws_api_gateway_rest_api_policy" "demo_tools_lambda_api_gw_policy" {
  rest_api_id = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
	{
		"Effect": "Allow",
		"Principal": "*",
		"Action": "execute-api:Invoke",
		"Resource": [
			"*"
		]
	},
	{
		"Effect": "Deny",
		"Principal": "*",
		"Action": "execute-api:Invoke",
		"Resource": [
			"*"
		],
		"Condition" : {
			"StringNotEquals": {
				"aws:SourceVpce": "${aws_vpc_endpoint.demo_tools_lambda_vpc_endpoint.id}"
			}
		}
	}
  ]
}
EOF
}

resource "aws_api_gateway_resource" "demo_tools_lambda_api_gw_rsrc" {
  rest_api_id = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  parent_id   = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.root_resource_id
  path_part   = "{proxy+}"
}

# Handle general urls
resource "aws_api_gateway_method" "demo_tools_lambda_api_gw_method" {
  rest_api_id   = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  resource_id   = aws_api_gateway_resource.demo_tools_lambda_api_gw_rsrc.id
  http_method   = "ANY"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "demo_tools_lambda_api_gw_integ" {
  rest_api_id             = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  resource_id             = aws_api_gateway_resource.demo_tools_lambda_api_gw_rsrc.id
  http_method             = aws_api_gateway_method.demo_tools_lambda_api_gw_method.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.demo_tools_lambda.invoke_arn
}

# Handle root url
resource "aws_api_gateway_method" "demo_tools_lambda_api_gw_method_root" {
  rest_api_id   = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  resource_id   = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.root_resource_id
  http_method   = "ANY"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "demo_tools_lambda_api_gw_integ_root" {
  rest_api_id             = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  resource_id             = aws_api_gateway_method.demo_tools_lambda_api_gw_method_root.resource_id
  http_method             = aws_api_gateway_method.demo_tools_lambda_api_gw_method_root.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.demo_tools_lambda.invoke_arn
}

resource "aws_api_gateway_deployment" "demo_tools_lambda_api_gw_deployment" {
  depends_on = [
    aws_api_gateway_integration.demo_tools_lambda_api_gw_integ,
    aws_api_gateway_integration.demo_tools_lambda_api_gw_integ_root
  ]
  rest_api_id = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id

  triggers = {
    # NOTE: The configuration below will satisfy ordering considerations,
    #       but not pick up all future REST API changes. More advanced patterns
    #       are possible, such as using the filesha1() function against the
    #       Terraform configuration file(s) or removing the .id references to
    #       calculate a hash against whole resources. Be aware that using whole
    #       resources will show a difference after the initial implementation.
    #       It will stabilize to only change when resources change afterwards.
    redeployment = sha1(jsonencode([
      aws_api_gateway_resource.demo_tools_lambda_api_gw_rsrc.id,
      aws_api_gateway_method.demo_tools_lambda_api_gw_method_root.id,
      aws_api_gateway_method.demo_tools_lambda_api_gw_method.id,
      aws_api_gateway_integration.demo_tools_lambda_api_gw_integ.id,
      aws_api_gateway_integration.demo_tools_lambda_api_gw_integ_root.id,
    ]))
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_stage" "demo_tools_lambda_api_gw_stage" {
  deployment_id = aws_api_gateway_deployment.demo_tools_lambda_api_gw_deployment.id
  rest_api_id   = aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id
  stage_name    = "demo-tools-lambda-api-gw-stage"
}

resource "aws_lambda_permission" "demo_tools_lambda_api_gw_perm" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.demo_tools_lambda.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_api_gateway_rest_api.demo_tools_lambda_api_gw.execution_arn}/*/*"
}

output "demo_tools_gw_url_vpce" {
  description = "Base URL for demo Tools API Gateway stage."
  value = "https://${aws_api_gateway_rest_api.demo_tools_lambda_api_gw.id}-${aws_vpc_endpoint.demo_tools_lambda_vpc_endpoint.id}.execute-api.us-west-2.amazonaws.com/${aws_api_gateway_stage.demo_tools_lambda_api_gw_stage.stage_name}"
}