Container Security

GCP Cloud Run Supply Chain Security

A practical playbook for protecting the supply chain of services running on Cloud Run: image provenance, Binary Authorization, runtime identity, and the gaps the default configuration leaves wide open.

Nayan Dey
Senior Security Engineer
7 min read

Cloud Run makes it easy to go from a container image to a public HTTPS endpoint in under a minute. That is the selling point, and it is a real one. It is also the thing that makes Cloud Run a common weak link in otherwise well-secured GCP organizations. Teams pick it because it is fast, deploy hardened-enough images, and then skip the supply-chain controls they would never skip on GKE. The resulting services run with fewer guarantees about what is actually executing in production than the team thinks.

I ran a supply-chain assessment on a mid-size SaaS company in early 2024 that had about 280 Cloud Run services spread across 12 projects. The postmortem of that assessment became the outline for this post. The TL;DR is that Cloud Run has all the primitives you need to enforce supply-chain integrity, but you have to turn them on, and the defaults are calibrated for developer velocity rather than security.

The baseline Cloud Run threat model

A Cloud Run service has three supply-chain entry points: the container image, the runtime identity, and the deploy-time configuration. An attacker who controls any of the three can run arbitrary code with your service's privileges.

The image is the obvious one. If an attacker can push a tampered image to the repository Cloud Run pulls from, they own the runtime. The runtime identity matters because Cloud Run services can call other GCP APIs using a service account, and a compromised service can pivot using those credentials. The deploy-time configuration includes the image reference, environment variables, and mounted secrets. An attacker with run.services.update on a service can redirect it to a malicious image without ever touching the registry.

Supply-chain security on Cloud Run is the discipline of closing all three entry points simultaneously. Closing one without the others leaves a straight-line path to compromise.

Enforce Binary Authorization, and do it at the platform

Binary Authorization became generally available for Cloud Run in October 2021 and has matured considerably since. The policy evaluates each deploy against a set of attestations, and rejects deployments that do not carry the required signatures. Enabled properly, it closes both the image path and most of the configuration path in one move.

The common mistake is to enable Binary Authorization per service. Do not do that. Enable it at the Cloud Run level via the policy object at projects/PROJECT/policy, with defaultAdmissionRule set to REQUIRE_ATTESTATION and the required attestors listed. This makes the default secure, and service owners have to explicitly request an exception. I much prefer the audit log of exceptions to the audit log of missing enforcement.

Pair Binary Authorization with a continuous validation policy. As of March 2023, Cloud Run supports continuous validation that re-evaluates running revisions against the current policy and emits Cloud Audit Logs when a running revision no longer complies. This catches attestor-key rotation gaps and services that were deployed before the policy tightened, which is a class of drift static admission cannot detect.

Build provenance or you are signing nothing meaningful

An attestation is only as trustworthy as the process that produced it. If your build pipeline signs any image that happens to come out of it, Binary Authorization is theatre. The attestation needs to encode facts about the build that you actually care about: the source commit, the builder identity, the base image, and ideally the full SBOM.

Cloud Build's SLSA v1.0 provenance support, rolled out through 2023, produces this kind of attestation. The in-toto statement includes the source repository URL, the commit SHA, the builder's identity (https://cloudbuild.googleapis.com/GoogleHostedWorker), and the resolved input materials. You verify it with gcloud artifacts docker images describe --show-provenance, and you can wire the verification into Binary Authorization through the attestor resource backed by a Cloud KMS key.

For teams that have a heterogeneous build fleet, the pragmatic path is to keep a small set of blessed builders, each with a dedicated KMS-backed attestor, and refuse to admit anything else. I have seen organizations try to enumerate every builder in policy, and it does not scale. Whitelisting three attestors and gating exceptions on a platform-security review is both simpler and auditable.

Runtime identity: one service account per service

The default Cloud Run runtime service account is the Compute Engine default, PROJECT_NUMBER-compute@developer.gserviceaccount.com, which historically carries roles/editor at the project level. Google now warns about this in the console, but the number of services still running with it in early 2024 was startling to me.

Create a dedicated service account per Cloud Run service, grant it only the IAM roles it needs, and set it as the --service-account on the revision. For a service that only reads one Firestore collection, the right role is roles/datastore.viewer with an IAM condition scoped to that collection. For a service that publishes to one Pub/Sub topic, it is roles/pubsub.publisher on the topic, nothing more.

Rotate these service accounts on a schedule. Cloud Run does not cache credentials meaningfully, so rotation is mostly about key-less access anyway. Do not create JSON keys for Cloud Run service accounts, and set constraints/iam.disableServiceAccountKeyCreation at the organization level to prevent anyone from doing so accidentally. If an external system needs to impersonate the service account, use Workload Identity Federation with an appropriately scoped iam.serviceAccounts.getAccessToken grant, not static keys.

Ingress, egress, and the forgotten paths

Supply-chain security bleeds into network policy on Cloud Run more than people expect. A service that egresses to the public internet can be instructed by a poisoned dependency to exfiltrate data. A service that accepts public ingress is directly reachable by an attacker who wants to probe for the compromised dependency's beacon.

Set --ingress=internal-and-cloud-load-balancing for anything that does not need public reach, and set VPC egress to --vpc-egress=all-traffic with a Serverless VPC Access connector that routes through a Cloud NAT with explicit allowlisting. The allowlist is the control; the connector is the enforcement point. I audit the allowlist monthly, because the entropy of "let us just add googleapis.com real quick" is high.

VPC Service Controls add a second layer for data egress. Put Cloud Run inside a perimeter that includes Secret Manager, Artifact Registry, Firestore, and Cloud Storage for the project's data buckets. Service controls catch exfiltration attempts that route through legitimate API calls, which is exactly the path a sophisticated supply-chain attacker will take.

Monitor what actually runs

Cloud Audit Logs give you the full deploy history of a Cloud Run service. The alerts worth building are: a revision deployed outside a change window, a revision whose service account changed, a revision whose image came from a new repository, and any run.services.setIamPolicy call that grants roles/run.invoker to allUsers or allAuthenticatedUsers. The last one catches accidental public exposure, which happens surprisingly often.

Pair this with Cloud Run revision labels that encode the source commit and the build pipeline ID. When an incident requires you to identify every service running code from a specific commit, labels make it a single gcloud query rather than a forensic expedition.

How Safeguard Helps

Safeguard tracks every Cloud Run revision against its source attestation, SBOM, and build identity, so you can see at a glance whether what is running matches what your pipeline produced. When a revision is deployed outside the expected builder, runs with an unexpected service account, or contains components with newly disclosed vulnerabilities, Safeguard surfaces the finding against the specific service and revision. Continuous validation evidence, provenance freshness, and Binary Authorization exceptions all land in the same place your developers already triage findings. The result is a single view of Cloud Run supply-chain health that an auditor can read and an engineer can act on.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.