Configure GCP Cloud Storage IAM Permissions

The gcp:storage/bucketIAMBinding:BucketIAMBinding resource, part of the Pulumi GCP provider, manages IAM role bindings for Cloud Storage buckets by controlling which members have a specific role. This guide focuses on three capabilities: authoritative role binding that replaces all members for a role, incremental member addition that preserves other members, and conditional access with time-based expiration.

IAM bindings reference existing buckets and grant access to IAM principals (users, service accounts, groups) that must exist separately. The examples are intentionally small. Combine them with your own bucket and identity management.

Grant a role to multiple members at once

Teams managing bucket access often need to grant the same role to multiple users, service accounts, or groups.

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

const binding = new gcp.storage.BucketIAMBinding("binding", {
    bucket: _default.name,
    role: "roles/storage.admin",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.storage.BucketIAMBinding("binding",
    bucket=default["name"],
    role="roles/storage.admin",
    members=["user:jane@example.com"])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storage.NewBucketIAMBinding(ctx, "binding", &storage.BucketIAMBindingArgs{
			Bucket: pulumi.Any(_default.Name),
			Role:   pulumi.String("roles/storage.admin"),
			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.Storage.BucketIAMBinding("binding", new()
    {
        Bucket = @default.Name,
        Role = "roles/storage.admin",
        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.storage.BucketIAMBinding;
import com.pulumi.gcp.storage.BucketIAMBindingArgs;
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 BucketIAMBinding("binding", BucketIAMBindingArgs.builder()
            .bucket(default_.name())
            .role("roles/storage.admin")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  binding:
    type: gcp:storage:BucketIAMBinding
    properties:
      bucket: ${default.name}
      role: roles/storage.admin
      members:
        - user:jane@example.com

BucketIAMBinding is authoritative for the specified role: it replaces any existing members for that role with the list you provide. The bucket property references your bucket by name, role specifies the IAM role to grant, and members lists all identities that should have that role. If another binding or member resource grants the same role, they will conflict.

Add time-based access with IAM Conditions

Access grants sometimes need expiration dates or other constraints beyond simple role assignment.

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

const binding = new gcp.storage.BucketIAMBinding("binding", {
    bucket: _default.name,
    role: "roles/storage.admin",
    members: ["user:jane@example.com"],
    condition: {
        title: "expires_after_2019_12_31",
        description: "Expiring at midnight of 2019-12-31",
        expression: "request.time < timestamp(\"2020-01-01T00:00:00Z\")",
    },
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.storage.BucketIAMBinding("binding",
    bucket=default["name"],
    role="roles/storage.admin",
    members=["user:jane@example.com"],
    condition={
        "title": "expires_after_2019_12_31",
        "description": "Expiring at midnight of 2019-12-31",
        "expression": "request.time < timestamp(\"2020-01-01T00:00:00Z\")",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storage.NewBucketIAMBinding(ctx, "binding", &storage.BucketIAMBindingArgs{
			Bucket: pulumi.Any(_default.Name),
			Role:   pulumi.String("roles/storage.admin"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
			Condition: &storage.BucketIAMBindingConditionArgs{
				Title:       pulumi.String("expires_after_2019_12_31"),
				Description: pulumi.String("Expiring at midnight of 2019-12-31"),
				Expression:  pulumi.String("request.time < timestamp(\"2020-01-01T00:00:00Z\")"),
			},
		})
		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.Storage.BucketIAMBinding("binding", new()
    {
        Bucket = @default.Name,
        Role = "roles/storage.admin",
        Members = new[]
        {
            "user:jane@example.com",
        },
        Condition = new Gcp.Storage.Inputs.BucketIAMBindingConditionArgs
        {
            Title = "expires_after_2019_12_31",
            Description = "Expiring at midnight of 2019-12-31",
            Expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.storage.BucketIAMBinding;
import com.pulumi.gcp.storage.BucketIAMBindingArgs;
import com.pulumi.gcp.storage.inputs.BucketIAMBindingConditionArgs;
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 BucketIAMBinding("binding", BucketIAMBindingArgs.builder()
            .bucket(default_.name())
            .role("roles/storage.admin")
            .members("user:jane@example.com")
            .condition(BucketIAMBindingConditionArgs.builder()
                .title("expires_after_2019_12_31")
                .description("Expiring at midnight of 2019-12-31")
                .expression("request.time < timestamp(\"2020-01-01T00:00:00Z\")")
                .build())
            .build());

    }
}
resources:
  binding:
    type: gcp:storage:BucketIAMBinding
    properties:
      bucket: ${default.name}
      role: roles/storage.admin
      members:
        - user:jane@example.com
      condition:
        title: expires_after_2019_12_31
        description: Expiring at midnight of 2019-12-31
        expression: request.time < timestamp("2020-01-01T00:00:00Z")

IAM Conditions attach restrictions to role bindings using CEL expressions. The condition block requires a title for identification, an expression that evaluates to true or false (here checking request time against a timestamp), and an optional description. When the expression evaluates to false, the binding no longer grants access. This extends the basic binding pattern with temporal or attribute-based controls.

Add a single member to a role incrementally

When multiple teams manage bucket access, you need to add members without coordinating with others or overwriting their grants.

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

const member = new gcp.storage.BucketIAMMember("member", {
    bucket: _default.name,
    role: "roles/storage.admin",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

member = gcp.storage.BucketIAMMember("member",
    bucket=default["name"],
    role="roles/storage.admin",
    member="user:jane@example.com")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storage.NewBucketIAMMember(ctx, "member", &storage.BucketIAMMemberArgs{
			Bucket: pulumi.Any(_default.Name),
			Role:   pulumi.String("roles/storage.admin"),
			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.Storage.BucketIAMMember("member", new()
    {
        Bucket = @default.Name,
        Role = "roles/storage.admin",
        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.storage.BucketIAMMember;
import com.pulumi.gcp.storage.BucketIAMMemberArgs;
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 BucketIAMMember("member", BucketIAMMemberArgs.builder()
            .bucket(default_.name())
            .role("roles/storage.admin")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:storage:BucketIAMMember
    properties:
      bucket: ${default.name}
      role: roles/storage.admin
      member: user:jane@example.com

BucketIAMMember is non-authoritative: it adds one member to a role without affecting other members for that role. Use member (singular) instead of members (plural) to specify a single identity. This resource type lets different teams manage their own access grants independently, though you must still avoid granting the same member-role pair from multiple resources.

Beyond these examples

These snippets focus on specific IAM binding features: role binding management (authoritative per role), incremental member grants (non-authoritative), and time-based and conditional access. They’re intentionally minimal rather than full access control policies.

The examples reference pre-existing infrastructure such as Cloud Storage buckets and IAM principals (users, service accounts, groups). They focus on configuring role bindings rather than provisioning buckets or identities.

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

  • Full policy replacement (BucketIAMPolicy resource)
  • Custom role definitions and formatting
  • Conflict resolution between IAM resource types
  • IAM Conditions limitations and edge cases

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

Let's configure GCP Cloud Storage 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 & Conflicts
What's the difference between BucketIAMPolicy, BucketIAMBinding, and BucketIAMMember?
gcp.storage.BucketIAMPolicy is authoritative and replaces the entire IAM policy. gcp.storage.BucketIAMBinding is authoritative for a specific role, preserving other roles. gcp.storage.BucketIAMMember is non-authoritative, adding a single member while preserving other members for the role.
Can I use BucketIAMPolicy together with BucketIAMBinding or BucketIAMMember?
No, gcp.storage.BucketIAMPolicy cannot be used with gcp.storage.BucketIAMBinding or gcp.storage.BucketIAMMember because they will conflict over the policy.
Can I use BucketIAMBinding and BucketIAMMember together?
Yes, but only if they don’t grant privileges to the same role. Each role must be managed by only one resource type.
Configuration & Member Formats
What member identity formats are supported?
Supported formats include allUsers, allAuthenticatedUsers, user:{emailid}, serviceAccount:{emailid}, group:{emailid}, domain:{domain}, projectOwner:projectid, projectEditor:projectid, and projectViewer:projectid.
How do I specify a custom IAM role?
Custom roles must use the format [projects|organizations]/{parent-name}/roles/{role-name}.
IAM Conditions & Limitations
Can I use IAM Conditions with bucket IAM resources?
Yes, IAM Conditions are supported through the condition property with title, description, and expression fields. However, IAM Conditions have known limitations that you should review.
Immutability & Updates
What properties can't I change after creating a BucketIAMBinding?
The bucket, role, and condition properties are immutable and cannot be changed after creation.

Using a different cloud?

Explore storage guides for other cloud providers: