Configure GCP Spanner Database IAM Permissions

The gcp:spanner/databaseIAMBinding:DatabaseIAMBinding resource, part of the Pulumi GCP provider, manages all members who have a specific IAM role on a Spanner database. When you apply a binding, it replaces any existing members for that role. This guide focuses on two capabilities: granting roles to multiple members and applying conditional access with CEL expressions.

DatabaseIAMBinding is one of three IAM resources for Spanner databases. It’s authoritative for a single role but preserves other roles on the database. DatabaseIAMBinding can coexist with DatabaseIAMMember resources as long as they manage different roles. The examples are intentionally small. Combine them with your own Spanner infrastructure and identity management.

Grant a role to multiple members at once

When multiple users or service accounts need the same database permissions, DatabaseIAMBinding manages them as a group.

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

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

database = gcp.spanner.DatabaseIAMBinding("database",
    instance="your-instance-name",
    database="your-database-name",
    role="roles/compute.networkUser",
    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.NewDatabaseIAMBinding(ctx, "database", &spanner.DatabaseIAMBindingArgs{
			Instance: pulumi.String("your-instance-name"),
			Database: pulumi.String("your-database-name"),
			Role:     pulumi.String("roles/compute.networkUser"),
			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 database = new Gcp.Spanner.DatabaseIAMBinding("database", new()
    {
        Instance = "your-instance-name",
        Database = "your-database-name",
        Role = "roles/compute.networkUser",
        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.DatabaseIAMBinding;
import com.pulumi.gcp.spanner.DatabaseIAMBindingArgs;
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 database = new DatabaseIAMBinding("database", DatabaseIAMBindingArgs.builder()
            .instance("your-instance-name")
            .database("your-database-name")
            .role("roles/compute.networkUser")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  database:
    type: gcp:spanner:DatabaseIAMBinding
    properties:
      instance: your-instance-name
      database: your-database-name
      role: roles/compute.networkUser
      members:
        - user:jane@example.com

The role property specifies which IAM role to grant. The members array lists all identities that should have this role; any existing members not in this list are removed. The instance and database properties identify which Spanner database to configure. This binding is authoritative for the specified role: it replaces all existing members.

Apply conditional access based on resource attributes

For fine-grained control, IAM Conditions restrict permissions to specific database roles or resources.

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

const database = new gcp.spanner.DatabaseIAMBinding("database", {
    instance: "your-instance-name",
    database: "your-database-name",
    role: "roles/compute.networkUser",
    members: ["user:jane@example.com"],
    condition: {
        title: "My Role",
        description: "Grant permissions on my_role",
        expression: "(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))",
    },
});
import pulumi
import pulumi_gcp as gcp

database = gcp.spanner.DatabaseIAMBinding("database",
    instance="your-instance-name",
    database="your-database-name",
    role="roles/compute.networkUser",
    members=["user:jane@example.com"],
    condition={
        "title": "My Role",
        "description": "Grant permissions on my_role",
        "expression": "(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))",
    })
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.NewDatabaseIAMBinding(ctx, "database", &spanner.DatabaseIAMBindingArgs{
			Instance: pulumi.String("your-instance-name"),
			Database: pulumi.String("your-database-name"),
			Role:     pulumi.String("roles/compute.networkUser"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
			Condition: &spanner.DatabaseIAMBindingConditionArgs{
				Title:       pulumi.String("My Role"),
				Description: pulumi.String("Grant permissions on my_role"),
				Expression:  pulumi.String("(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))"),
			},
		})
		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 database = new Gcp.Spanner.DatabaseIAMBinding("database", new()
    {
        Instance = "your-instance-name",
        Database = "your-database-name",
        Role = "roles/compute.networkUser",
        Members = new[]
        {
            "user:jane@example.com",
        },
        Condition = new Gcp.Spanner.Inputs.DatabaseIAMBindingConditionArgs
        {
            Title = "My Role",
            Description = "Grant permissions on my_role",
            Expression = "(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.spanner.DatabaseIAMBinding;
import com.pulumi.gcp.spanner.DatabaseIAMBindingArgs;
import com.pulumi.gcp.spanner.inputs.DatabaseIAMBindingConditionArgs;
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 database = new DatabaseIAMBinding("database", DatabaseIAMBindingArgs.builder()
            .instance("your-instance-name")
            .database("your-database-name")
            .role("roles/compute.networkUser")
            .members("user:jane@example.com")
            .condition(DatabaseIAMBindingConditionArgs.builder()
                .title("My Role")
                .description("Grant permissions on my_role")
                .expression("(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))")
                .build())
            .build());

    }
}
resources:
  database:
    type: gcp:spanner:DatabaseIAMBinding
    properties:
      instance: your-instance-name
      database: your-database-name
      role: roles/compute.networkUser
      members:
        - user:jane@example.com
      condition:
        title: My Role
        description: Grant permissions on my_role
        expression: (resource.type == "spanner.googleapis.com/DatabaseRole" && (resource.name.endsWith("/myrole")))

The condition block adds a CEL expression that evaluates at request time. The expression checks resource type and name, granting access only when the condition matches. The title and description document the condition’s purpose. Here, the binding grants roles/compute.networkUser only for operations on the database role named “myrole”.

Beyond these examples

These snippets focus on specific DatabaseIAMBinding features: role-based member management and conditional access with CEL expressions. They’re intentionally minimal rather than complete access control configurations.

The examples reference pre-existing infrastructure such as Spanner instances and databases, and database roles for conditional access. They focus on configuring IAM bindings rather than provisioning the underlying Spanner resources.

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

  • Full policy replacement (DatabaseIAMPolicy)
  • Single-member grants (DatabaseIAMMember)
  • Multiple conditions per role
  • Custom role definitions

These omissions are intentional: the goal is to illustrate how DatabaseIAMBinding wires access control, not provide drop-in security modules. See the Spanner DatabaseIAMBinding resource reference for all available configuration options.

Let's configure GCP Spanner Database IAM Permissions

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 & Compatibility
Which IAM resource should I use for my Spanner database?
Choose based on your needs: gcp.spanner.DatabaseIAMPolicy replaces the entire IAM policy (authoritative), gcp.spanner.DatabaseIAMBinding manages all members for a specific role (authoritative per role), and gcp.spanner.DatabaseIAMMember adds individual members to a role (non-authoritative). Use DatabaseIAMBinding or DatabaseIAMMember for most cases to avoid accidentally removing existing permissions.
Can I use DatabaseIAMPolicy with DatabaseIAMBinding or DatabaseIAMMember?
No, gcp.spanner.DatabaseIAMPolicy cannot be used with gcp.spanner.DatabaseIAMBinding or gcp.spanner.DatabaseIAMMember because they will conflict over policy management. Choose one approach for your database.
Can I use DatabaseIAMBinding and DatabaseIAMMember together?
Yes, but only if they don’t grant privilege to the same role. Each role must be managed by only one resource type to avoid conflicts.
Configuration & Usage
What member formats are supported for granting database access?
You can use: allUsers (anyone on the internet), allAuthenticatedUsers (anyone with a Google account), user:{email} (specific Google account), serviceAccount:{email} (service account), group:{email} (Google group), or domain:{domain} (G Suite domain).
How do I specify a custom role?
Custom roles must use the format [projects|organizations]/{parent-name}/roles/{role-name}, for example projects/my-project/roles/my-custom-role.
How do I add conditional access using IAM Conditions?
Add a condition block with title, description, and expression fields. For example, to grant access to a specific database role: expression: "(resource.type == \"spanner.googleapis.com/DatabaseRole\" && (resource.name.endsWith(\"/myrole\")))".
Can I use multiple DatabaseIAMBinding resources for the same role?
No, only one gcp.spanner.DatabaseIAMBinding can be used per role. If you need to manage members individually, use gcp.spanner.DatabaseIAMMember instead.
Common Pitfalls
Can I lock myself out of my database using DatabaseIAMPolicy?
Yes, gcp.spanner.DatabaseIAMPolicy replaces the entire IAM policy, removing any default permissions not explicitly included in your configuration. Always include all necessary permissions, or use gcp.spanner.DatabaseIAMBinding or gcp.spanner.DatabaseIAMMember instead.
What properties can't be changed after creating a DatabaseIAMBinding?
The database, instance, project, role, and condition properties are immutable. To change these, you must delete and recreate the resource.

Using a different cloud?

Explore database guides for other cloud providers: