Configure GCP Project IAM Policies

The gcp:projects/iAMPolicy:IAMPolicy resource family, part of the Pulumi GCP provider, manages IAM access control for GCP projects through four distinct resources. IAMPolicy replaces the entire policy (authoritative), IAMBinding controls all members for a specific role (role-authoritative), IAMMember adds individual members without affecting others (non-authoritative), and IAMAuditConfig enables audit logging. This guide focuses on four capabilities: role-based access with IAMBinding, incremental grants with IAMMember, time-based conditional access, and audit logging.

These resources require an existing GCP project and reference user or service account identities that must already exist. The examples are intentionally small. Combine them with your own project structure and identity management.

Grant a role to multiple members with IAMBinding

When you need to manage all members for a specific role as a single unit, IAMBinding provides authoritative control over that role’s membership list.

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

const project = new gcp.projects.IAMBinding("project", {
    project: "your-project-id",
    role: "roles/editor",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

project = gcp.projects.IAMBinding("project",
    project="your-project-id",
    role="roles/editor",
    members=["user:jane@example.com"])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := projects.NewIAMBinding(ctx, "project", &projects.IAMBindingArgs{
			Project: pulumi.String("your-project-id"),
			Role:    pulumi.String("roles/editor"),
			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 project = new Gcp.Projects.IAMBinding("project", new()
    {
        Project = "your-project-id",
        Role = "roles/editor",
        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.projects.IAMBinding;
import com.pulumi.gcp.projects.IAMBindingArgs;
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 project = new IAMBinding("project", IAMBindingArgs.builder()
            .project("your-project-id")
            .role("roles/editor")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  project:
    type: gcp:projects:IAMBinding
    properties:
      project: your-project-id
      role: roles/editor
      members:
        - user:jane@example.com

The IAMBinding resource takes complete ownership of the specified role. The members array lists all identities that should have this role; anyone not in the list loses access. This is useful when you want to define the complete membership for a role in one place, but it cannot be mixed with IAMMember resources for the same role.

Add time-based conditions to role bindings

Temporary access grants often require expiration dates to limit privilege duration without manual cleanup.

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

const project = new gcp.projects.IAMBinding("project", {
    project: "your-project-id",
    role: "roles/container.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

project = gcp.projects.IAMBinding("project",
    project="your-project-id",
    role="roles/container.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/projects"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := projects.NewIAMBinding(ctx, "project", &projects.IAMBindingArgs{
			Project: pulumi.String("your-project-id"),
			Role:    pulumi.String("roles/container.admin"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
			Condition: &projects.IAMBindingConditionArgs{
				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 project = new Gcp.Projects.IAMBinding("project", new()
    {
        Project = "your-project-id",
        Role = "roles/container.admin",
        Members = new[]
        {
            "user:jane@example.com",
        },
        Condition = new Gcp.Projects.Inputs.IAMBindingConditionArgs
        {
            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.projects.IAMBinding;
import com.pulumi.gcp.projects.IAMBindingArgs;
import com.pulumi.gcp.projects.inputs.IAMBindingConditionArgs;
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 project = new IAMBinding("project", IAMBindingArgs.builder()
            .project("your-project-id")
            .role("roles/container.admin")
            .members("user:jane@example.com")
            .condition(IAMBindingConditionArgs.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:
  project:
    type: gcp:projects:IAMBinding
    properties:
      project: your-project-id
      role: roles/container.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 use Common Expression Language (CEL) to define access constraints. The expression property evaluates at request time; here, request.time < timestamp("2020-01-01T00:00:00Z") automatically revokes access after the specified date. The title and description properties document the condition’s purpose. Note that conditions cannot be used with Basic Roles like Owner.

Add a single member to a role with IAMMember

Applications that grant access incrementally need non-authoritative updates that preserve existing role members.

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

const project = new gcp.projects.IAMMember("project", {
    project: "your-project-id",
    role: "roles/editor",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

project = gcp.projects.IAMMember("project",
    project="your-project-id",
    role="roles/editor",
    member="user:jane@example.com")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := projects.NewIAMMember(ctx, "project", &projects.IAMMemberArgs{
			Project: pulumi.String("your-project-id"),
			Role:    pulumi.String("roles/editor"),
			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 project = new Gcp.Projects.IAMMember("project", new()
    {
        Project = "your-project-id",
        Role = "roles/editor",
        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.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
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 project = new IAMMember("project", IAMMemberArgs.builder()
            .project("your-project-id")
            .role("roles/editor")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  project:
    type: gcp:projects:IAMMember
    properties:
      project: your-project-id
      role: roles/editor
      member: user:jane@example.com

The IAMMember resource adds one identity to a role without affecting other members. Unlike IAMBinding, multiple IAMMember resources can target the same role, making it suitable for incremental grants. The member property uses the format type:identity (e.g., user:, serviceAccount:, group:).

Enable audit logging for project operations

Compliance requirements often mandate logging of administrative actions and data access across all services.

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

const project = new gcp.projects.IAMAuditConfig("project", {
    project: "your-project-id",
    service: "allServices",
    auditLogConfigs: [
        {
            logType: "ADMIN_READ",
        },
        {
            logType: "DATA_READ",
            exemptedMembers: ["user:joebloggs@example.com"],
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

project = gcp.projects.IAMAuditConfig("project",
    project="your-project-id",
    service="allServices",
    audit_log_configs=[
        {
            "log_type": "ADMIN_READ",
        },
        {
            "log_type": "DATA_READ",
            "exempted_members": ["user:joebloggs@example.com"],
        },
    ])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := projects.NewIAMAuditConfig(ctx, "project", &projects.IAMAuditConfigArgs{
			Project: pulumi.String("your-project-id"),
			Service: pulumi.String("allServices"),
			AuditLogConfigs: projects.IAMAuditConfigAuditLogConfigArray{
				&projects.IAMAuditConfigAuditLogConfigArgs{
					LogType: pulumi.String("ADMIN_READ"),
				},
				&projects.IAMAuditConfigAuditLogConfigArgs{
					LogType: pulumi.String("DATA_READ"),
					ExemptedMembers: pulumi.StringArray{
						pulumi.String("user:joebloggs@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 project = new Gcp.Projects.IAMAuditConfig("project", new()
    {
        Project = "your-project-id",
        Service = "allServices",
        AuditLogConfigs = new[]
        {
            new Gcp.Projects.Inputs.IAMAuditConfigAuditLogConfigArgs
            {
                LogType = "ADMIN_READ",
            },
            new Gcp.Projects.Inputs.IAMAuditConfigAuditLogConfigArgs
            {
                LogType = "DATA_READ",
                ExemptedMembers = new[]
                {
                    "user:joebloggs@example.com",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.projects.IAMAuditConfig;
import com.pulumi.gcp.projects.IAMAuditConfigArgs;
import com.pulumi.gcp.projects.inputs.IAMAuditConfigAuditLogConfigArgs;
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 project = new IAMAuditConfig("project", IAMAuditConfigArgs.builder()
            .project("your-project-id")
            .service("allServices")
            .auditLogConfigs(            
                IAMAuditConfigAuditLogConfigArgs.builder()
                    .logType("ADMIN_READ")
                    .build(),
                IAMAuditConfigAuditLogConfigArgs.builder()
                    .logType("DATA_READ")
                    .exemptedMembers("user:joebloggs@example.com")
                    .build())
            .build());

    }
}
resources:
  project:
    type: gcp:projects:IAMAuditConfig
    properties:
      project: your-project-id
      service: allServices
      auditLogConfigs:
        - logType: ADMIN_READ
        - logType: DATA_READ
          exemptedMembers:
            - user:joebloggs@example.com

The IAMAuditConfig resource enables Cloud Audit Logs for the specified service. The service property can target specific APIs or use allServices for project-wide logging. Each auditLogConfig entry specifies a logType (ADMIN_READ, DATA_READ, DATA_WRITE) and optional exemptedMembers who bypass logging. This generates logs in Cloud Logging for security analysis and compliance audits.

Beyond these examples

These snippets focus on specific IAM management features: role binding and member management, conditional access with time constraints, and audit logging configuration. They’re intentionally minimal rather than full access control systems.

The examples require pre-existing infrastructure such as a GCP project with project ID, and user accounts or service accounts to grant access to. They focus on configuring IAM policies rather than provisioning the underlying project or identity infrastructure.

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

  • IAMPolicy resource (authoritative, replaces entire policy)
  • Organization-level IAM (focuses on project scope)
  • 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 Projects IAMPolicy resource reference for all available configuration options.

Let's configure GCP Project 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 IAMPolicy, IAMBinding, IAMMember, and IAMAuditConfig?
gcp.projects.IAMPolicy is authoritative and replaces the entire IAM policy. gcp.projects.IAMBinding is authoritative for a specific role but preserves other roles. gcp.projects.IAMMember is non-authoritative and preserves other members for the same role. gcp.projects.IAMAuditConfig is authoritative for audit logging configuration for a service.
Can I use IAMPolicy with IAMBinding or IAMMember?
No. gcp.projects.IAMPolicy cannot be used with gcp.projects.IAMBinding, gcp.projects.IAMMember, or gcp.projects.IAMAuditConfig because they will conflict over the policy configuration.
Can I use IAMBinding and IAMMember together?
Yes, but only if they don’t grant privileges to the same role. Each role must be managed by only one resource type.
Lockout Prevention & Safety
Can I accidentally lock myself out of my project with IAMPolicy?
Yes. Deleting gcp.projects.IAMPolicy removes access from anyone without organization-level access to the project. It’s not recommended to use gcp.projects.IAMPolicy with your provider project to avoid locking yourself out.
When should I use IAMPolicy?
Use gcp.projects.IAMPolicy only with projects fully managed by Pulumi. Avoid using it with your provider project to prevent lockout.
Should I import the policy before applying IAMPolicy changes?
Yes. It’s recommended to import the existing policy before applying changes to avoid accidentally removing existing access.
IAM Conditions
How do I add time-based or conditional IAM policies?
Use the condition block with title, description, and expression properties. For example, to expire access at a specific time: expression: "request.time < timestamp(\"2020-01-01T00:00:00Z\")".
Why am I getting a 400 error when using IAM conditions?
IAM Conditions cannot be used with Basic Roles such as Owner. Use conditions only with predefined or custom roles. Review the API constraints at https://cloud.google.com/resource-manager/reference/rest/v1/projects/setIamPolicy for additional restrictions.
Configuration
Where does the policyData come from?
Use the gcp.organizations.getIAMPolicy data source to generate the policyData. The policy will be merged with any existing policy on the project.
Is the project parameter inferred from the provider?
No. The project parameter is required and must be explicitly specified. It’s not inferred from the provider configuration.

Using a different cloud?

Explore security guides for other cloud providers: