The Cloud Run Security Gap You Didn't Know You Had (and How to Fix It)
Learn how to use IAM's allow policies and role bindings to give specific service accounts access to individual Cloud Run services, enhancing your GCP security
Introduction
Off the back of Tuesday's post on Cloud Engineering sub (have a look at the blog btw if you haven’t already) about protected Cloud Run services, I got the question about how to control which principal (who) can invoke the Cloud Run service in your project? Anyone with the run.routes.invoke
permission can. But how do I stop other principals with this role invoking other protected Cloud Run services in my account? Well my friends, it’s time for a quick dive into the land of IAM and how it works on Google Cloud.
IAM recap
Firstly a quick primer. What is IAM? It’s short for Identity and Access Management. According to Google’s own documentation:
With IAM, you manage access control by defining who (identity) has what access (role) for which resource. For example, Compute Engine virtual machine instances, Google Kubernetes Engine (GKE) clusters, and Cloud Storage buckets are all Google Cloud resources. The organizations, folders, and projects that you use to organize your resources are also resources.
Unlike other Clouds, Google’s IAM model is flipped such that if you grant a permission to a resource higher up in the resource hierarchy, it is automatically inherited to it’s children. So, let’s consider this image, also from the GCP docs on policy inheritance.
Ok so with this in mind, let’s note a few things:
Each resource only has one parent.
Each permission granted at say the “Engineering” folder means that it will automatically have the same permission at anything underneath it.
The organisation is the “root” node in any GCP environment and there will always be exactly one of these. Permissions applied at this level will mean that principal can do anything with that permission in the organisation! (hint: don’t do day-to-day IAM stuff here).
Role Bindings, Allow Policies and Service Accounts
So back to the question:
How can I control which principal (who) can invoke a Cloud Run service in a project?
In the IAM console, we can create a service account with the predefined role roles/run.invoker
permission, which is a predefined role that has the ability to run a Cloud Run service or Cloud Run job. But, service accounts are resources in a project and permissions applied here apply to the project.
So, considering the IAM model, what will happen is that you’ll grant the service account principal the ability to invoke any Cloud Run service or job in the project since the specific resource (the Cloud Run service) is a child of the project.
How can we solve this? We don’t want any old service account with that permission to run the Cloud Run service, instead we only want to give specific permission to a designated service account to perform the task. And this, is where Allow Policies and Role Bindings come into play.
Role Bindings you say?
A role binding specifies what access should be granted to a resource. It associates, or binds, one or more principals with a single IAM role and any context-specific conditions that change how and when the role is granted. The metadata includes additional information about the allow policy, such as an etag and version to facilitate policy management.
An allow policy (only one per resource) is a collection of role bindings and some metadata to enable fine grained control over who can do what with your resources.
Give me the solution already!
So how can we fix this in our case? Well for starters, remove the permission in your service account for invoking Cloud Run services. You can verify this worked by trying to access the protected service via the NGINX proxy. If it still works, give it a bit of time, and it will eventually start returning unauthorised responses. Then, go to the list of Cloud Run services in the console, and tick the box of the protected service we want to control.
In the row of icons that appears above the list of services, select permissions. A popout will appear, similar to below. Click on “Add Principal” button.
Then you’ll get another popout such as below. Here you select the principal (service account) you want to bind to this particular resource, “hello” in this case.
You’ll notice that that on save, the service account with “Cloud Run Invoker” appears, but that it’s policy is no longer inherited.
After a few moments, you’ll be able to see that the service is working again as originally designed, but now that service account cannot invoke other Cloud Run services, only that one.
Safe and secure! 🔒