Manage GCP Pub/Sub Topic IAM Policies

The gcp:pubsub/topicIAMPolicy:TopicIAMPolicy resource, part of the Pulumi GCP provider, manages IAM permissions for Pub/Sub topics using three distinct approaches: complete policy replacement (TopicIAMPolicy), role-level binding (TopicIAMBinding), or individual member grants (TopicIAMMember). This guide focuses on three capabilities: complete policy replacement, role-level member binding, and individual member grants.

All three resources reference existing Pub/Sub topics by project and name. The examples are intentionally small. Combine them with your own topic infrastructure and organizational IAM policies.

Replace the entire IAM policy for a topic

When you need complete control over topic access, you can set the entire IAM policy at once, replacing any existing permissions.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const admin = gcp.organizations.getIAMPolicy({
    bindings: [{
        role: "roles/viewer",
        members: ["user:jane@example.com"],
    }],
});
const policy = new gcp.pubsub.TopicIAMPolicy("policy", {
    project: example.project,
    topic: example.name,
    policyData: admin.then(admin => admin.policyData),
});
import pulumi
import pulumi_gcp as gcp

admin = gcp.organizations.get_iam_policy(bindings=[{
    "role": "roles/viewer",
    "members": ["user:jane@example.com"],
}])
policy = gcp.pubsub.TopicIAMPolicy("policy",
    project=example["project"],
    topic=example["name"],
    policy_data=admin.policy_data)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/pubsub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		admin, err := organizations.LookupIAMPolicy(ctx, &organizations.LookupIAMPolicyArgs{
			Bindings: []organizations.GetIAMPolicyBinding{
				{
					Role: "roles/viewer",
					Members: []string{
						"user:jane@example.com",
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		_, err = pubsub.NewTopicIAMPolicy(ctx, "policy", &pubsub.TopicIAMPolicyArgs{
			Project:    pulumi.Any(example.Project),
			Topic:      pulumi.Any(example.Name),
			PolicyData: pulumi.String(admin.PolicyData),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var admin = Gcp.Organizations.GetIAMPolicy.Invoke(new()
    {
        Bindings = new[]
        {
            new Gcp.Organizations.Inputs.GetIAMPolicyBindingInputArgs
            {
                Role = "roles/viewer",
                Members = new[]
                {
                    "user:jane@example.com",
                },
            },
        },
    });

    var policy = new Gcp.PubSub.TopicIAMPolicy("policy", new()
    {
        Project = example.Project,
        Topic = example.Name,
        PolicyData = admin.Apply(getIAMPolicyResult => getIAMPolicyResult.PolicyData),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetIAMPolicyArgs;
import com.pulumi.gcp.pubsub.TopicIAMPolicy;
import com.pulumi.gcp.pubsub.TopicIAMPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var admin = OrganizationsFunctions.getIAMPolicy(GetIAMPolicyArgs.builder()
            .bindings(GetIAMPolicyBindingArgs.builder()
                .role("roles/viewer")
                .members("user:jane@example.com")
                .build())
            .build());

        var policy = new TopicIAMPolicy("policy", TopicIAMPolicyArgs.builder()
            .project(example.project())
            .topic(example.name())
            .policyData(admin.policyData())
            .build());

    }
}
resources:
  policy:
    type: gcp:pubsub:TopicIAMPolicy
    properties:
      project: ${example.project}
      topic: ${example.name}
      policyData: ${admin.policyData}
variables:
  admin:
    fn::invoke:
      function: gcp:organizations:getIAMPolicy
      arguments:
        bindings:
          - role: roles/viewer
            members:
              - user:jane@example.com

TopicIAMPolicy is authoritative: it replaces the topic’s entire IAM policy with the policy you provide. The policyData property accepts output from getIAMPolicy, which defines bindings (role-to-members mappings). This approach gives you complete control but requires coordination, since it overwrites any permissions set outside your Pulumi stack. TopicIAMPolicy cannot be used alongside TopicIAMBinding or TopicIAMMember for the same topic, as they would conflict over the policy state.

Grant a role to multiple members at once

Teams often need to grant the same role to several users or service accounts while preserving other role assignments.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const binding = new gcp.pubsub.TopicIAMBinding("binding", {
    project: example.project,
    topic: example.name,
    role: "roles/viewer",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.pubsub.TopicIAMBinding("binding",
    project=example["project"],
    topic=example["name"],
    role="roles/viewer",
    members=["user:jane@example.com"])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/pubsub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := pubsub.NewTopicIAMBinding(ctx, "binding", &pubsub.TopicIAMBindingArgs{
			Project: pulumi.Any(example.Project),
			Topic:   pulumi.Any(example.Name),
			Role:    pulumi.String("roles/viewer"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var binding = new Gcp.PubSub.TopicIAMBinding("binding", new()
    {
        Project = example.Project,
        Topic = example.Name,
        Role = "roles/viewer",
        Members = new[]
        {
            "user:jane@example.com",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.pubsub.TopicIAMBinding;
import com.pulumi.gcp.pubsub.TopicIAMBindingArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var binding = new TopicIAMBinding("binding", TopicIAMBindingArgs.builder()
            .project(example.project())
            .topic(example.name())
            .role("roles/viewer")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  binding:
    type: gcp:pubsub:TopicIAMBinding
    properties:
      project: ${example.project}
      topic: ${example.name}
      role: roles/viewer
      members:
        - user:jane@example.com

TopicIAMBinding is authoritative for a single role: it sets the complete member list for that role while leaving other roles untouched. The members property accepts a list of identity strings (users, service accounts, groups). If another stack or process grants the same role, TopicIAMBinding will overwrite those grants. You can use multiple TopicIAMBinding resources for different roles on the same topic, but only one binding per role.

Add a single member to a role incrementally

When managing permissions across multiple stacks or teams, you often need to add one member without coordinating with other permission grants.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const member = new gcp.pubsub.TopicIAMMember("member", {
    project: example.project,
    topic: example.name,
    role: "roles/viewer",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

member = gcp.pubsub.TopicIAMMember("member",
    project=example["project"],
    topic=example["name"],
    role="roles/viewer",
    member="user:jane@example.com")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/pubsub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := pubsub.NewTopicIAMMember(ctx, "member", &pubsub.TopicIAMMemberArgs{
			Project: pulumi.Any(example.Project),
			Topic:   pulumi.Any(example.Name),
			Role:    pulumi.String("roles/viewer"),
			Member:  pulumi.String("user:jane@example.com"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var member = new Gcp.PubSub.TopicIAMMember("member", new()
    {
        Project = example.Project,
        Topic = example.Name,
        Role = "roles/viewer",
        Member = "user:jane@example.com",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.pubsub.TopicIAMMember;
import com.pulumi.gcp.pubsub.TopicIAMMemberArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var member = new TopicIAMMember("member", TopicIAMMemberArgs.builder()
            .project(example.project())
            .topic(example.name())
            .role("roles/viewer")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:pubsub:TopicIAMMember
    properties:
      project: ${example.project}
      topic: ${example.name}
      role: roles/viewer
      member: user:jane@example.com

TopicIAMMember is non-authoritative: it adds a single member to a role without affecting other members. The member property accepts one identity string. Multiple TopicIAMMember resources can grant the same role to different members, and they coexist peacefully. This approach works well for distributed permission management but makes it harder to see the complete access picture in one place. TopicIAMMember can be used alongside TopicIAMBinding as long as they don’t grant the same role.

Beyond these examples

These snippets focus on specific IAM management features: authoritative vs non-authoritative IAM management, and policy-level, role-level, and member-level grants. They’re intentionally minimal rather than full access control configurations.

The examples reference pre-existing infrastructure such as Pub/Sub topics (by project and name). They focus on IAM binding configuration rather than topic provisioning.

To keep things focused, common IAM patterns are omitted, including:

  • IAM conditions for time-based or attribute-based access
  • Service account impersonation and domain restrictions
  • Audit logging configuration
  • Cross-project topic access

These omissions are intentional: the goal is to illustrate how each IAM resource type is wired, not provide drop-in access control modules. See the Pub/Sub TopicIAMPolicy resource reference for all available configuration options.

Let's manage GCP Pub/Sub Topic IAM Policies

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Selection & Conflicts
What's the difference between TopicIAMPolicy, TopicIAMBinding, and TopicIAMMember?
TopicIAMPolicy is authoritative and replaces the entire IAM policy. TopicIAMBinding is authoritative for a specific role, preserving other roles. TopicIAMMember is non-authoritative, adding individual members while preserving existing ones.
Which IAM resources can I use together?
TopicIAMPolicy cannot be used with TopicIAMBinding or TopicIAMMember as they’ll conflict over policy state. However, TopicIAMBinding and TopicIAMMember can be used together if they don’t grant the same role.
Why are my IAM resources fighting over policy state?
You’re likely mixing TopicIAMPolicy (authoritative) with TopicIAMBinding or TopicIAMMember. Use TopicIAMPolicy alone, or use only TopicIAMBinding/TopicIAMMember together.
Configuration & Setup
How do I generate the policyData for TopicIAMPolicy?
Use the gcp.organizations.getIAMPolicy data source with bindings, then pass its policyData output to the TopicIAMPolicy resource.
Can I change the topic or project after creating the IAM resource?
No, both topic and project are immutable. Changing them requires recreating the resource.
Import & Migration
What formats can I use when importing IAM policies?
Three formats are supported: projects/{{project}}/topics/{{name}}, {{project}}/{{name}}, or just {{name}}.
How do I import IAM resources with custom roles?
Use the full custom role name in the format [projects/my-project|organizations/my-org]/roles/my-custom-role.

Using a different cloud?

Explore security guides for other cloud providers: