Manage GCP Identity-Aware Proxy IAM Bindings

The gcp:iap/tunnelDestGroupIamBinding:TunnelDestGroupIamBinding resource, part of the Pulumi GCP provider, manages IAM role bindings for Identity-Aware Proxy tunnel destination groups, controlling which identities can access tunneled resources. This guide focuses on three capabilities: authoritative role grants with IamBinding, time-based access with IAM Conditions, and non-authoritative member grants with IamMember.

IAM resources reference existing tunnel destination groups and require valid project and region identifiers. The examples are intentionally small. Combine them with your own tunnel groups and identity management strategy.

Grant a role to multiple members with IamBinding

Teams managing IAP tunnel access often need to grant the same role to multiple users or service accounts at once.

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

const binding = new gcp.iap.TunnelDestGroupIamBinding("binding", {
    project: destGroup.project,
    region: destGroup.region,
    destGroup: destGroup.groupName,
    role: "roles/iap.tunnelResourceAccessor",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.iap.TunnelDestGroupIamBinding("binding",
    project=dest_group["project"],
    region=dest_group["region"],
    dest_group=dest_group["groupName"],
    role="roles/iap.tunnelResourceAccessor",
    members=["user:jane@example.com"])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := iap.NewTunnelDestGroupIamBinding(ctx, "binding", &iap.TunnelDestGroupIamBindingArgs{
			Project:   pulumi.Any(destGroup.Project),
			Region:    pulumi.Any(destGroup.Region),
			DestGroup: pulumi.Any(destGroup.GroupName),
			Role:      pulumi.String("roles/iap.tunnelResourceAccessor"),
			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.Iap.TunnelDestGroupIamBinding("binding", new()
    {
        Project = destGroup.Project,
        Region = destGroup.Region,
        DestGroup = destGroup.GroupName,
        Role = "roles/iap.tunnelResourceAccessor",
        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.iap.TunnelDestGroupIamBinding;
import com.pulumi.gcp.iap.TunnelDestGroupIamBindingArgs;
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 TunnelDestGroupIamBinding("binding", TunnelDestGroupIamBindingArgs.builder()
            .project(destGroup.project())
            .region(destGroup.region())
            .destGroup(destGroup.groupName())
            .role("roles/iap.tunnelResourceAccessor")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  binding:
    type: gcp:iap:TunnelDestGroupIamBinding
    properties:
      project: ${destGroup.project}
      region: ${destGroup.region}
      destGroup: ${destGroup.groupName}
      role: roles/iap.tunnelResourceAccessor
      members:
        - user:jane@example.com

The IamBinding resource is authoritative for the specified role. The members array lists all identities that should have this role; any members not in the list lose access. The role property specifies which IAP permission to grant (here, roles/iap.tunnelResourceAccessor allows tunnel connections). The destGroup, project, and region properties identify which tunnel destination group to configure.

Add time-based access with IAM Conditions

Access policies sometimes need expiration dates or time windows without managing separate cleanup processes.

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

const binding = new gcp.iap.TunnelDestGroupIamBinding("binding", {
    project: destGroup.project,
    region: destGroup.region,
    destGroup: destGroup.groupName,
    role: "roles/iap.tunnelResourceAccessor",
    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.iap.TunnelDestGroupIamBinding("binding",
    project=dest_group["project"],
    region=dest_group["region"],
    dest_group=dest_group["groupName"],
    role="roles/iap.tunnelResourceAccessor",
    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/iap"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := iap.NewTunnelDestGroupIamBinding(ctx, "binding", &iap.TunnelDestGroupIamBindingArgs{
			Project:   pulumi.Any(destGroup.Project),
			Region:    pulumi.Any(destGroup.Region),
			DestGroup: pulumi.Any(destGroup.GroupName),
			Role:      pulumi.String("roles/iap.tunnelResourceAccessor"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
			Condition: &iap.TunnelDestGroupIamBindingConditionArgs{
				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.Iap.TunnelDestGroupIamBinding("binding", new()
    {
        Project = destGroup.Project,
        Region = destGroup.Region,
        DestGroup = destGroup.GroupName,
        Role = "roles/iap.tunnelResourceAccessor",
        Members = new[]
        {
            "user:jane@example.com",
        },
        Condition = new Gcp.Iap.Inputs.TunnelDestGroupIamBindingConditionArgs
        {
            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.iap.TunnelDestGroupIamBinding;
import com.pulumi.gcp.iap.TunnelDestGroupIamBindingArgs;
import com.pulumi.gcp.iap.inputs.TunnelDestGroupIamBindingConditionArgs;
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 TunnelDestGroupIamBinding("binding", TunnelDestGroupIamBindingArgs.builder()
            .project(destGroup.project())
            .region(destGroup.region())
            .destGroup(destGroup.groupName())
            .role("roles/iap.tunnelResourceAccessor")
            .members("user:jane@example.com")
            .condition(TunnelDestGroupIamBindingConditionArgs.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:iap:TunnelDestGroupIamBinding
    properties:
      project: ${destGroup.project}
      region: ${destGroup.region}
      destGroup: ${destGroup.groupName}
      role: roles/iap.tunnelResourceAccessor
      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 temporal constraints to role grants. The condition block requires a title, description, and expression. The expression uses Common Expression Language (CEL) to define when access is valid; here, request.time < timestamp("2020-01-01T00:00:00Z") grants access until midnight on December 31, 2019. After that timestamp, the binding becomes inactive automatically.

Add individual members with IamMember

When multiple teams manage access independently, IamMember allows non-authoritative grants without coordinating changes.

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

const member = new gcp.iap.TunnelDestGroupIamMember("member", {
    project: destGroup.project,
    region: destGroup.region,
    destGroup: destGroup.groupName,
    role: "roles/iap.tunnelResourceAccessor",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

member = gcp.iap.TunnelDestGroupIamMember("member",
    project=dest_group["project"],
    region=dest_group["region"],
    dest_group=dest_group["groupName"],
    role="roles/iap.tunnelResourceAccessor",
    member="user:jane@example.com")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := iap.NewTunnelDestGroupIamMember(ctx, "member", &iap.TunnelDestGroupIamMemberArgs{
			Project:   pulumi.Any(destGroup.Project),
			Region:    pulumi.Any(destGroup.Region),
			DestGroup: pulumi.Any(destGroup.GroupName),
			Role:      pulumi.String("roles/iap.tunnelResourceAccessor"),
			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.Iap.TunnelDestGroupIamMember("member", new()
    {
        Project = destGroup.Project,
        Region = destGroup.Region,
        DestGroup = destGroup.GroupName,
        Role = "roles/iap.tunnelResourceAccessor",
        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.iap.TunnelDestGroupIamMember;
import com.pulumi.gcp.iap.TunnelDestGroupIamMemberArgs;
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 TunnelDestGroupIamMember("member", TunnelDestGroupIamMemberArgs.builder()
            .project(destGroup.project())
            .region(destGroup.region())
            .destGroup(destGroup.groupName())
            .role("roles/iap.tunnelResourceAccessor")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:iap:TunnelDestGroupIamMember
    properties:
      project: ${destGroup.project}
      region: ${destGroup.region}
      destGroup: ${destGroup.groupName}
      role: roles/iap.tunnelResourceAccessor
      member: user:jane@example.com

The IamMember resource adds one member to a role without affecting other members or roles. Unlike IamBinding, multiple IamMember resources can target the same role, each adding a different identity. The member property uses the same identity format as the members array in IamBinding, but grants access to only one identity per resource.

Beyond these examples

These snippets focus on specific IAM binding features: role-based access control (IamBinding vs IamMember) and IAM Conditions for temporal constraints. They’re intentionally minimal rather than complete access management solutions.

The examples reference pre-existing infrastructure such as IAP tunnel destination groups and GCP projects and regions. They focus on configuring IAM bindings rather than provisioning the underlying tunnel infrastructure.

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

  • Full policy replacement (IamPolicy resource)
  • Custom role definitions
  • Federated identity configuration
  • Audit logging and access monitoring

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

Let's manage GCP Identity-Aware Proxy IAM Bindings

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 resources can I use together?
gcp.iap.TunnelDestGroupIamPolicy cannot be used with gcp.iap.TunnelDestGroupIamBinding or gcp.iap.TunnelDestGroupIamMember, as they’ll conflict over policy management. However, gcp.iap.TunnelDestGroupIamBinding and gcp.iap.TunnelDestGroupIamMember can be used together if they don’t grant privileges to the same role.
What's the difference between Policy, Binding, and Member resources?
gcp.iap.TunnelDestGroupIamPolicy is fully authoritative and replaces the entire IAM policy. gcp.iap.TunnelDestGroupIamBinding is authoritative for a specific role but preserves other roles. gcp.iap.TunnelDestGroupIamMember is non-authoritative and preserves other members for the same role.
IAM Configuration
What member identity formats are supported?
You can use allUsers, allAuthenticatedUsers, user:{emailid}, serviceAccount:{emailid}, group:{emailid}, domain:{domain}, projectOwner:projectid, projectEditor:projectid, projectViewer:projectid, and federated identities like principal://iam.googleapis.com/locations/global/workforcePools/....
How do I specify custom roles?
Custom roles must use the format [projects|organizations]/{parent-name}/roles/{role-name}, for example projects/my-project/roles/my-custom-role.
Can I use time-based access conditions?
Yes, use the condition property with title, description, and expression. For example, set expression to request.time < timestamp("2020-01-01T00:00:00Z") to expire access at a specific time.
Immutability & Limitations
What properties can't I change after creation?
The destGroup, project, region, role, and condition properties are all immutable and require resource replacement if changed.
Are there limitations with IAM Conditions?
Yes, IAM Conditions are supported but have known limitations. Review the GCP documentation on IAM Conditions limitations if you encounter issues.

Using a different cloud?

Explore security guides for other cloud providers: