Configure GCP Project IAM Policies

The gcp:projects/iAMPolicy:IAMPolicy resource, part of the Pulumi GCP provider, manages IAM access control for GCP projects through four related resources with different authoritativeness levels. This guide focuses on four capabilities: authoritative policy replacement (IAMPolicy), role-level member management (IAMBinding), individual member grants (IAMMember), and conditional access with audit logging.

These resources reference existing GCP projects and grant access to existing user accounts or service accounts. The examples are intentionally small. Combine them with your own project IDs and identity management.

Replace entire project IAM policy authoritatively

Some teams manage project access entirely through infrastructure-as-code, replacing the complete IAM policy rather than adding individual bindings incrementally.

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 project = new gcp.projects.IAMPolicy("project", {
    project: "your-project-id",
    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"],
}])
project = gcp.projects.IAMPolicy("project",
    project="your-project-id",
    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/projects"
	"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 = projects.NewIAMPolicy(ctx, "project", &projects.IAMPolicyArgs{
			Project:    pulumi.String("your-project-id"),
			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 project = new Gcp.Projects.IAMPolicy("project", new()
    {
        Project = "your-project-id",
        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.projects.IAMPolicy;
import com.pulumi.gcp.projects.IAMPolicyArgs;
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 project = new IAMPolicy("project", IAMPolicyArgs.builder()
            .project("your-project-id")
            .policyData(admin.policyData())
            .build());

    }
}
resources:
  project:
    type: gcp:projects:IAMPolicy
    properties:
      project: your-project-id
      policyData: ${admin.policyData}
variables:
  admin:
    fn::invoke:
      function: gcp:organizations:getIAMPolicy
      arguments:
        bindings:
          - role: roles/editor
            members:
              - user:jane@example.com

The IAMPolicy resource sets the complete IAM policy for a project using policyData from the getIAMPolicy data source. The bindings array defines roles and their members. This resource is authoritative: it replaces any existing policy, potentially locking out users without organization-level access. The documentation recommends importing the existing policy before applying changes to avoid accidental lockouts.

Grant a role to multiple members with IAMBinding

When multiple users or service accounts need the same role, IAMBinding manages the complete member list for that role while preserving other roles in the project.

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 is authoritative for a single role. The members array lists all identities that should have this role. Other roles in the project remain unchanged. This resource cannot be used with IAMPolicy (they conflict), but can coexist with IAMMember resources as long as they target different roles.

Add a single member to a role with IAMMember

Applications often need to grant access to individual service accounts or users without affecting other members who already have the same role.

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 is non-authoritative: it adds one member to a role without affecting other members. The member property specifies a single identity (user, serviceAccount, or group). Multiple IAMMember resources can target the same role, and they preserve existing members granted through other means.

Apply time-based access with IAM Conditions

Temporary access grants expire automatically when you attach conditions to role bindings, eliminating manual cleanup for contractors or time-limited projects.

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 work with IAMBinding and IAMMember resources. The condition block requires a title and expression. The expression uses CEL (Common Expression Language) to evaluate request context; here, request.time checks against a timestamp to enforce expiration. Conditions cannot be used with Basic Roles like Owner, only with predefined and custom roles.

Enable audit logging for project services

Compliance requirements often mandate logging administrative actions and data access across all services or specific APIs.

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 a service. The service property can target specific APIs or use “allServices” for project-wide coverage. The auditLogConfigs array defines log types (ADMIN_READ, DATA_READ, DATA_WRITE) and optional exemptedMembers who won’t generate logs for that type.

Beyond these examples

These snippets focus on specific IAM management features: authoritative vs non-authoritative access control, time-based conditional access, and audit logging configuration. They’re intentionally minimal rather than full access management solutions.

The examples reference pre-existing infrastructure such as GCP projects with existing IAM policies, and user accounts and service accounts to grant access to. They focus on configuring IAM resources rather than provisioning the underlying projects or identities.

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

  • Custom role definitions and management
  • Organization and folder-level IAM (project-level only)
  • IAM policy import procedures
  • Conflict resolution between IAMPolicy, IAMBinding, and IAMMember

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 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
Which IAM resource should I use for my project?
Choose based on your management approach: gcp.projects.IAMPolicy is authoritative and replaces the entire policy, gcp.projects.IAMBinding is authoritative for a specific role while preserving other roles, gcp.projects.IAMMember is non-authoritative and preserves other members for a role, and gcp.projects.IAMAuditConfig is authoritative for audit logging configuration.
Can I use IAMPolicy with other IAM resources?
No, gcp.projects.IAMPolicy cannot be used with gcp.projects.IAMBinding, gcp.projects.IAMMember, or gcp.projects.IAMAuditConfig as they will conflict over the policy. Choose one approach: use IAMPolicy alone for full control, or use the granular resources (IAMBinding, IAMMember, IAMAuditConfig) together.
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 to avoid conflicts.
Security & Access Risks
Can I accidentally lock myself out using IAMPolicy?
Yes, deleting gcp.projects.IAMPolicy removes access from anyone without organization-level access to the project. Don’t use it with your provider project, only use it with projects fully managed by Pulumi, and import the existing policy before applying changes.
IAM Conditions & Constraints
Can I use IAM Conditions with Basic Roles like Owner?
No, IAM Conditions cannot be used with Basic Roles such as Owner. Use predefined or custom roles instead, or you’ll receive a 400 error from the API.
What's the difference between members and member properties?
gcp.projects.IAMBinding uses members (an array of principals) because it manages all members for a role, while gcp.projects.IAMMember uses member (a single principal) because it manages one member at a time.
Import & Migration
Should I import my existing policy before applying IAMPolicy?
Yes, importing the existing policy before applying changes is recommended to avoid accidentally removing access or causing disruptions.
How do I import a custom role?
Use the full name format: [projects/my-project|organizations/my-org]/roles/my-custom-role when importing IAM resources with custom roles.
How do I import a conditional IAM binding?
Include the condition title in your import command: pulumi import google_project_iam_binding.my_project "{{your-project-id}} roles/{{role_id}} condition-title".

Using a different cloud?

Explore security guides for other cloud providers: