Configure GCP Identity-Aware Proxy IAM Bindings

The gcp:iap/webTypeAppEngingIamBinding:WebTypeAppEngingIamBinding resource, part of the Pulumi GCP provider, manages IAM role bindings for Identity-Aware Proxy on App Engine applications. It controls which identities can access protected resources. This guide focuses on three capabilities: authoritative role bindings with multiple members, time-based access with IAM Conditions, and non-authoritative single-member grants.

IAM bindings for IAP reference existing App Engine applications and operate within GCP projects. The examples are intentionally small. Combine them with your own App Engine infrastructure and identity management strategy.

Grant a role to multiple members at once

Teams managing IAP access often need to grant the same role to multiple users or service accounts simultaneously. The Binding resource treats the member list as authoritative for that specific role, replacing any existing members.

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

const binding = new gcp.iap.WebTypeAppEngingIamBinding("binding", {
    project: app.project,
    appId: app.appId,
    role: "roles/iap.httpsResourceAccessor",
    members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp

binding = gcp.iap.WebTypeAppEngingIamBinding("binding",
    project=app["project"],
    app_id=app["appId"],
    role="roles/iap.httpsResourceAccessor",
    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.NewWebTypeAppEngingIamBinding(ctx, "binding", &iap.WebTypeAppEngingIamBindingArgs{
			Project: pulumi.Any(app.Project),
			AppId:   pulumi.Any(app.AppId),
			Role:    pulumi.String("roles/iap.httpsResourceAccessor"),
			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.WebTypeAppEngingIamBinding("binding", new()
    {
        Project = app.Project,
        AppId = app.AppId,
        Role = "roles/iap.httpsResourceAccessor",
        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.WebTypeAppEngingIamBinding;
import com.pulumi.gcp.iap.WebTypeAppEngingIamBindingArgs;
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 WebTypeAppEngingIamBinding("binding", WebTypeAppEngingIamBindingArgs.builder()
            .project(app.project())
            .appId(app.appId())
            .role("roles/iap.httpsResourceAccessor")
            .members("user:jane@example.com")
            .build());

    }
}
resources:
  binding:
    type: gcp:iap:WebTypeAppEngingIamBinding
    properties:
      project: ${app.project}
      appId: ${app.appId}
      role: roles/iap.httpsResourceAccessor
      members:
        - user:jane@example.com

The role property specifies which IAM role to grant (here, roles/iap.httpsResourceAccessor for HTTPS access). The members array lists all identities that should have this role. The appId identifies which App Engine application to protect. When you update the members list, the binding replaces the entire set of members for this role, removing any not in the list.

Add time-based access with IAM Conditions

Access requirements sometimes include expiration dates or time windows. IAM Conditions attach temporal constraints to role bindings without managing separate resources for temporary access.

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

const binding = new gcp.iap.WebTypeAppEngingIamBinding("binding", {
    project: app.project,
    appId: app.appId,
    role: "roles/iap.httpsResourceAccessor",
    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.WebTypeAppEngingIamBinding("binding",
    project=app["project"],
    app_id=app["appId"],
    role="roles/iap.httpsResourceAccessor",
    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.NewWebTypeAppEngingIamBinding(ctx, "binding", &iap.WebTypeAppEngingIamBindingArgs{
			Project: pulumi.Any(app.Project),
			AppId:   pulumi.Any(app.AppId),
			Role:    pulumi.String("roles/iap.httpsResourceAccessor"),
			Members: pulumi.StringArray{
				pulumi.String("user:jane@example.com"),
			},
			Condition: &iap.WebTypeAppEngingIamBindingConditionArgs{
				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.WebTypeAppEngingIamBinding("binding", new()
    {
        Project = app.Project,
        AppId = app.AppId,
        Role = "roles/iap.httpsResourceAccessor",
        Members = new[]
        {
            "user:jane@example.com",
        },
        Condition = new Gcp.Iap.Inputs.WebTypeAppEngingIamBindingConditionArgs
        {
            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.WebTypeAppEngingIamBinding;
import com.pulumi.gcp.iap.WebTypeAppEngingIamBindingArgs;
import com.pulumi.gcp.iap.inputs.WebTypeAppEngingIamBindingConditionArgs;
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 WebTypeAppEngingIamBinding("binding", WebTypeAppEngingIamBindingArgs.builder()
            .project(app.project())
            .appId(app.appId())
            .role("roles/iap.httpsResourceAccessor")
            .members("user:jane@example.com")
            .condition(WebTypeAppEngingIamBindingConditionArgs.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:WebTypeAppEngingIamBinding
    properties:
      project: ${app.project}
      appId: ${app.appId}
      role: roles/iap.httpsResourceAccessor
      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 a CEL expression that must evaluate to true for the binding to apply. The expression property defines the logic (here, checking if the current time is before 2020-01-01). The title and description provide human-readable context. When the condition evaluates to false, IAP denies access even though the role binding exists.

Add a single member to an existing role

When you need to grant access to one additional user without affecting other members, the Member resource provides non-authoritative updates that preserve existing grants managed elsewhere.

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

const member = new gcp.iap.WebTypeAppEngingIamMember("member", {
    project: app.project,
    appId: app.appId,
    role: "roles/iap.httpsResourceAccessor",
    member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp

member = gcp.iap.WebTypeAppEngingIamMember("member",
    project=app["project"],
    app_id=app["appId"],
    role="roles/iap.httpsResourceAccessor",
    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.NewWebTypeAppEngingIamMember(ctx, "member", &iap.WebTypeAppEngingIamMemberArgs{
			Project: pulumi.Any(app.Project),
			AppId:   pulumi.Any(app.AppId),
			Role:    pulumi.String("roles/iap.httpsResourceAccessor"),
			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.WebTypeAppEngingIamMember("member", new()
    {
        Project = app.Project,
        AppId = app.AppId,
        Role = "roles/iap.httpsResourceAccessor",
        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.WebTypeAppEngingIamMember;
import com.pulumi.gcp.iap.WebTypeAppEngingIamMemberArgs;
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 WebTypeAppEngingIamMember("member", WebTypeAppEngingIamMemberArgs.builder()
            .project(app.project())
            .appId(app.appId())
            .role("roles/iap.httpsResourceAccessor")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:iap:WebTypeAppEngingIamMember
    properties:
      project: ${app.project}
      appId: ${app.appId}
      role: roles/iap.httpsResourceAccessor
      member: user:jane@example.com

The member property specifies a single identity to grant the role to (format: user:email@example.com). Unlike Binding, this resource adds one member without replacing the entire list. You can use multiple Member resources for the same role, or combine Member resources with a Binding resource for different roles on the same App Engine application.

Beyond these examples

These snippets focus on specific IAM binding features: role bindings with multiple members, IAM Conditions for temporal access, and non-authoritative member grants. They’re intentionally minimal rather than full access control configurations.

The examples reference pre-existing infrastructure such as App Engine applications (by appId) and GCP projects. They focus on configuring IAM bindings rather than provisioning the underlying applications.

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

  • Policy resource (authoritative, replaces entire policy)
  • Federated identity and workload identity pool principals
  • Custom role definitions and formatting
  • Combining Binding and Member resources for different roles

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

Let's configure 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 Conflicts & Compatibility
Can I use WebTypeAppEngingIamPolicy with WebTypeAppEngingIamBinding or WebTypeAppEngingIamMember?
No, gcp.iap.WebTypeAppEngingIamPolicy cannot be used with gcp.iap.WebTypeAppEngingIamBinding or gcp.iap.WebTypeAppEngingIamMember as they will conflict over policy management. Choose one approach: use Policy for full control, or use Binding/Member for granular management.
Can I use WebTypeAppEngingIamBinding and WebTypeAppEngingIamMember together?
Yes, but only if they manage different roles. If both resources grant privileges to the same role, they will conflict.
IAM Configuration & Roles
What are the required properties for this resource?
You must provide appId (App Engine application ID), project (GCP project ID), role (IAM role to grant), and members (array of identities). All except members are immutable after creation.
How do I specify a custom IAM role?
Use the format [projects|organizations]/{parent-name}/roles/{role-name} for custom roles. For example: projects/my-project/roles/my-custom-role.
Member Identities & Access Control
What member identity formats are supported?

Supported formats include:

  • Users: user:alice@gmail.com
  • Service accounts: serviceAccount:my-app@appspot.gserviceaccount.com
  • Groups: group:admins@example.com
  • Domains: domain:example.com
  • Special identifiers: allUsers, allAuthenticatedUsers
  • Project roles: projectOwner:my-project, projectEditor:my-project, projectViewer:my-project
  • Federated identities: principal://iam.googleapis.com/locations/global/workforcePools/...
IAM Conditions & Time-based Access
How do I add time-based access restrictions?
Use the condition property with title, description, and expression fields. For example, to expire access at midnight on 2020-01-01: expression: "request.time < timestamp(\"2020-01-01T00:00:00Z\")".
What are the limitations of IAM Conditions?
IAM Conditions are supported but have known limitations. Review the GCP IAM Conditions limitations documentation if you encounter issues with conditional access.
Immutability & Updates
What properties can't be changed after creation?
The appId, project, role, and condition properties are immutable. Only the members array can be updated after creation.

Using a different cloud?

Explore iam guides for other cloud providers: