GCP Notes: Configure API Gateway with 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 document that allows you to map the Cloud Function endpoint to an API endpoint:
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.
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:
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.
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
- OpenAPI Extensions https://cloud.google.com/endpoints/docs/openapi/openapi-extensions#x-google-jwt-locations
- API Gateway Official Docs https://cloud.google.com/api-gateway/docs