GCP Notes: Configure API Gateway with Terraform
Tags: gcp
Published: 2021-09-30 16:39:09

_Originally posted in https://blog.openstep.net/posts/gcp-api-terraform/_

GCP API Gateway gives you more control over access to your Cloud Functions triggered by HTTP. I will explain how to configure an API Gateway and why you might want to do that.

According to GCP's documentation: "With API Gateway, you can create, secure, and monitor APIs for Google Cloud serverless back ends, including Cloud Functions, Cloud Run, and App Engine. Built on Envoy, API Gateway gives you high performance, scalability, and the freedom to focus on building great apps. Consumption-based and tiered pricing means you can better manage cost."

If you have built serverless APIs in AWS, you have more than likely used AWS API Gateway to expose your lambdas as HTTP endpoints. AWS API Gateway allows you to throttle traffic, add authentication, modify headers, etc. GCP did not have an equivalent service until 2020, and as of this writing (September 2021), it is still in beta. GCP Cloud Functions provide you with an HTTP(s) endpoint by default if they have an HTTP trigger. You can also specify if the Cloud Function is publicly exposed. However, you can not secure the endpoint, nor throttle traffic, nor use a custom domain. API Gateway also provides metrics about the endpoints (latency, error rates, etc.). If you want to keep track of what applications use your APIs, you can create API keys for each application. API Gateway then allows you to view the traffic generated by each application, and you can throttle or disable APIs using the keys instead of having to modify code.

## GCP API Gateway Components

The API Gateway is made up of 3 components:
The Config
The Gateway
The API

The `Config` is an [OpenAPI v2](https://swagger.io/specification/v2/) document that allows you to map the Cloud Function endpoint to an API endpoint:

```yaml
swagger: '2.0'
info:
  title: poe-api
  description: API Gateway for POE
  version: 1.0.0
schemes:
  - https
produces:
  - application/json
paths:
  /api/echo:
    get:
      summary: Echo
      operationId: echo
      x-google-backend:
        address: https://project-id.cloudfunctions.net/HandlerEcho
      responses:
        200:
          description: Successful response
          schema:
            type: string
```

This example deploys an API that forwards all requests to `/api/echo` to the Cloud Function `HandlerEcho.` It also only uses `HTTPS,` and all the endpoints return `JSON.`

## Enabling CORS

You need to enable CORS if your APIs are accessed from a web application. Browsers restrict cross-origin HTTP requests initiate from scripts unless the response from other origins includes the correct CORS headers. I won't explain how to configure CORS on your server; that topic is out of scope, but I'll explain how to configure GCP API Gateway to work with CORS.

Configuring CORS can be annoying because you have to provide an additional path for every one of our endpoints. There is probably a better way of doing this, but I found that this works for me. You have to copy-paste the path you configured and change it to be an `options` request.

```yaml
paths:
	 /api/echo:
	    get:
	        summary: Echo
    	    operationId: echo
	        x-google-backend:
	            address: https://project-id.cloudfunctions.net/HandlerEcho
	        responses:
	            200:
	                description: Successful response
	                schema:
	                    type: string
	    options:
	        summary: Echo
            operationId: echo
            x-google-backend:
	            address: https://project-id.cloudfunctions.net/HandlerEcho
	        responses:
        200:
            description: Successful response
            schema:
                type: string

```

## API Keys

GCP API Gateway allows you to secure & monitor your APIs in several ways. APIs can be throttled and disabled based on the API Key that they used. You can create an API key for a web application and another key for machine-to-machine access. If you notice that the latter makes more requests than expected, that API Key can be throttled or disabled without affecting the web application.

You need to configure API Keys in your config file and also using the GCP console. In your config file, add a top-level field:

```yaml
security:
- api_key: []
securityDefinitions:
	api_key:
		type: apiKey
		name: apiKey
		in: query
```

This snippet says that all endpoints expect a `query` parameter called `apiKey.` There are other types of security that API Gateway provides, but I won't cover them in this post.

The second step is to go to the GCP console, navigate to `APIs & Services,` then click on `Enable APIs and Services.` This will take you to a page where you should search for the API you created and enable it. Then return to the `APIs & Services` page and click on `Credentials` and `Create Credentials.` Then select `API Key.` GCP assigns an autogenerated name to the key, but you should change it and customize it. Click on the name of the generated key, and change the name to something that describes the service using the key. The string under `API Key` is what customers of your APIs should use. You can also further restrict access by type of application and `HTTP referrers` if the key will be used by a web application. Finally, click on `Restrict Key` and select your API from the drop-down.

## Prevent access to the backend

You can force everyone to use your API only via the API Gateway by disabling `unauthenticated` access to your backend. The API Gateway will use a service account to access the backend. However, if your backends are inspecting the `Authorization` header for JWT tokens, then your endpoints will stop working. This happens because the API Gateway will inject its own JWT token on that header. You will need to use a different header to provide the JWT token that your application needs. Maybe something like `X-Authorization,` but whatever you use, be consistent and make sure to document it.

## Deploying with Terraform

You will use the Terraform Google provider (https://registry.terraform.io/providers/hashicorp/google/latest/docs) for deployment. You need three resources to configure an API Gateway:

- Config: google_api_gateway_api_config
- API: google_api_gateway_api
- Gateway: google_api_gateway_gateway

The Config holds the OpenAPI document that describes the endpoints that will be served.
The Gateway defines an external URL that API clients will use to access the API.

```hcl
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

locals {
  api_config_id_prefix     = "api"
  api_id                   = "your-api-id"
  gateway_id               = "your-gateway-id"
  display_name             = "API Display Name"
}

resource "google_api_gateway_api" "api_gw" {
  provider     = google-beta
  api_id       = local.api_gateway_container_id
  project      = var.project_id
  display_name = local.display_name
}

resource "google_api_gateway_api_config" "api_cfg" {
  provider             = google-beta
  api                  = google_api_gateway_api.api_gw.api_id
  api_config_id_prefix = local.api_config_id_prefix
  project              = var.project_id
  display_name         = local.display_name

  openapi_documents {
    document {
      path     = "openapi.yaml"
      contents = filebase64("openapi.yml")
    }
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "google_api_gateway_gateway" "gw" {
  provider = google-beta
  region   = var.region
  project  = var.project_id


  api_config   = google_api_gateway_api_config.api_cfg.id

  gateway_id   = local.gateway_id
  display_name = local.display_name

  depends_on   = [google_api_gateway_api_config.api_cfg]
}

```

You should be able to run `terraform plan` to verify the configuration, and `terraform apply` to deploy.

## Conclusion

You can use GCP API Gateway to have more fine-grained control over your APIs. You saw how to do this using the OpenAPI specification and how to use API Keys to protect your endpoints. You can also leverage Terraform for declaring your API Gateway as code.

## References

- The Terraform Provider [https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/api_gateway_api](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/api_gateway_api)
- OpenAPI Extensions [https://cloud.google.com/endpoints/docs/openapi/openapi-extensions#x-google-jwt-locations](https://cloud.google.com/endpoints/docs/openapi/openapi-extensions#x-google-jwt-locations)
- API Gateway Official Docs [https://cloud.google.com/api-gateway/docs](https://cloud.google.com/api-gateway/docs)