# ¿Cómo Crear una API Gateway REST Privada con Dominio Personalizado en AWS?

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 `PRIVATE`
    
* Un **VPC Endpoint Interface** para `execute-api`
    
* Un 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:**
    

```text
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_name` junto con `domain_name_id` generaba una construcción incorrecta en la llamada interna a la API de AWS.
    

Por ejemplo:

```bash
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:

```bash
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_resource` con `local-exec` que ejecute manualmente el CLI de AWS con el `domain-name-id` correcto.
    

### 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.

```bash
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      = ""
}
```

**<mark>👀 Recomendación:</mark>** <mark> Asegúrate de estar utilizando Terraform v5.88 o superior para evitar este problema.</mark>

---

## Paso 1: Crear el Dominio Personalizado Privado

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

<mark>Desde </mark> **<mark>Terraform v5.88+</mark>**<mark>, se soporta el mapeo de base path en dominios privados:</mark>

```bash
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:

```bash
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

```bash
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:

```bash
curl -k https://api.infra.local/prod/validar
```
