GCP API Gateway (Servlerless)

mohanganesh

mohan-ganesh

Posted on October 20, 2020

GCP API Gateway (Servlerless)

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
Enter fullscreen mode Exit fullscreen mode

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));

    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Enable the cloud run services, if it's not enabled.

gcloud services enable run.googleapis.com
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Replace the x-google-backend address with cloud run service url.
Edit x-google-issuer url to add your project name.
Edit x-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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You should see something like this. Click on the API's name and in the next section glance at the Configs and Gateways

Alt Text

You would see the information like below.
Alt Text
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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>'
Enter fullscreen mode Exit fullscreen mode

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
}'
Enter fullscreen mode Exit fullscreen mode

You can also, get the same key via GCP console under Credentials

Alt Text

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>' \
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
mohanganesh
mohan-ganesh

Posted on October 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

GCP API Gateway (Servlerless)
googlecloud GCP API Gateway (Servlerless)

October 20, 2020