GCP API Gateway (Servlerless)
mohan-ganesh
Posted on October 20, 2020
Recently google has introduced a new API management service. Google Cloud API Gateway is a fully managed service that makes it easy for organizations/developers to create, publish, maintain, monitor and secure API's. It acts as one of the "front door" (ideally we want managed load balancer) for an application deployed on backend services like App Engine, Cloud Run and Cloud Functions, Compute Engine, or Google Kubernetes Engine.
In this article, we are going to deploy couple of endpoints from cloud run environment with front end proxy by API Gateway. We will have two API's to demonstrate public and secured (secured by firebase auth) by oauth2 Bearer Token.
GCP Products
- Cloud Run: HTTP backend services and accessible only through API Gateway
- API Gateway: Frontend service to process the incoming request, and check the security if the services are secured.
- Firebase: Used to generate the custom token and exchange firebase custom token for google id token.
- App Engine: HTTP endpoints for generating firebase custom token
We will breakdown the exercise in to multipart, first we will set up the backend services and integrated with API Gateway. Once that step is complete we will focus on the firebase/appengine parts to validate the security aspect.
Setup
- Git: Git is used to managing the binary
- GCP: You will need a google cloud project with billing enabled
- JDK11 environment
Create a GCP project and clone the repository Link
Open the Google Cloud Shell or terminal clone the repository(https://github.com/mohan-ganesh/apigateway-gcp.git) and switch to cloud-run directory.
export PROJECT_ID=<YOUR-PROJECT-ID>
git clone https://github.com/mohan-ganesh/apigateway-gcp.git
cd apigateway-gcp/cloud-run
The cloud run application contains simple two services end points healthcheck
and identity
. healthcheck
is a simple endpoint that just responds with > alive if the application is running. identity
has small block of code that reads one of the header named x-apigateway-api-userinfo.
@RequestMapping (path="/identity", method= RequestMethod.GET,produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity identity(HttpServletRequest request) {
String authInfo = request.getHeader("x-apigateway-api-userinfo");
byte data[] = Base64.getDecoder().decode(authInfo);
return ResponseEntity.ok(new String(data));
}
Build the application. This step may ask for the enablement of the Container Registry if it's not enabled.
gcloud builds submit --tag gcr.io/$PROJECT_ID/apigateway-cloudrun
Enable the cloud run services, if it's not enabled.
gcloud services enable run.googleapis.com
Then, deploy the container in private mode to the cloud run.
gcloud run deploy --image gcr.io/$PROJECT_ID/apigateway-cloudrun --platform managed --region us-central1 --no-allow-unauthenticated --memory=512Mi
Once the deploy completes, please make a note of the Cloud Run Service URL: https://.run.app.
Also, make a note of the cloud run service account. To get the cloud service account, execute the below command
gcloud iam service-accounts list
The account that has the following pattern 123456-compute@developer.gserviceaccount.com is the cloud service account. We will need this information to use in open API definition.
now, change the directory to the openapi-definition directory.
cd ..
cd openapi-definition
Replace the
x-google-backend
address with cloud run service url.
Editx-google-issuer
url to add your project name.
Editx-google-audiences
with your project name.
paths:
/healthcheck:
get:
summary: healthcheck
operationId: healthcheck
responses:
'200':
description: A successful response
schema:
type: string
/identity:
get:
summary: print user identity
operationId: headers
security:
- firebase: [ ]
responses:
'200':
description: A successful response
schema:
type: string
Please note that in the above block of code healthcheck does not have any security, whereas identity path is secured with firebase security definition.
At this point in time, you are ready to create the API Gateway configs and gateway's.
gcloud beta api-gateway api-configs create <open-api-config-name-v1> \
--api=<cloudrun-sevice-name> --openapi-spec=openapi-spec.yaml \
--project=$PROJECT_ID --backend-auth-service-account=<cloud-run-service-account>
The above step creates the api-gateway config, now you are ready to create API gateways
gcloud beta api-gateway gateways create <open-api-gateway-v1> \
--api=<cloudrun-sevice-name> --api-config=<open-api-config-name> \
--location=us-central1 --project=$PROJECT_ID
If you need to update the config, replace create with an update.
The cloud run services are private at this point in time, we would like to grant "roles/run.invoker" permission to cloud run service account so that it can invoke the services.
gcloud run services add-iam-policy-binding <cloudrun-service-name> \
--member "serviceAccount:<cloud-run-serviceaccount>" \
--role "roles/run.invoker" \
--platform managed \
--region us-central1 \
--project $PROJECT_ID
With these steps, you are almost ready to test the API service endpoints via GCP API Gateway.
To get the API Gateway generated url, please execute the command or navigate to API Gateway in GCP Console.
gcloud beta api-gateway gateways describe <open-api-gateway-v1> \
--location=us-central1 --project=$PROJECT_ID
You should see something like this. Click on the API's name and in the next section glance at the Configs and Gateways
You would see the information like below.
The url displayed under Gateways is the API Gateway generated front-end url.
You can also get that url via gcloud command.
gcloud beta api-gateway gateways describe <open-api-gateway-config-name> \
--location=us-central1 --project=$PROJECT_ID
Test API
Now you can send request's to your API
$ curl -X GET \
https://open-api-gateway-<hash>.gateway.dev/healthcheck \
At the very first request, you may see a response like below.
{
"code": 504,
"message": "upstream request timeout"
}
That is because, via api gateway, it request had tried to reach the backend, and response did not come in time.
If you retry after a few seconds, you should see the response alive
This step's complete you have successfully able to invoke the API endpoint via GCP API Gateway.
Now, if you do curl on the identity endpoint, you should see
{
"code": 401,
"message": "Jwt is missing"
}
At this point in time, this is a valid response for this endpoint.
Part 2
At the beginning of the article, we said we will also demonstrate the API's secured by the firebase auth token. As you can see when you make a request to 'identity' service, it's expecting the caller to pass a valid bearer token. It's time to generate one.
Head over to App Engine folder. This is also a simple spring-boot application that contains a couple of endpoints and we are interested in 'firebase/custom/token'.
For more about firebase custom token, please refer Link
cd ..
cd app-engine-standard
mvn clean package
gcloud app deploy
With the above steps, the application gets deployed to App Engine Standard instance.
Time to grant, a couple of permissions.
below one for enabling IAM and the next one is for granting Account Token Creator permission for appspot service account.
Enable IAM services
gcloud services enable iamcredentials.googleapis.com
Add TokenCreator role to app-engine service account. This step is needed in order to sign the JWT token that is going to be created.
For more information, please read at firebase documentation.
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:<replace-your-id>@appspot.gserviceaccount.com" \
--role="roles/iam.serviceAccountTokenCreator"
If everything is fine, if you do test the custom token API you would get JWT. If you experience any error's you could also refer to the triage section at firebase troubleshooting documentation.
curl -X GET \
'https://<app-engine-generated-value>.appspot.com/firebase/custom/token?userId=<unique-id>'
The response would be valid JWT, which is valid for one hour.
This is still a valid token, but very short-lived and lesser scope. Most of the products such as endpoint and API gateway respect's OAuth bearer token.
Now we will trade or exchange, the custom generated JWT token with google ID-Token. Before we do that, we need to get the Firebase project web api key. To get that value go to
Link and add the created GCP project to enable the firebase.
Then navigate to url (replace the project id with your project id) or click on Project Overview gear icon --> Project Settings. Under Your Project
section you should have web-api-key there. Or visit the link by replacing your GCP project id.
https://console.firebase.google.com/u/0/project/<project_id>/settings/general
Under the general section note down the Web API Key. This Web API key is being passed in the below api call to get the idToken. The reason for getting this Web API Key, this is how GCP knows the token that was generated belong to the respective project. You can also able to find this information in GCP Console under the Credentials
section.
curl -X POST \
https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=<Firebase-Web-API-Token>' \
-d '{
"token":"<jwt-cutom-token>",
"returnSecureToken":true
}'
You can also, get the same key via GCP console under Credentials
if everything is fine, you will see a response like below
{
"kind": "identitytoolkit#VerifyCustomTokenResponse",
"idToken": "long-base64encoded-string",
"refreshToken": "refreshToken",
"expiresIn": "3600",
"isNewUser": false
}
from the response, grab the idToken, now you are ready to test the 'identity' api from api-gateway.
curl -X GET \
https://open-api-gateway-<hash>.gateway.dev/identity \
-H 'authorization: Bearer <idToken>' \
So, when we pass the Bearer token, API gateway does validate the token, if the token is valid then it would let the request to process to reach the backend. As a part of that step, API gateway adds two additional auth related headers. Now you can have backend API's to just expect x-apigateway-api-userinfo
if its present process the business logic.
You should see JSON response like below, and that's the end of the article.
{
"iss": "https://securetoken.google.com/",
"aud": "",
"auth_time": ,
"user_id": "",
"sub": "",
"iat": 1603075386,
"exp": 1603078986,
"firebase": {
"identities": {},
"sign_in_provider": "custom"
}
}
With this implementation, the API can just focus on the business logic, and security is completely handled by the API Gateway layer.
Posted on October 20, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.