Written by Alistair Sykes
Feb 22, 2018

Using Google Cloud Endpoints

APIs need protecting - when we were recently using Google App Engine we explored Google Cloud Endpoints and utilised them to improve the security.

Cloud Endpoints

Cloud Endpoints allows you to protect and monitor your APIs on any Google Cloud backend through a formalised interface. It provides an API console, hosting, logging, monitoring and other features to help you create, share, maintain, and secure your APIs. https://cloud.google.com/endpoints/

Getting started

To set up endpoints you will need a class which represents your API and change a few config files.

@Api(
   name = "echoApi",
   version = "v1",
)
public class Echo {
 @ApiMethod
 public Message echo(@Named("message") @Nullable String message) {
   return new Message(message);
 }
}

Here is a very simple API which takes the message string from the url query parameter and returns it back via the Message class:

public class Message {

 private String message;

 public String getMessage() {
   return this.message;
 }

 public void setMessage(String message) {
   this.message = message;
 }
}

To call this API you would do a GET request to:

https://project-id.appspot.com/_ah/api/echoApi/v1/echo?message=mytestmessage

This is a sample project which can be really helpful in understanding endpoints. Here you can also see the required changes to the config files (appengine-web.xml, web.xml):  

https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/app...

To deploy your endpoints code:

./gradlew build
./gradlew endpointsOpenApiDocs
gcloud service-management deploy build/endpointsOpenApiDocs/openapi.json

Structure

It is worth thinking about how you want your urls structured (RESTful or otherwise). And throwing an appropriate exception can go a long way (https://cloud.google.com/endpoints/docs/frameworks/java/exceptions).

Enqueuing

In our case, we wanted to start one of our enqueuing task HttpServlets and then have firebase cloud functions call the endpoint.

@Api(
   name = "update",
   version = "v1"
)
public class UpdateApi {

   @ApiMethod
   public Result startEnqueue() {
       Queue queue = QueueFactory.getQueue("my-queue");
       queue.addAsync(TaskOptions.Builder.withUrl("/enqueue/start-enqueuing"));
       return new Result(true);
   }
}

public class Result {

   private boolean mResult;

   public Result(boolean success) {
       mResult = success;
   }

   public boolean getResult() {
       return mResult;
   }
}

We used our existing Firebase Cloud Functions to call this new endpoint whenever we detect a change to a file in the Firebase Cloud Storage (for background see here).

const functions = require('firebase-functions');
const request = require('request-promise-native');
const path = require('path');
exports.startEnqueuing = functions.storage.object().onChange(event => {
 const file = event.data; // The Storage object.
 const fileBucket = file.bucket; // The Storage bucket that contains the file.
 const filePath = file.name; // File path in the bucket.
 const contentType = file.contentType; // File content type.
 const fileName = path.basename(filePath); // Get the file name.

 if (fileName != "FileOfInterest.txt") {
   return;
 }
 console.log("CALLING APP ENGINE");

 let url = "https://project-id.appspot.com/_ah/api/update/v1/startEnqueue";

 return request(url)
   .then(function(response) {
     console.log(response);
   })
   .catch(function(error) {
     console.error(error);
   });
});

Key to the kingdom

Now to secure these urls. Endpoints gives you a few different options here (https://cloud.google.com/endpoints/docs/frameworks/authentication-method), for our use case, we decided to use API Keys. To achieve this we simply add a configuration option to API class:

@Api(
   name = "update",
   version = "v1",
   apiKeyRequired = AnnotationBoolean.TRUE
)

And then we add an API key through the cloud console (https://console.cloud.google.com/apis/credentials) and add that into our url call in cloud functions:

let url = "https://project-id.appspot.com/_ah/api/update/v1/startEnqueue?key=[API-KEY]";

Halt!

All that’s left to do is to stop the enqueuing and task servlets being accessible directly. That is just the  small addition of a security-constraint to the web.xml:

<security-constraint>
   <web-resource-collection>
       <web-resource-name>enqueue</web-resource-name>
       <url-pattern>/enqueuing/*</url-pattern>
   </web-resource-collection>
   <auth-constraint>
       <role-name>admin</role-name>
   </auth-constraint>
</security-constraint>
<security-constraint>
   <web-resource-collection>
       <web-resource-name>enqueue</web-resource-name>
       <url-pattern>/enqueue/*</url-pattern>
   </web-resource-collection>
   <auth-constraint>
       <role-name>admin</role-name>
   </auth-constraint>
</security-constraint>

Next Post

Setting up Cloud SQL in Java

Previous Post

Queued tasks on App Engine for Firebase

Top