¿Cómo Crear una API Gateway REST Privada con Dominio Personalizado en AWS?
Implementaremos un API Gateway REST privada en AWS, con un VPC Endpoint Interface y expuesta mediante un dominio personalizado privado.
Cuando se desarrollan microservicios internos o APIs para sistemas confidenciales, es común que necesitemos exponer estas APIs solo dentro del entorno privado de AWS, evitando cualquier exposición pública. Para ello, AWS ofrece API Gateway con endpoint PRIVATE combinado con VPC Endpoint Interface.
En este artículo veremos como crear y configurar:
API Gateway REST configurado como
PRIVATEUn VPC Endpoint Interface para
execute-apiUn dominio privado personalizado (ej:
api.infra.local)Validación SSL con ACM
Alias en Route 53 hacia el VPC Endpoint
⚠️ Advertencias y Problemas Resueltos
Durante la implementación nos enfrentamos a las siguientes restricciones y errores relacionados con las versiones de Terraform:
Problema: Error 404 al usar aws_api_gateway_base_path_mapping con dominios privados
- Mensaje:
Error: creating API Gateway Base Path Mapping (operation error API Gateway: CreateBasePathMapping, https response error StatusCode: 404, RequestID: xxx, NotFoundException: Invalid domain name identifier specified)
- Causa: En versiones anteriores a Terraform v5.88, el proveedor tenía un bug donde la combinación del campo
domain_namejunto condomain_name_idgeneraba una construcción incorrecta en la llamada interna a la API de AWS.
Por ejemplo:
resource "aws_api_gateway_base_path_mapping" "example" {
domain_name = aws_api_gateway_domain_name.example.domain_name
domain_name_id = aws_api_gateway_domain_name.example.id
api_id = aws_api_gateway_rest_api.example.id
stage_name = aws_api_gateway_stage.example.stage_name
base_path = ""
}
Generaba internamente algo como:
api.dev.mobiles.secure.alfalfita.click//api.dev.mobiles.secure.alfalfita.click/okmnxgsnua
Esta combinación con doble barra (//) es inválida y resultaba en el error Invalid domain name identifier specified.
- Solución temporal: Usar un
null_resourceconlocal-execque ejecute manualmente el CLI de AWS con eldomain-name-idcorrecto.
Fix oficial en Terraform v5.88
Desde la versión Terraform v5.88, el proveedor de AWS para Terraform ya permite el uso adecuado de domain_name_id con dominios privados, sin necesidad de combinarlo con domain_name, y sin generar duplicaciones.
resource "aws_api_gateway_base_path_mapping" "private_api_mapping" {
domain_name_id = aws_api_gateway_domain_name.private_custom_domain.domain_name_id
api_id = aws_api_gateway_rest_api.private.id
stage_name = aws_api_gateway_stage.stage.stage_name
base_path = ""
}
👀 Recomendación: Asegúrate de estar utilizando Terraform v5.88 o superior para evitar este problema.
Paso 1: Crear el Dominio Personalizado Privado
resource "aws_api_gateway_domain_name" "private_custom_domain" {
domain_name = "api.infra.local"
certificate_arn = aws_acm_certificate.cert.arn
security_policy = "TLS_1_2"
endpoint_configuration {
types = ["PRIVATE"]
}
}
Para la validación de certificado se recomienda usar DNS con una zona Route 53 pública o privada según el caso.
Paso 2: Crear el VPC Endpoint Interface para API Gateway
resource "aws_vpc_endpoint" "execute_api" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.execute-api"
vpc_endpoint_type = "Interface"
subnet_ids = var.subnet_ids
security_group_ids = var.sg_ids
private_dns_enabled = false
}
Este endpoint permite a las instancias en la VPC comunicarse con la API Gateway privada.
Paso 3: Asociar el Custom Domain al VPC Endpoint
resource "aws_api_gateway_domain_name_access_association" "assoc" {
access_association_source = aws_vpc_endpoint.execute_api.id
access_association_source_type = "VPCE"
domain_name_arn = aws_api_gateway_domain_name.private_custom_domain.arn
}
Paso 4: Crear la API Gateway REST Privada
resource "aws_api_gateway_rest_api" "private_api" {
name = "mi-api-privada"
endpoint_configuration {
types = ["PRIVATE"]
vpc_endpoint_ids = [aws_vpc_endpoint.execute_api.id]
}
}
Paso 5: Crear los Recursos y Métodos
resource "aws_api_gateway_resource" "validar" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
parent_id = aws_api_gateway_rest_api.private_api.root_resource_id
path_part = "validar"
}
resource "aws_api_gateway_method" "get" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
resource_id = aws_api_gateway_resource.validar.id
http_method = "GET"
authorization = "NONE"
}
Paso 6: Configurar Integración y Deployment
resource "aws_api_gateway_integration" "mock" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
resource_id = aws_api_gateway_resource.validar.id
http_method = aws_api_gateway_method.get.http_method
type = "MOCK"
request_templates = {
"application/json" = "{\"statusCode\": 200}"
}
}
resource "aws_api_gateway_deployment" "deploy" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
triggers = {
redeploy = timestamp()
}
lifecycle {
create_before_destroy = true
}
}
Paso 7: Mapear la API al Dominio Privado
Desde Terraform v5.88+, se soporta el mapeo de base path en dominios privados:
resource "aws_api_gateway_base_path_mapping" "map" {
domain_name_id = aws_api_gateway_domain_name.private_custom_domain.domain_name_id
api_id = aws_api_gateway_rest_api.private_api.id
stage_name = aws_api_gateway_stage.stage.stage_name
base_path = ""
}
Paso 8: Aplicar Políticas de Seguridad
Restringe acceso a la API por VPC o por VPC Endpoint:
data "aws_iam_policy_document" "private_policy" {
statement {
effect = "Deny"
actions = ["execute-api:Invoke"]
resources = ["${aws_api_gateway_rest_api.private_api.execution_arn}/*/*"]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "StringNotEquals"
variable = "aws:SourceVpce"
values = [aws_vpc_endpoint.execute_api.id]
}
}
statement {
effect = "Allow"
actions = ["execute-api:Invoke"]
resources = ["${aws_api_gateway_rest_api.private_api.execution_arn}/*/*"]
principals {
type = "AWS"
identifiers = ["*"]
}
}
}
Paso 9: Alias DNS en Route 53
resource "aws_route53_record" "custom_domain_alias" {
zone_id = aws_route53_zone.private.id
name = "api.infra.local"
type = "A"
alias {
name = aws_vpc_endpoint.execute_api.dns_entry[0].dns_name
zone_id = aws_vpc_endpoint.execute_api.dns_entry[0].hosted_zone_id
evaluate_target_health = false
}
}
Conclusión
Con esta configuración, logramos una API REST totalmente privada, expuesta con HTTPS y dominio personalizado, con seguridad a nivel de red (VPC Endpoint) e IAM. Una solución ideal para integraciones internas seguras, cumplimiento de normativas y entornos confidenciales.
Para consumir tu API desde la misma VPC, puedes ejecutar:
curl -k https://api.infra.local/prod/validar
