Declare a simple REST API Gateway - Terraform
Published: 2021-02-25 13:44:59

Recently, I had to create a REST API Gateway in AWS with Terraform. The documentation is good but I had to took some time, navigating pages to understand what should I do. So, here is a resume of that research.

> Note : For HTTP and Websockets API Gateway, there is another of the Terraform scripts.

---

# The base - API Gateway Rest API
[Terraform Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api)

With it's minimal form, the Gateway is quite simple.
```yaml
resource "aws_api_gateway_rest_api" "example" {
  name = "example"
}
```

But you have a lot of optional elements like :
* **description** : Quick description of the API
* **body** : Expose the API services exposed with an [OpenAPI Specification](https://swagger.io/specification/).
* **endpoint_configuration** : Define the endpoint configuration type <br/>
How to choose your endpoint configuration : https://docs.aws.amazon.com/en_en/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html
* ...

```yaml
resource "aws_api_gateway_rest_api" "example" {
  body = jsonencode({
    openapi = "3.0.1"
    info = {
      title   = "example"
      version = "1.0"
    }
    paths = {
      "/path1" = {
        get = {
          x-amazon-apigateway-integration = {
            httpMethod           = "GET"
            payloadFormatVersion = "1.0"
            type                 = "HTTP_PROXY"
            uri                  = "https://ip-ranges.amazonaws.com/ip-ranges.json"
          }
        }
      }
    }
  })

  description = "Example of an API Gateway"
  name        = "example"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

```

---

# Ingress Policy - API Gateway Rest API Policy
[Terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api_policy)

The definition of the Ingress Policy can help you to manage ingress access. It can be really helpful because Security Groups can't be applied to the API Gateway.

*In this example, we only open the API Gateway to the IP Ranges: 10.0.0.0/24 and 10.10.0.0/24*
```yaml
resource "aws_api_gateway_rest_api_policy" "test" {
  rest_api_id = aws_api_gateway_rest_api.test.id

  policy = <<EOF
{
    Version = "2012-10-17",
    Statement = [{
        Effect = "Deny",
        Principal = "*",
        Action = "execute-api:Invoke",
        Resource = "execute-api:/*/*/*"
      },
      {
        Effect = "Allow",
        Principal = "*",
        Action = "execute-api:Invoke",
        Resource = "execute-api:/*/*/*",
        Condition = {
          NotIpAddress = {
            "aws:SourceIp" = ["10.0.0.0/24", "10.10.0.0/24"]
          }
        }
      }
    ]
  }
EOF
```

---

# Define the exposed resources
## Declare paths - API Gateway Resource
[Terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_resource)

The **Resource** element will define all the path you want to expose.

Let's take an example.

You have the following paths :
* /users
    * /add
    * /list
* /cards
    * /play
    * /send

Both **/users** path expose a method of the same service, but each of **/card** expose a different one. 

To do it, first we have to create a resource for **/users** and one for **/cards**.
```yaml
resource "aws_api_gateway_resource" "usersResource" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  parent_id   = aws_api_gateway_rest_api.MyDemoAPI.root_resource_id # In this case, the parent id should the gateway root_resource_id.
  path_part   = "users"
}

resource "aws_api_gateway_resource" "cardsResource" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  parent_id   = aws_api_gateway_rest_api.MyDemoAPI.root_resource_id
  path_part   = "cards"
}
```

Then, we had to create another resource for each subpath which expose differents endpoints. *(You can do others resources for each users path)*
```yaml
resource "aws_api_gateway_resource" "playCardResource" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  parent_id   = aws_api_gateway_resource.cardsResource.id # In this case, the parent id should be the parent aws_api_gateway_resource id.
  path_part   = "play"
}

resource "aws_api_gateway_resource" "sendCardResource" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  parent_id   = aws_api_gateway_resource.cardsResource.id
  path_part   = "send"
}
```

---

## Declare Methods - API Gateway Method
[Terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method)

Declare all methods available for each path.

*Example where we only allow GET methods for /cards/send*
```yaml
resource "aws_api_gateway_method" "MyDemoMethod" {
  rest_api_id   = aws_api_gateway_rest_api.MyDemoAPI.id
  resource_id   = aws_api_gateway_resource.sendCardResource.id
  http_method   = "GET"
  authorization = "NONE"
}
```

If you want, you can define a way to check autorization with Cognito or IAM.

---

## Integrate endpoints - API Gateway Integration
[Terraform Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_integration)

Now, we integrate endpoints with differents services. Here is differents examples.

### Lambda
```yaml
resource "aws_api_gateway_integration" "integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_resource.resource.id
  http_method             = aws_api_gateway_method.method.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.lambda.invoke_arn
}
```

### HTTP
```yaml
resource "aws_api_gateway_integration" "test" {
  rest_api_id = aws_api_gateway_rest_api.api_gateway_pmt_apis.id
  resource_id = aws_api_gateway_resource.api_gateway_ressource_all.id
  http_method = aws_api_gateway_method.aws_api_gateway_method_root.http_method

  type                    = "HTTP"
  uri                     = "https://www.google.de"
  integration_http_method = "GET"
}
```

### Mock
This element can be really useful if you want to quickly expose a method for a frontend service. But to define completly a mock, we will need the two next elements.

```yaml
resource "aws_api_gateway_integration" "MyDemoIntegration" {
  rest_api_id          = aws_api_gateway_rest_api.MyDemoAPI.id
  resource_id          = aws_api_gateway_resource.MyDemoResource.id
  http_method          = aws_api_gateway_method.MyDemoMethod.http_method
  type                 = "MOCK"
}
```

---

## Declare Mock HTTP Response - API Gateway Method Response
[Terraform Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_response)

When you want to mock a integration, you have to have to declare its response.

```yaml
resource "aws_api_gateway_method_response" "response_200" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  resource_id = aws_api_gateway_resource.MyDemoResource.id
  http_method = aws_api_gateway_method.MyDemoMethod.http_method
  status_code = "200"
}
```

---

## Declare Mock HTTP Response Body - API Gateway Integration Response
[Terraform Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_integration_response)

```yaml
resource "aws_api_gateway_integration_response" "MyDemoIntegrationResponse" {
  rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
  resource_id = aws_api_gateway_resource.MyDemoResource.id
  http_method = aws_api_gateway_method.MyDemoMethod.http_method
  status_code = aws_api_gateway_method_response.response_200.status_code

  # Transforms the backend JSON response to XML
  response_templates = {
    "application/xml" = <<EOF
#set($inputRoot = $input.path('$'))
<?xml version="1.0" encoding="UTF-8"?>
<message>
    $inputRoot.body
</message>
EOF
  }
}
```

---

I hope it will help you!