Pub/Sub is the connective tissue for a surprising number of GCP-native systems. It sits between Cloud Functions and their triggers, between microservices and their event streams, and between data pipelines and their audit logs. When it is set up correctly, it is the kind of infrastructure you forget about. When it is set up poorly, it becomes the path by which an attacker pivots from one compromised component to every downstream consumer.
This post is the Pub/Sub security configuration I arrived at after reviewing about 30 Pub/Sub deployments across the back half of 2024. It is opinionated. The opinions are what I would defend in an incident postmortem, which is the only test of a security configuration that matters.
Topic and subscription IAM, with scope that actually works
Pub/Sub IAM has two levels: topic-level and subscription-level. A topic grants publishers the right to publish messages, and a subscription grants subscribers the right to pull or receive pushed messages. The roles that matter are roles/pubsub.publisher on topics, and roles/pubsub.subscriber on subscriptions.
Use topic-level and subscription-level IAM, not project-level. roles/pubsub.editor at the project grants full control over every topic and subscription in the project, which is almost never what you want. roles/pubsub.viewer at the project grants the right to list and describe all topics and subscriptions, which is usually more visibility than any non-admin needs.
The least-privilege pattern I use: each producer has roles/pubsub.publisher on the specific topics it publishes to, granted with an IAM condition if the consumer changes topics based on environment (resource.name.startsWith("projects/PROJECT/topics/orders-")). Each consumer has roles/pubsub.subscriber on the specific subscription it reads, not on the topic. Subscription-level grants separate consumers from each other even when they read the same topic, which is the exact pattern you want for fan-out architectures.
Audit IAM with a Cloud Asset Inventory query that lists all Pub/Sub bindings and flags any that include allUsers, allAuthenticatedUsers, or broad principal groups. The first two are hard errors; public Pub/Sub topics are almost never intentional. The last one catches the "we gave the whole engineering group publish access for a sprint" situation that then persists.
Encryption in transit and at rest
Pub/Sub encrypts messages at rest with Google-managed keys by default. For regulated workloads, configure customer-managed encryption keys (CMEK) through Cloud KMS. The topic's kmsKeyName field points to a KMS key, and the service account service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com needs roles/cloudkms.cryptoKeyEncrypterDecrypter on the key.
CMEK on Pub/Sub is not a free security improvement for every workload, but it has two properties that matter in specific cases. First, you can rotate the key, and rotation invalidates the cached message plaintext such that messages encrypted under the old key version cannot be decrypted with the new key without an explicit re-wrap. Second, you can revoke the key, which is the emergency kill switch for a topic whose contents have been exposed.
In transit, Pub/Sub uses TLS 1.2 or higher for all client connections. There is no configuration here; the service does not accept unencrypted connections. For push subscriptions, the push endpoint URL must use HTTPS, and I strongly prefer endpoints under a Google Cloud Load Balancer with a managed certificate rather than third-party URLs, because the auth story is cleaner with Google-hosted endpoints.
VPC Service Controls, and the service perimeter
Pub/Sub is supported by VPC Service Controls. Put it in the perimeter for any project where Pub/Sub is the conduit for sensitive data. The perimeter blocks data exfiltration paths even when IAM is misconfigured, which is the exact scenario VPC-SC was designed for.
The wrinkle with Pub/Sub and VPC-SC is cross-project subscriptions. If project A publishes to a topic and project B has a subscription to it, both projects must be in the same perimeter (or connected via a perimeter bridge) for the subscription to work. I have seen more than one incident where an otherwise-correct VPC-SC rollout broke Pub/Sub because the subscriber project was missed. The fix is to audit cross-project Pub/Sub relationships before the rollout, and to add the subscriber projects explicitly.
Private Google Access on the subscriber's VPC lets the subscriber pull messages without traversing the public internet, which is the right default for anything in a VPC. For Cloud Run and Cloud Functions subscribers, the push mode handles this for you; for GCE or GKE subscribers pulling messages, set up the private endpoint.
Push vs. pull, and the auth you need for each
Push subscriptions deliver messages to an HTTPS endpoint that you specify. Pull subscriptions are read by your consumer at its own pace. Both have authentication patterns you need to configure explicitly.
For push, enable OIDC authentication on the subscription by setting pushConfig.oidcToken.serviceAccountEmail to a service account that the push endpoint trusts. The endpoint receives each request with a signed JWT that it validates against Google's JWKS. Without this, a push endpoint is a public HTTPS URL that anyone on the internet can POST to, which is a trivial DoS vector and a potential abuse pathway if the endpoint does not validate payloads.
For pull, the consumer authenticates via its own service account. Make sure the consumer is not running with broad credentials; scope the service account's roles to roles/pubsub.subscriber on the specific subscription plus whatever else the downstream processing requires.
Dead-letter topics, poison messages, and the replay problem
Every production subscription should have a dead-letter topic. A message that repeatedly fails to process gets routed to the dead-letter topic after maxDeliveryAttempts attempts, which defaults to 5. Without a dead-letter topic, a poisoned message drives retry storms, inflates costs, and can exhaust the subscription's quota.
The dead-letter topic itself needs IAM. The Pub/Sub service account needs roles/pubsub.publisher on the dead-letter topic, and the remediation consumer needs roles/pubsub.subscriber on its dead-letter subscription. Do not grant anything else; the dead-letter flow is a narrow pipe and should stay narrow.
Replay, via the seek operation on a subscription, is a useful recovery tool and a security consideration. A subscriber who can call pubsub.subscriptions.seek can replay old messages, which may have been processed already. For audit and billing pipelines, that can cause correctness issues. Restrict roles/pubsub.editor (the role that includes seek) to break-glass accounts, and audit any seek call in Cloud Audit Logs.
Schema validation, for the supply-chain story
Pub/Sub schemas, generally available since October 2021, let you enforce a schema on messages published to a topic. Supported formats are Protocol Buffers and Apache Avro. Binding a schema to a topic rejects malformed messages at publish time, which is a cheap defense against adversarial inputs from a compromised publisher.
Schema validation is not, on its own, a security control. A compromised publisher can still publish schema-valid messages with malicious payloads. But schema validation narrows the input surface for consumers, which pairs well with consumer-side validation. For internal event streams that a consumer trusts more than it should, this is worth the small operational cost.
Audit, detection, and the alerts worth building
Cloud Audit Logs capture every Pub/Sub admin-api operation: topic creation, subscription creation, IAM changes, schema updates, dead-letter configuration changes. Data-access logs, which are disabled by default, capture every publish and pull; enable them only on the topics where the message content is not sensitive, because enabling them on everything creates a compliance problem (the log itself becomes a sensitive record).
The alerts worth building: a subscription pointing to a new push endpoint, a topic gaining a new publisher from a service account not in the expected set, a CMEK key rotation or revocation, a dead-letter topic disabled, and any pubsub.subscriptions.seek call on a production subscription. Route them to the security team's alerting channel, not the application team's, because these events are more often attack signals than operational ones.
How Safeguard Helps
Safeguard inventories every Pub/Sub topic and subscription across your GCP organization, maps the producers and consumers of each, and scores the configuration against policy: IAM scope, CMEK coverage, VPC Service Controls, push authentication, dead-letter presence, and schema enforcement. Cross-project subscription relationships are rendered as a dependency graph so you can see the blast radius of a compromised topic. Drift in IAM, key rotation, or push endpoint configuration surfaces as findings against the specific topic or subscription. The result is a Pub/Sub deployment whose messaging backbone is as well understood and defended as the services it connects.