Manage GCP GKE Hub Membership IAM Policies

The gcp:gkehub/membershipIamPolicy:MembershipIamPolicy resource, part of the Pulumi GCP provider, manages IAM policies for GKE Hub memberships, controlling who can access and manage registered clusters. This guide focuses on three approaches: authoritative policy replacement (MembershipIamPolicy), role-level binding management (MembershipIamBinding), and individual member grants (MembershipIamMember).

All three resources reference an existing GKE Hub membership by project, location, and membershipId. The examples are intentionally small. Combine them with your own membership infrastructure and access requirements.

Replace the entire IAM policy for a membership

When you need complete control over membership 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.gkehub.MembershipIamPolicy("policy", {
    project: membership.project,
    location: membership.location,
    membershipId: membership.membershipId,
    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.gkehub.MembershipIamPolicy("policy",
    project=membership["project"],
    location=membership["location"],
    membership_id=membership["membershipId"],
    policy_data=admin.policy_data)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"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 = gkehub.NewMembershipIamPolicy(ctx, "policy", &gkehub.MembershipIamPolicyArgs{
			Project:      pulumi.Any(membership.Project),
			Location:     pulumi.Any(membership.Location),
			MembershipId: pulumi.Any(membership.MembershipId),
			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.GkeHub.MembershipIamPolicy("policy", new()
    {
        Project = membership.Project,
        Location = membership.Location,
        MembershipId = membership.MembershipId,
        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.gkehub.MembershipIamPolicy;
import com.pulumi.gcp.gkehub.MembershipIamPolicyArgs;
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 MembershipIamPolicy("policy", MembershipIamPolicyArgs.builder()
            .project(membership.project())
            .location(membership.location())
            .membershipId(membership.membershipId())
            .policyData(admin.policyData())
            .build());

    }
}
resources:
  policy:
    type: gcp:gkehub:MembershipIamPolicy
    properties:
      project: ${membership.project}
      location: ${membership.location}
      membershipId: ${membership.membershipId}
      policyData: ${admin.policyData}
variables:
  admin:
    fn::invoke:
      function: gcp:organizations:getIAMPolicy
      arguments:
        bindings:
          - role: roles/viewer
            members:
              - user:jane@example.com

The MembershipIamPolicy resource is authoritative: it replaces all existing IAM bindings with the policy you provide. The policyData comes from the getIAMPolicy data source, which defines roles and members. Any permissions not included in policyData are removed when you apply this resource.

Grant a role to multiple members at once

Teams often need to assign the same role to several users without affecting other roles already on the membership.

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

const binding = new gcp.gkehub.MembershipIamBinding("binding", {
    project: membership.project,
    location: membership.location,
    membershipId: membership.membershipId,
    role: "roles/viewer",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.gkehub.MembershipIamBinding("binding",
    project=membership["project"],
    location=membership["location"],
    membership_id=membership["membershipId"],
    role="roles/viewer",
    members=["user:jane@example.com"])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := gkehub.NewMembershipIamBinding(ctx, "binding", &gkehub.MembershipIamBindingArgs{
			Project:      pulumi.Any(membership.Project),
			Location:     pulumi.Any(membership.Location),
			MembershipId: pulumi.Any(membership.MembershipId),
			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.GkeHub.MembershipIamBinding("binding", new()
    {
        Project = membership.Project,
        Location = membership.Location,
        MembershipId = membership.MembershipId,
        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.gkehub.MembershipIamBinding;
import com.pulumi.gcp.gkehub.MembershipIamBindingArgs;
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 MembershipIamBinding("binding", MembershipIamBindingArgs.builder()
            .project(membership.project())
            .location(membership.location())
            .membershipId(membership.membershipId())
            .role("roles/viewer")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  binding:
    type: gcp:gkehub:MembershipIamBinding
    properties:
      project: ${membership.project}
      location: ${membership.location}
      membershipId: ${membership.membershipId}
      role: roles/viewer
      members:
        - user:jane@example.com

The MembershipIamBinding resource is authoritative for a single role: it replaces all members for that role but preserves other roles. The members property lists all identities that should have this role. This approach works well when you manage role membership as a group rather than adding members individually.

Add a single member to a role incrementally

When you need to grant access to one user without disturbing existing permissions, you can add individual members to roles.

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

const member = new gcp.gkehub.MembershipIamMember("member", {
    project: membership.project,
    location: membership.location,
    membershipId: membership.membershipId,
    role: "roles/viewer",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

member = gcp.gkehub.MembershipIamMember("member",
    project=membership["project"],
    location=membership["location"],
    membership_id=membership["membershipId"],
    role="roles/viewer",
    member="user:jane@example.com")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := gkehub.NewMembershipIamMember(ctx, "member", &gkehub.MembershipIamMemberArgs{
			Project:      pulumi.Any(membership.Project),
			Location:     pulumi.Any(membership.Location),
			MembershipId: pulumi.Any(membership.MembershipId),
			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.GkeHub.MembershipIamMember("member", new()
    {
        Project = membership.Project,
        Location = membership.Location,
        MembershipId = membership.MembershipId,
        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.gkehub.MembershipIamMember;
import com.pulumi.gcp.gkehub.MembershipIamMemberArgs;
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 MembershipIamMember("member", MembershipIamMemberArgs.builder()
            .project(membership.project())
            .location(membership.location())
            .membershipId(membership.membershipId())
            .role("roles/viewer")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:gkehub:MembershipIamMember
    properties:
      project: ${membership.project}
      location: ${membership.location}
      membershipId: ${membership.membershipId}
      role: roles/viewer
      member: user:jane@example.com

The MembershipIamMember resource is non-authoritative: it adds one member to one role without removing others. You can use multiple MembershipIamMember resources safely, even for the same role, as long as they grant access to different members. This is the safest option when multiple teams manage access independently.

Beyond these examples

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

The examples reference pre-existing infrastructure such as GKE Hub membership (project, location, membershipId). They focus on IAM policy configuration rather than provisioning the membership itself.

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

  • Conditional IAM bindings (conditions)
  • Audit logging configuration (auditConfigs)
  • Custom role definitions
  • Service account impersonation

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 GKE Hub MembershipIamPolicy resource reference for all available configuration options.

Let's manage GCP GKE Hub Membership 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 MembershipIamPolicy, MembershipIamBinding, and MembershipIamMember?
MembershipIamPolicy is authoritative and replaces the entire IAM policy. MembershipIamBinding is authoritative for a specific role but preserves other roles. MembershipIamMember is non-authoritative and adds individual members while preserving existing members for that role.
Which IAM resources can I use together?
You cannot use MembershipIamPolicy with MembershipIamBinding or MembershipIamMember, as they will conflict. However, you can use MembershipIamBinding and MembershipIamMember together only if they manage different roles.
Why are my IAM resources fighting over the policy?
Mixing MembershipIamPolicy (authoritative) with MembershipIamBinding or MembershipIamMember (granular) causes conflicts. Choose one approach: either use MembershipIamPolicy alone, or use MembershipIamBinding/MembershipIamMember without MembershipIamPolicy.
Configuration & Setup
How do I generate the policyData for MembershipIamPolicy?
Use the gcp.organizations.getIAMPolicy data source with your desired bindings, then pass its policyData output to the MembershipIamPolicy resource.
What's the default location for a membership?
The default location is global. If not specified, it’s parsed from the parent resource identifier or taken from the provider configuration.
Can I change the membershipId or location after creation?
No, both membershipId and location are immutable after creation. Changing them requires recreating the resource.
Import & Migration
How do I import IAM resources with different formats?

Import syntax varies by resource type:

  • Policy: pulumi import gcp:gkehub/membershipIamPolicy:MembershipIamPolicy editor projects/{{project}}/locations/{{location}}/memberships/{{membership_id}}
  • Binding: Add the role: ...{{membership_id}} roles/viewer
  • Member: Add role and member: ...{{membership_id}} roles/viewer user:jane@example.com
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, not just the short role name.

Using a different cloud?

Explore security guides for other cloud providers: