Summary
Creating a Pub/Sub channel whose subscription carries a DeadLetterPolicy unconditionally reads and
writes project-level IAM. If the caller's principal cannot do that, channel creation throws and the
consumer never starts — even when the dead-letter topic and its IAM binding already exist.
That makes Brighter require permissions an application service account usually does not have, because
the DLQ is normally provisioned by infrastructure-as-code rather than by the app at startup.
The path
GcpPubSubMessageGateway.EnsureSubscriptionExistsAsync (:235) calls
UpdateIAmRoleForDeadLetterAsync (:481) whenever DeadLetter != null. That method makes two calls,
neither of which is guarded or optional:
ProjectsClient.GetProjectAsync — Cloud Resource Manager, used to derive the default Pub/Sub
service account when DeadLetterPolicy.PublisherMember is unset. Needs resourcemanager.projects.get.
PublisherServiceApiClient.IAMPolicyClient.GetIamPolicyAsync (then SetIamPolicy when the binding
is missing) on the dead-letter topic. Needs pubsub.topics.getIamPolicy / setIamPolicy.
Any failure propagates out of CreateSyncChannel / CreateAsyncChannelAsync.
How it showed up
Running the FR-23 conformance behaviour (#4240 / #4297) against the Pub/Sub emulator
(gcr.io/google.com/cloudsdktool/cloud-sdk:emulators, PUBSUB_EMULATOR_HOST=localhost:8085), all
eight tests — four GCP configurations × both variants — fail in ~4 s during arrange, before any pump
runs:
| call |
result on the emulator |
ProjectsClient.GetProjectAsync |
Unauthenticated: Request had invalid authentication credentials — the request leaves for real GCP. Resource Manager is a different service from Pub/Sub, so PUBSUB_EMULATOR_HOST cannot redirect it |
IAMPolicyClient.GetIamPolicyAsync |
Unimplemented — the emulator has no IAM surface at all |
The second was isolated rather than inferred: setting DeadLetterPolicy.PublisherMember explicitly
skips the Resource Manager call, and the run then fails one line later on GetIamPolicy.
Suggested fix
Tolerate Unimplemented and PermissionDenied from both calls — log and continue rather than throw.
The binding may already exist, and a principal that cannot read the policy also cannot tell. Optionally
add an opt-out on DeadLetterPolicy for callers who provision IAM out of band.
Impact
- Production: any application whose service account lacks project IAM admin cannot create a
DLQ-backed channel, even against a correctly provisioned topic.
- Testing: no GCP behaviour that needs a DLQ can be exercised against the Pub/Sub emulator, so the
four GCP FR-23 conformance cells can only ever be verified by gcp-ci against a real project.
Summary
Creating a Pub/Sub channel whose subscription carries a
DeadLetterPolicyunconditionally reads andwrites project-level IAM. If the caller's principal cannot do that, channel creation throws and the
consumer never starts — even when the dead-letter topic and its IAM binding already exist.
That makes Brighter require permissions an application service account usually does not have, because
the DLQ is normally provisioned by infrastructure-as-code rather than by the app at startup.
The path
GcpPubSubMessageGateway.EnsureSubscriptionExistsAsync(:235) callsUpdateIAmRoleForDeadLetterAsync(:481) wheneverDeadLetter != null. That method makes two calls,neither of which is guarded or optional:
ProjectsClient.GetProjectAsync— Cloud Resource Manager, used to derive the default Pub/Subservice account when
DeadLetterPolicy.PublisherMemberis unset. Needsresourcemanager.projects.get.PublisherServiceApiClient.IAMPolicyClient.GetIamPolicyAsync(thenSetIamPolicywhen the bindingis missing) on the dead-letter topic. Needs
pubsub.topics.getIamPolicy/setIamPolicy.Any failure propagates out of
CreateSyncChannel/CreateAsyncChannelAsync.How it showed up
Running the FR-23 conformance behaviour (#4240 / #4297) against the Pub/Sub emulator
(
gcr.io/google.com/cloudsdktool/cloud-sdk:emulators,PUBSUB_EMULATOR_HOST=localhost:8085), alleight tests — four GCP configurations × both variants — fail in ~4 s during arrange, before any pump
runs:
ProjectsClient.GetProjectAsyncUnauthenticated: Request had invalid authentication credentials— the request leaves for real GCP. Resource Manager is a different service from Pub/Sub, soPUBSUB_EMULATOR_HOSTcannot redirect itIAMPolicyClient.GetIamPolicyAsyncUnimplemented— the emulator has no IAM surface at allThe second was isolated rather than inferred: setting
DeadLetterPolicy.PublisherMemberexplicitlyskips the Resource Manager call, and the run then fails one line later on
GetIamPolicy.Suggested fix
Tolerate
UnimplementedandPermissionDeniedfrom both calls — log and continue rather than throw.The binding may already exist, and a principal that cannot read the policy also cannot tell. Optionally
add an opt-out on
DeadLetterPolicyfor callers who provision IAM out of band.Impact
DLQ-backed channel, even against a correctly provisioned topic.
four GCP FR-23 conformance cells can only ever be verified by
gcp-ciagainst a real project.