❯Cloud Run Pub/Sub Push Subscriptions: Setup, IAM Authentication, and OIDC Security
Setting up Cloud Run with Pub/Sub push subscription and OIDC authentication.
This is the third article of a series. See the footer for the links to the other articles.
This article explains how to connect Google Cloud Pub/Sub to Cloud Run using push subscriptions with OIDC authentication and IAM roles. We walk through a production-ready configuration including service accounts, invoker permissions, secure ingress settings, and Terraform-based deployment. The goal is to implement a secure, serverless event-driven architecture where Pub/Sub reliably triggers Cloud Run services with proper authentication and network isolation.
Table of Contents
IAM Permission setup for Pub/Sub and Cloud Run
There are 3 fundamental identities at play in our configuration:
- the Pub/Sub Service Agent: This is an internal Google-managed service account that sends messages via a push subscription to the Cloud Run Service.
- The Cloud Run Invoker: This is a user-managed Service Account impersonated by the Pub/Sub subscription. It must be granted the
roles/run.invokerpermission on the Cloud Run service in order to run the container. Its sole purpose is to serve as the identity for which Pub/Sub mints an OpenID Connect (OIDC) token to prove the request is authorized. - The Worker: this is the identity used by the container while it executes. When your code needs to talk to a database (like Cloud SQL), Cloud Storage, or an external API, it uses this identity. By default, this is the Compute Engine default service account, but it should be configured as a custom, least-privileged service account.
Notice how there are two distinct identities related to Cloud Run: the invoker (which gets the request through the door) and the worker (which executes the code). The invoker service account requires no permissions other than roles/run.invoker. Pub/Sub utilizes its internal service agent to mint an OIDC token for this invoker identity, attaching it to the Authorization: Bearer <TOKEN> header of the push request.
Cloud Run Pub/Sub push subscription setup with OIDC authentication
- Pub/Sub sends an HTTP POST request to the Cloud Run URL. Inside the Authorization header is the OIDC token representing the invoker identity.
- The container deployed within Cloud Run is behind Google Front End (GFE) and Cloud Run's regional control plane infrastructure. When you set your service to "Require Authentication" (by not granting
roles/run.invokertoallUsers), you are instructing this proxy layer to intercept and validate all inbound request. - The GFE terminates the TLS connection and hands the HTTP request to Cloud Run's internal infrastructure.
- The proxy layer extracts the OIDC token, validates its signature, and checks IAM to verify that the invoker identity claims inside the token possess the roles/run.invoker permission for this specific service.
- If allowed: The proxy lets the request pass through, routes it to an available container instance (or triggers a cold start to spin one up), and forwards the request.
- If denied: If the token is missing or invalid, the proxy rejects the request and returns an HTTP 401 Unauthorized. If the token is valid but lacks the required IAM permission, it returns an HTTP 403 Forbidden.
It is worth pointing out a few more concepts to close the gaps in the model above:
We could in theory create a Pub/Sub push subscription without authentication enabled, allowing anybody to trigger the function without a token. For security reasons we want the subscription to require authentication, specifying that only messages coming from the invoker (as specified in the token) can be authorized. In Terraform, the push subscription to Cloud Run looks like this:
# Pub/Sub push subscription to cloud run
resource "google_pubsub_subscription" "cloud_run_trigger" {
name = "cloud-run-trigger"
topic = google_pubsub_topic.my_topic.id
project = var.project_id
ack_deadline_seconds = 200
message_retention_duration = "604800s"
retain_acked_messages = true
push_config {
push_endpoint = google_cloud_run_v2_service.my_service.uri
oidc_token {
# This account mints the token
service_account_email = google_service_account.invoker.email
audience = google_cloud_run_v2_service.my_service.uri
}
}
retry_policy {
minimum_backoff = "10s"
maximum_backoff = "600s"
}
dead_letter_policy {
dead_letter_topic = google_pubsub_topic.my_service_dlq.id
max_delivery_attempts = 5
}
}We need to explicitly allow Pub/Sub to mint the token. First, we assign the roles/iam.serviceAccountTokenCreator to the Pub/Sub service agent on the invoker service account resource. This gives Pub/Sub the permission to impersonate the invoker, mint the OIDC token, and attach it to the push request. In Terraform we grant the permission like so:
# Pub/Sub impersonates the invoker SA to mint a token
resource "google_service_account_iam_member" "pubsub_token_creator" {
service_account_id = google_service_account.invoker.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${data.google_project_service_identity.pubsub_agent.email}"
}To complete our subscription configuration we also need to allow Pub/Sub to publish messages to the Dead Letter Queue (DLQ):
# Allow Pub/Sub service agent to push failed items to your DLQ topic
resource "google_pubsub_topic_iam_member" "pubsub_dlq_publisher" {
topic = google_pubsub_topic.service_dlq.name
role = "roles/pubsub.publisher"
member = "serviceAccount:${locals.pubsub_serivce_agent.email}"
}Securing the Cloud Run Service Deployment
A couple of fundamental configuration options help strenghten the security posture of the Cloud Run deployment.
First we set the ingress to INGRESS_TRAFFIC_INTERNAL_ONLY. According to the documentation, setting the ingress to internal-only allows requests from Pub/Sub to stay within the Google network. Requests from other sources, including the internet, cannot reach your service (at the run.app URL or custom domains).
resource "google_cloud_run_v2_service" "invoice_processor" {
name = "invoice-processor"
location = var.region
# Allows Pub/Sub making the endpoint unreachable from the public internet
ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY"
template {
# This is the Worker service account
service_account = google_service_account.worker_sa.email
timeout = "120s"
max_instance_request_concurrency = 80
scaling {
max_instance_count = var.invoice_processor_max_instances
}
vpc_access {
# force Cloud Run egress through the VPC
egress = "ALL_TRAFFIC"
}
containers {
image = "${var.region}.pkg.dev/${var.project_id}/${var.repo_and_image_name}"
resources {
limits = {
memory = "256Mi"
cpu = "1"
}
}
}
}
}To run workflows with the worker service account and benefit from the permissions associated with it, we point cloud run to the worker service account.
Egress and VPC Service Controls (VPC-SC)
A third note concerns egress. Setting egress = "ALL_TRAFFIC" forces all outbound network traffic originating from the Cloud Run container to route through a private IP gateway inside your VPC (via Direct VPC Egress or a Serverless VPC Access Connector). This allows you to apply standard VPC firewall rules and monitor network-level connections to external IP addresses.
However, network-level controls generally cannot enforce resource-level restrictions within Google APIs. For example, they cannot distinguish between writing data to an approved Cloud Storage bucket and writing data to an attacker-controlled Cloud Storage bucket when both operations use legitimate Google API endpoints.
To mitigate this specific vector, we implement VPC Service Controls (VPC-SC):
- Cloud Run and Pub/Sub are Google-managed services rather than resources deployed directly into a customer's VPC network. Instead, VPC-SC acts as an extra authorization boundary at the Google API level.
- When a workload attempts to access a resource outside the permitted VPC Service Controls perimeter, Google Cloud evaluates the request against the perimeter's ingress and egress policies. If the operation violates the perimeter policy, the request is denied and typically returns a 403 VPC Service Controls error, preventing this Google API–based data exfiltration path.
VPC Service Controls primarily protect access to supported Google Cloud APIs and data services. Their effectiveness depends on which services are included in the perimeter and whether those services support VPC-SC enforcement. VPC connectivity must be explicitly configured and will be the subject of another article.