Manage GCP Folder IAM Policies

The gcp:folder/iAMPolicy:IAMPolicy resource, part of the Pulumi GCP provider, manages IAM policies for GCP folders, controlling who can access folder resources and what actions they can perform. This guide focuses on four capabilities: authoritative policy replacement, non-authoritative member grants, time-limited access with IAM Conditions, and audit logging.

IAM resources reference existing folders by ID. IAMPolicy is authoritative and replaces the entire policy; IAMBinding and IAMMember are safer for incremental changes. The examples are intentionally small. Combine them with your own folder hierarchy and organizational policies.

Replace all folder permissions with a complete policy

Organizations managing folder access entirely through code define the complete IAM policy in one place, replacing any manually-added permissions.

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 folder = new gcp.folder.IAMPolicy("folder", {
    folder: "folders/1234567",
    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"],
}])
folder = gcp.folder.IAMPolicy("folder",
    folder="folders/1234567",
    policy_data=admin.policy_data)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/folder"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"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 = folder.NewIAMPolicy(ctx, "folder", &folder.IAMPolicyArgs{
			Folder:     pulumi.String("folders/1234567"),
			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 folder = new Gcp.Folder.IAMPolicy("folder", new()
    {
        Folder = "folders/1234567",
        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.folder.IAMPolicy;
import com.pulumi.gcp.folder.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 folder = new IAMPolicy("folder", IAMPolicyArgs.builder()
            .folder("folders/1234567")
            .policyData(admin.policyData())
            .build());

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

The getIAMPolicy data source constructs a policy document from bindings. The IAMPolicy resource applies this policy to the folder, replacing any existing permissions. This is authoritative: deleting the resource removes ALL access, potentially locking you out. Import existing policies before applying changes, and avoid using this on your provider folder.

Set time-limited permissions with IAM Conditions

Temporary access grants, such as contractor permissions or time-boxed experiments, need expiration dates to revoke access automatically.

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

const admin = gcp.organizations.getIAMPolicy({
    bindings: [{
        role: "roles/compute.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\")",
        },
    }],
});
const folder = new gcp.folder.IAMPolicy("folder", {
    folder: "folders/1234567",
    policyData: admin.then(admin => admin.policyData),
});
import pulumi
import pulumi_gcp as gcp

admin = gcp.organizations.get_iam_policy(bindings=[{
    "role": "roles/compute.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\")",
    },
}])
folder = gcp.folder.IAMPolicy("folder",
    folder="folders/1234567",
    policy_data=admin.policy_data)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/folder"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"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/compute.admin",
					Members: []string{
						"user:jane@example.com",
					},
					Condition: {
						Title:       "expires_after_2019_12_31",
						Description: pulumi.StringRef("Expiring at midnight of 2019-12-31"),
						Expression:  "request.time < timestamp(\"2020-01-01T00:00:00Z\")",
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		_, err = folder.NewIAMPolicy(ctx, "folder", &folder.IAMPolicyArgs{
			Folder:     pulumi.String("folders/1234567"),
			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/compute.admin",
                Members = new[]
                {
                    "user:jane@example.com",
                },
                Condition = new Gcp.Organizations.Inputs.GetIAMPolicyBindingConditionInputArgs
                {
                    Title = "expires_after_2019_12_31",
                    Description = "Expiring at midnight of 2019-12-31",
                    Expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")",
                },
            },
        },
    });

    var folder = new Gcp.Folder.IAMPolicy("folder", new()
    {
        Folder = "folders/1234567",
        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.folder.IAMPolicy;
import com.pulumi.gcp.folder.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/compute.admin")
                .members("user:jane@example.com")
                .condition(GetIAMPolicyBindingConditionArgs.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())
            .build());

        var folder = new IAMPolicy("folder", IAMPolicyArgs.builder()
            .folder("folders/1234567")
            .policyData(admin.policyData())
            .build());

    }
}
resources:
  folder:
    type: gcp:folder:IAMPolicy
    properties:
      folder: folders/1234567
      policyData: ${admin.policyData}
variables:
  admin:
    fn::invoke:
      function: gcp:organizations:getIAMPolicy
      arguments:
        bindings:
          - role: roles/compute.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")

The condition block adds time-based constraints to role bindings. The expression uses CEL (Common Expression Language) to compare request.time against a timestamp. When the condition evaluates to false, access is denied. IAM Conditions cannot be used with Basic Roles like Owner; use predefined or custom roles instead.

Grant a single member access without affecting others

When adding one person or service account, you often want to preserve existing permissions rather than managing the complete member list.

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

const folder = new gcp.folder.IAMMember("folder", {
    folder: "folders/1234567",
    role: "roles/editor",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

folder = gcp.folder.IAMMember("folder",
    folder="folders/1234567",
    role="roles/editor",
    member="user:jane@example.com")
package main

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

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

    }
}
resources:
  folder:
    type: gcp:folder:IAMMember
    properties:
      folder: folders/1234567
      role: roles/editor
      member: user:jane@example.com

IAMMember is non-authoritative: it adds one member to a role without affecting other members. The member property uses the format “user:email”, “serviceAccount:email”, or “group:email”. You can safely use multiple IAMMember resources for the same folder as long as they grant different roles.

Enable audit logging for folder operations

Compliance requirements often mandate logging of administrative actions and data access for security monitoring.

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

const folder = new gcp.folder.IamAuditConfig("folder", {
    folder: "folders/1234567",
    service: "allServices",
    auditLogConfigs: [
        {
            logType: "ADMIN_READ",
        },
        {
            logType: "DATA_READ",
            exemptedMembers: ["user:joebloggs@example.com"],
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

folder = gcp.folder.IamAuditConfig("folder",
    folder="folders/1234567",
    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/folder"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := folder.NewIamAuditConfig(ctx, "folder", &folder.IamAuditConfigArgs{
			Folder:  pulumi.String("folders/1234567"),
			Service: pulumi.String("allServices"),
			AuditLogConfigs: folder.IamAuditConfigAuditLogConfigArray{
				&folder.IamAuditConfigAuditLogConfigArgs{
					LogType: pulumi.String("ADMIN_READ"),
				},
				&folder.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 folder = new Gcp.Folder.IamAuditConfig("folder", new()
    {
        Folder = "folders/1234567",
        Service = "allServices",
        AuditLogConfigs = new[]
        {
            new Gcp.Folder.Inputs.IamAuditConfigAuditLogConfigArgs
            {
                LogType = "ADMIN_READ",
            },
            new Gcp.Folder.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.folder.IamAuditConfig;
import com.pulumi.gcp.folder.IamAuditConfigArgs;
import com.pulumi.gcp.folder.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 folder = new IamAuditConfig("folder", IamAuditConfigArgs.builder()
            .folder("folders/1234567")
            .service("allServices")
            .auditLogConfigs(            
                IamAuditConfigAuditLogConfigArgs.builder()
                    .logType("ADMIN_READ")
                    .build(),
                IamAuditConfigAuditLogConfigArgs.builder()
                    .logType("DATA_READ")
                    .exemptedMembers("user:joebloggs@example.com")
                    .build())
            .build());

    }
}
resources:
  folder:
    type: gcp:folder:IamAuditConfig
    properties:
      folder: folders/1234567
      service: allServices
      auditLogConfigs:
        - logType: ADMIN_READ
        - logType: DATA_READ
          exemptedMembers:
            - user:joebloggs@example.com

IamAuditConfig enables Cloud Audit Logs for a service. The service property accepts “allServices” or specific service names like “storage.googleapis.com”. Each auditLogConfig specifies a logType (ADMIN_READ, DATA_READ, DATA_WRITE) and optional exemptedMembers who bypass logging. Logs flow to Cloud Logging; configure log sinks separately for long-term storage.

Beyond these examples

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

The examples reference pre-existing infrastructure such as GCP folders (folders/{folder_id}) and assume parent folder or organization permissions for recovery. They focus on configuring IAM policies rather than provisioning the folder hierarchy.

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

  • Custom role definitions (organizations/{{org_id}}/roles/{{role_id}})
  • Service account and group member types
  • Multiple condition expressions (AND/OR logic)
  • Audit log exemptions for specific operations

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

Let's manage GCP Folder 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 & Compatibility
Which folder IAM resource should I use?

Choose based on your management needs:

  • gcp.folder.IAMPolicy: Authoritative, replaces the entire IAM policy
  • gcp.folder.IAMBinding: Authoritative for a specific role, preserves other roles
  • gcp.folder.IAMMember: Non-authoritative, adds a single member to a role while preserving other members
  • gcp.folder.IamAuditConfig: Authoritative for audit logging configuration
Can I use multiple folder IAM resources together?
Partially. gcp.folder.IAMPolicy cannot be used with any other folder IAM resource (they will conflict). However, gcp.folder.IAMBinding and gcp.folder.IAMMember can be used together only if they don’t manage the same role.
Common Pitfalls & Errors
How do I avoid locking myself out when using IAMPolicy?
Deleting gcp.folder.IAMPolicy removes all access for anyone without parent folder/organization permissions. To avoid lockout: import the existing policy before applying changes, avoid using IAMPolicy with your provider folder, and only use it with folders fully managed by Pulumi.
Why am I getting a 400 error when using IAM Conditions?
IAM Conditions cannot be used with Basic Roles such as Owner. Use predefined or custom roles instead when applying conditions.
IAM Conditions
How do I add time-based or conditional access to folder IAM?
Add a 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\")".
Configuration & Import
What format does the folder property require?
Use the format folders/{folder_id}, for example: folders/1234567. This property is immutable after creation.
What's the difference between member and members properties?
gcp.folder.IAMMember uses singular member for a single identity, while gcp.folder.IAMBinding uses plural members for a list of identities.
How do I import a folder IAM resource with custom roles?
Use the full role name format: organizations/{{org_id}}/roles/{{role_id}} instead of just the role ID.
How do I import a conditional IAM binding?
Include the condition title in your import command: pulumi import gcp:folder/iAMBinding:IAMBinding my_folder "folder roles/{{role_id}} condition-title".

Using a different cloud?

Explore security guides for other cloud providers: