Software & Cloud Engineer. Perth, Western Australia

Securing serverless APIs with Cloud Run

Posted on April 22, 2023

A padlock in the locked position tying a chain together over a green door.
Photo by Life Of Pix from Pexels

When building out APIs on with Cloud Run, it’s easy to get lost in the fun of building and deploying and seeing your service running live without much thought to the rest of your cloud infrastructure. What permission does your service account that’s running in Cloud Run have access to? In this post I’ll show you how to secure your application with an example application that connects to a Firestore database with the application of the principle of least privilege.

Google Cloud makes it really easy to get up and running quickly with a number of quick start guides, easy CLI tools and “click-ops” options if you’re so inclined to deploy your cloud services. But a number of those guides don’t really talk to how to make sure you’re not handing over the keys to your kingdom (read: cloud infrastructure). By default the compute engine service account which is automatically created for you as part of the project creation process (unless you’re in an organisation with service controls) will have very wide ranging permissions to be able to perform an number of tasks. This includes things like running VMs or building and publishing code artifacts to the artifact registry. The ability to connect to Cloud SQL or Big Query and so on. These permissions can be damaging if one of your APIs are compromised as the attacker will now have programmatic access to your cloud and be able to do all sorts of nefarious activities.

Thankfully, theres a number of ways to remediate this problem by understanding what kind of activities and services your API uses and then ensuring that it has the right permissions to do what it needs to, and nothing else. For example, you might want to give your API the ability to connect and perform SQL queries in a Cloud SQL instance, but that API has no need to talk to Big Query, nor anything else.

In this example, let’s use Firestore as our database, and we’ll setup a service account that our Cloud Run service will use which has just the right permissions to be able to read and write data from the Firestore database, but nothing else.

Let’s start by creating a role which the service account will be able to use, that has just the right amount of permission required to read write and delete data from Firestore. In the web console, you can create a service account by accessing the IAM and admin in the left menu main. Then click Roles. Click Create Role from the top menu. On the menu that appears, give your role a title, a description (not required, but I recommend), an ID, and select a launch stage. You can leave launch stage as the default if you want or you can select General Availability. The launch stage is simply informational.

With the basic info of the role set, now it’s time to assign the permissions we need. Still on the Create Role screen, select Add Permissions. In the filter, type in datastore.entities and then it will filter down to the entities sub-permission of Firestore. In this case we want to allow our API to only have permission to read, write, and delete records in our Firestore database, so let’s add the following permissions:

  • datastore.entities.create
  • datastore.entities.delete
  • datastore.entities.get
  • datastore.entities.list
  • datastore.entities.update

With the five check boxes ticked, click Add. Then, back on the Create Role screen, click Create. This will create our custom role.

Now it’s time to create the service account and assign it the minimum permissions via the role we just created. On the left menu of IAM, click Service accounts in the left sub-menu. On this page, you can see the existing service accounts which are already setup on your project. Up the top, add a new service account by clicking the + CREATE SERVICE ACCOUNT option. On the menu that appears, give the service account a name, which will auto populate an ID for this service account. Optionally, give the service account a description which will make it easy to find and understand it’s intent when you try to find it again later on, then click the Create and continue button.

In the Select Role section that appears in step 2 of creating a service account, click on the dropdown, and then choose the Custom option in the Quick Access section. There you’ll see the custom role we just created. Select it, and then click on the Done menu option down the bottom. This will create the service account.

Now that we have a service account with the minimum permission needed to read and write to Firestore, let’s assign it to our Cloud Run API.

Selecting a service account on Cloud Run

Navigate to your Cloud Run service, and then choose to Edit and Deploy a new version. On the Security tab, use the dropdown to select a new service account and select the service account we just created. Then click Deploy.

Congratulations! You just secured your Cloud Run API with the minimum permissions it needs in order to securely run in the cloud.