Configure GCP Identity-Aware Proxy IAM Policies

The gcp:iap/webTypeAppEngingIamPolicy:WebTypeAppEngingIamPolicy resource, part of the Pulumi GCP provider, manages IAM access control for Identity-Aware Proxy (IAP) protected App Engine applications. This guide focuses on three capabilities: role binding with member lists, time-based access conditions, and non-authoritative member additions.

These resources attach to existing App Engine applications and reference them by project and appId. The examples are intentionally small. Combine them with your own App Engine infrastructure and organizational access patterns.

Grant a role to multiple members with IamBinding

When you need to grant the same role to multiple users or service accounts, IamBinding manages the complete member list for that role while preserving other roles in the policy.

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 IAP role to grant (here, roles/iap.httpsResourceAccessor for HTTPS access). The members array lists all identities that should have this role. IamBinding is authoritative for this specific role: it replaces the member list but leaves other roles untouched. The appId and project properties identify which App Engine application to protect.

Add time-based access with IAM Conditions

Access requirements often include temporal constraints, such as granting temporary access that expires automatically.

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 time-based constraint to the role binding. The expression property uses CEL (Common Expression Language) to define when access is valid; here, access expires at midnight on 2020-01-01. The title and description properties document the condition’s purpose. IAM evaluates conditions at request time, automatically denying access once the timestamp passes.

Add individual members without affecting others

In collaborative environments, you often need to grant access to one person without modifying existing permissions or coordinating with other administrators.

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 (singular) grants access to one identity. Unlike IamBinding, IamMember is non-authoritative: it adds this member without affecting other members who have the same role. This approach works well when multiple teams manage access independently, but you must avoid using IamPolicy or IamBinding for the same role, as they would conflict.

Beyond these examples

These snippets focus on specific IAM management features: role binding and member management, time-based access with IAM Conditions, and authoritative vs non-authoritative updates. They’re intentionally minimal rather than complete access control configurations.

The examples reference pre-existing infrastructure such as App Engine applications (project and appId). They focus on configuring IAM policies rather than provisioning the applications themselves.

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

  • Full policy replacement with IamPolicy resource
  • Complex condition expressions (location, resource attributes)
  • Service account and group membership patterns
  • Policy data source for read-only access

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

Let's configure GCP Identity-Aware Proxy 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
Can I use WebTypeAppEngingIamPolicy with WebTypeAppEngingIamBinding or WebTypeAppEngingIamMember?
No, WebTypeAppEngingIamPolicy cannot be used with WebTypeAppEngingIamBinding or WebTypeAppEngingIamMember because they will conflict over policy management. Choose one approach: use WebTypeAppEngingIamPolicy for full policy control, or use WebTypeAppEngingIamBinding/WebTypeAppEngingIamMember for granular management.
Can I use WebTypeAppEngingIamBinding and WebTypeAppEngingIamMember together?
Yes, but only if they manage different roles. Using both resources for the same role will cause conflicts.
What's the difference between WebTypeAppEngingIamPolicy, WebTypeAppEngingIamBinding, and WebTypeAppEngingIamMember?
WebTypeAppEngingIamPolicy is authoritative and replaces the entire IAM policy. WebTypeAppEngingIamBinding is authoritative for a specific role but preserves other roles. WebTypeAppEngingIamMember is non-authoritative and adds individual members while preserving existing members for that role.
IAM Conditions & Advanced Features
How do I add time-based conditions to IAM policies?
Add a condition block with title, description, and expression fields. For example, to expire access at midnight on 2019-12-31, use expression request.time < timestamp("2020-01-01T00:00:00Z").
What are the limitations of IAM Conditions?
IAM Conditions are supported but have known limitations. If you experience issues with IAM Conditions, review the GCP documentation on condition limitations.
Configuration & Setup
What properties are required to configure this resource?
You need appId (the App Engine app ID), policyData (generated by gcp.organizations.getIAMPolicy), and project (the GCP project ID). Both appId and project are immutable after creation.
Import & Custom Roles
How do I import resources with custom IAM roles?
Use the full name of the custom role in the format [projects/my-project|organizations/my-org]/roles/my-custom-role when importing.

Using a different cloud?

Explore security guides for other cloud providers: