Manage GCP Spanner Instance IAM Policies

The gcp:spanner/instanceIAMPolicy:InstanceIAMPolicy resource, part of the Pulumi GCP provider, manages IAM policies for Spanner instances with three levels of control. This guide focuses on three capabilities: authoritative policy replacement (InstanceIAMPolicy), role-level member lists (InstanceIAMBinding), and individual member grants (InstanceIAMMember).

These resources reference existing Spanner instances and work with IAM principals such as users, service accounts, and groups. The examples are intentionally small. Combine them with your own instance references and IAM principals. Note that InstanceIAMPolicy cannot be used alongside InstanceIAMBinding or InstanceIAMMember, as they will conflict over policy state.

Replace the entire IAM policy authoritatively

When you need complete control over all IAM bindings, InstanceIAMPolicy replaces the entire policy in one operation, removing any existing bindings not included.

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

const admin = gcp.organizations.getIAMPolicy({
    bindings: [{
        role: "roles/editor",
        members: ["user:jane@example.com"],
    }],
});
const instance = new gcp.spanner.InstanceIAMPolicy("instance", {
    instance: "your-instance-name",
    policyData: admin.then(admin => admin.policyData),
});
import pulumi
import pulumi_gcp as gcp

admin = gcp.organizations.get_iam_policy(bindings=[{
    "role": "roles/editor",
    "members": ["user:jane@example.com"],
}])
instance = gcp.spanner.InstanceIAMPolicy("instance",
    instance="your-instance-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/spanner"
	"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/editor",
					Members: []string{
						"user:jane@example.com",
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		_, err = spanner.NewInstanceIAMPolicy(ctx, "instance", &spanner.InstanceIAMPolicyArgs{
			Instance:   pulumi.String("your-instance-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/editor",
                Members = new[]
                {
                    "user:jane@example.com",
                },
            },
        },
    });

    var instance = new Gcp.Spanner.InstanceIAMPolicy("instance", new()
    {
        Instance = "your-instance-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.spanner.InstanceIAMPolicy;
import com.pulumi.gcp.spanner.InstanceIAMPolicyArgs;
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/editor")
                .members("user:jane@example.com")
                .build())
            .build());

        var instance = new InstanceIAMPolicy("instance", InstanceIAMPolicyArgs.builder()
            .instance("your-instance-name")
            .policyData(admin.policyData())
            .build());

    }
}
resources:
  instance:
    type: gcp:spanner:InstanceIAMPolicy
    properties:
      instance: your-instance-name
      policyData: ${admin.policyData}
variables:
  admin:
    fn::invoke:
      function: gcp:organizations:getIAMPolicy
      arguments:
        bindings:
          - role: roles/editor
            members:
              - user:jane@example.com

The policyData property accepts output from getIAMPolicy, which defines bindings as role-member pairs. InstanceIAMPolicy is authoritative: it removes any permissions not explicitly listed, including default grants. This can lock you out if you omit necessary bindings.

Grant a role to multiple members at once

Teams often assign the same role to several users, such as granting database admin access to an operations team.

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

const instance = new gcp.spanner.InstanceIAMBinding("instance", {
    instance: "your-instance-name",
    role: "roles/spanner.databaseAdmin",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

instance = gcp.spanner.InstanceIAMBinding("instance",
    instance="your-instance-name",
    role="roles/spanner.databaseAdmin",
    members=["user:jane@example.com"])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := spanner.NewInstanceIAMBinding(ctx, "instance", &spanner.InstanceIAMBindingArgs{
			Instance: pulumi.String("your-instance-name"),
			Role:     pulumi.String("roles/spanner.databaseAdmin"),
			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 instance = new Gcp.Spanner.InstanceIAMBinding("instance", new()
    {
        Instance = "your-instance-name",
        Role = "roles/spanner.databaseAdmin",
        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.spanner.InstanceIAMBinding;
import com.pulumi.gcp.spanner.InstanceIAMBindingArgs;
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 instance = new InstanceIAMBinding("instance", InstanceIAMBindingArgs.builder()
            .instance("your-instance-name")
            .role("roles/spanner.databaseAdmin")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  instance:
    type: gcp:spanner:InstanceIAMBinding
    properties:
      instance: your-instance-name
      role: roles/spanner.databaseAdmin
      members:
        - user:jane@example.com

InstanceIAMBinding is authoritative for a single role. The members array lists all principals who should have this role. Other roles on the instance remain unchanged, but any members not in this list lose the specified role.

Add a single member to a role incrementally

When onboarding individual users, InstanceIAMMember adds one member without affecting others who already have the role.

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

const instance = new gcp.spanner.InstanceIAMMember("instance", {
    instance: "your-instance-name",
    role: "roles/spanner.databaseAdmin",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

instance = gcp.spanner.InstanceIAMMember("instance",
    instance="your-instance-name",
    role="roles/spanner.databaseAdmin",
    member="user:jane@example.com")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := spanner.NewInstanceIAMMember(ctx, "instance", &spanner.InstanceIAMMemberArgs{
			Instance: pulumi.String("your-instance-name"),
			Role:     pulumi.String("roles/spanner.databaseAdmin"),
			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 instance = new Gcp.Spanner.InstanceIAMMember("instance", new()
    {
        Instance = "your-instance-name",
        Role = "roles/spanner.databaseAdmin",
        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.spanner.InstanceIAMMember;
import com.pulumi.gcp.spanner.InstanceIAMMemberArgs;
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 instance = new InstanceIAMMember("instance", InstanceIAMMemberArgs.builder()
            .instance("your-instance-name")
            .role("roles/spanner.databaseAdmin")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  instance:
    type: gcp:spanner:InstanceIAMMember
    properties:
      instance: your-instance-name
      role: roles/spanner.databaseAdmin
      member: user:jane@example.com

InstanceIAMMember is non-authoritative: it grants a role to one member without modifying other assignments. You can use multiple InstanceIAMMember resources for the same role, or combine them with InstanceIAMBinding for different roles.

Beyond these examples

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

The examples reference pre-existing infrastructure such as Spanner instances. They focus on IAM policy configuration rather than provisioning instances or principals.

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

  • Project specification (project property)
  • Conditional IAM bindings
  • Service account creation and management
  • Custom role definitions

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 Spanner InstanceIAMPolicy resource reference for all available configuration options.

Let's manage GCP Spanner Instance 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 the three IAM resource types?

There are three options for managing Spanner instance IAM:

  1. InstanceIAMPolicy - Authoritative, replaces the entire IAM policy
  2. InstanceIAMBinding - Authoritative for a specific role, preserves other roles
  3. InstanceIAMMember - Non-authoritative, adds a single member to a role while preserving other members
Which IAM resources can I use together?
gcp.spanner.InstanceIAMPolicy cannot be used with gcp.spanner.InstanceIAMBinding or gcp.spanner.InstanceIAMMember because they will conflict. However, gcp.spanner.InstanceIAMBinding and gcp.spanner.InstanceIAMMember can be used together as long as they don’t grant privileges to the same role.
Common Pitfalls
Can I lock myself out of my Spanner instance?
Yes, when using gcp.spanner.InstanceIAMPolicy. It replaces the entire IAM policy, removing any default permissions not explicitly included in your configuration. Make sure to include all necessary permissions you want to preserve.
Why am I seeing IAM policy conflicts or unexpected changes?
You’re likely mixing incompatible IAM resources. gcp.spanner.InstanceIAMPolicy cannot coexist with gcp.spanner.InstanceIAMBinding or gcp.spanner.InstanceIAMMember as they will fight over the policy. Choose one approach and stick with it.
Configuration & Immutability
What properties can't be changed after creation?
Both instance and project are immutable. Changing either requires replacing the resource.
How do I import a custom IAM role?
Use the full name format: [projects/my-project|organizations/my-org]/roles/my-custom-role.

Using a different cloud?

Explore security guides for other cloud providers: