Manage GCP Identity-Aware Proxy IAM for App Engine

The gcp:iap/appEngineVersionIamMember:AppEngineVersionIamMember resource, part of the Pulumi GCP provider, grants IAM roles to individual members for accessing specific App Engine versions through Identity-Aware Proxy. This guide focuses on three capabilities: single-member access grants, time-limited access with IAM Conditions, and multi-member role bindings.

IAP access control sits in front of your App Engine application, checking user identity before allowing requests through. The examples reference existing App Engine versions and assume IAP is already enabled. Combine them with your own App Engine infrastructure and identity management.

Grant access to a single user

Most IAP deployments start by granting access to individual users or service accounts who need to reach your application.

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

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

member = gcp.iap.AppEngineVersionIamMember("member",
    project=version["project"],
    app_id=version["project"],
    service=version["service"],
    version_id=version["versionId"],
    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.NewAppEngineVersionIamMember(ctx, "member", &iap.AppEngineVersionIamMemberArgs{
			Project:   pulumi.Any(version.Project),
			AppId:     pulumi.Any(version.Project),
			Service:   pulumi.Any(version.Service),
			VersionId: pulumi.Any(version.VersionId),
			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.AppEngineVersionIamMember("member", new()
    {
        Project = version.Project,
        AppId = version.Project,
        Service = version.Service,
        VersionId = version.VersionId,
        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.AppEngineVersionIamMember;
import com.pulumi.gcp.iap.AppEngineVersionIamMemberArgs;
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 AppEngineVersionIamMember("member", AppEngineVersionIamMemberArgs.builder()
            .project(version.project())
            .appId(version.project())
            .service(version.service())
            .versionId(version.versionId())
            .role("roles/iap.httpsResourceAccessor")
            .member("user:jane@example.com")
            .build());

    }
}
resources:
  member:
    type: gcp:iap:AppEngineVersionIamMember
    properties:
      project: ${version.project}
      appId: ${version.project}
      service: ${version.service}
      versionId: ${version.versionId}
      role: roles/iap.httpsResourceAccessor
      member: user:jane@example.com

The member property identifies who receives access, using formats like “user:jane@example.com” for Google accounts or “serviceAccount:…” for service accounts. The role property specifies what they can do; “roles/iap.httpsResourceAccessor” allows HTTPS access through IAP. The appId, service, and versionId properties target a specific App Engine version.

Grant time-limited access with IAM Conditions

Contractors, auditors, or time-boxed projects often need access that expires automatically.

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

const member = new gcp.iap.AppEngineVersionIamMember("member", {
    project: version.project,
    appId: version.project,
    service: version.service,
    versionId: version.versionId,
    role: "roles/iap.httpsResourceAccessor",
    member: "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

member = gcp.iap.AppEngineVersionIamMember("member",
    project=version["project"],
    app_id=version["project"],
    service=version["service"],
    version_id=version["versionId"],
    role="roles/iap.httpsResourceAccessor",
    member="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.NewAppEngineVersionIamMember(ctx, "member", &iap.AppEngineVersionIamMemberArgs{
			Project:   pulumi.Any(version.Project),
			AppId:     pulumi.Any(version.Project),
			Service:   pulumi.Any(version.Service),
			VersionId: pulumi.Any(version.VersionId),
			Role:      pulumi.String("roles/iap.httpsResourceAccessor"),
			Member:    pulumi.String("user:jane@example.com"),
			Condition: &iap.AppEngineVersionIamMemberConditionArgs{
				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 member = new Gcp.Iap.AppEngineVersionIamMember("member", new()
    {
        Project = version.Project,
        AppId = version.Project,
        Service = version.Service,
        VersionId = version.VersionId,
        Role = "roles/iap.httpsResourceAccessor",
        Member = "user:jane@example.com",
        Condition = new Gcp.Iap.Inputs.AppEngineVersionIamMemberConditionArgs
        {
            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.AppEngineVersionIamMember;
import com.pulumi.gcp.iap.AppEngineVersionIamMemberArgs;
import com.pulumi.gcp.iap.inputs.AppEngineVersionIamMemberConditionArgs;
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 AppEngineVersionIamMember("member", AppEngineVersionIamMemberArgs.builder()
            .project(version.project())
            .appId(version.project())
            .service(version.service())
            .versionId(version.versionId())
            .role("roles/iap.httpsResourceAccessor")
            .member("user:jane@example.com")
            .condition(AppEngineVersionIamMemberConditionArgs.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:
  member:
    type: gcp:iap:AppEngineVersionIamMember
    properties:
      project: ${version.project}
      appId: ${version.project}
      service: ${version.service}
      versionId: ${version.versionId}
      role: roles/iap.httpsResourceAccessor
      member: 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 constraints to the access grant. The expression property uses CEL (Common Expression Language) to define when access is valid; here, it expires at midnight on 2020-01-01. The title and description properties document the constraint’s purpose. IAM evaluates the condition on every request, automatically denying access after expiration.

Grant access to multiple users at once

Managing team access is more efficient when you bind a role to multiple members together rather than creating individual resources.

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

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

binding = gcp.iap.AppEngineVersionIamBinding("binding",
    project=version["project"],
    app_id=version["project"],
    service=version["service"],
    version_id=version["versionId"],
    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.NewAppEngineVersionIamBinding(ctx, "binding", &iap.AppEngineVersionIamBindingArgs{
			Project:   pulumi.Any(version.Project),
			AppId:     pulumi.Any(version.Project),
			Service:   pulumi.Any(version.Service),
			VersionId: pulumi.Any(version.VersionId),
			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.AppEngineVersionIamBinding("binding", new()
    {
        Project = version.Project,
        AppId = version.Project,
        Service = version.Service,
        VersionId = version.VersionId,
        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.AppEngineVersionIamBinding;
import com.pulumi.gcp.iap.AppEngineVersionIamBindingArgs;
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 AppEngineVersionIamBinding("binding", AppEngineVersionIamBindingArgs.builder()
            .project(version.project())
            .appId(version.project())
            .service(version.service())
            .versionId(version.versionId())
            .role("roles/iap.httpsResourceAccessor")
            .members("user:jane@example.com")
            .build());

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

AppEngineVersionIamBinding manages all members for a specific role as a single unit. The members property lists everyone who should have the role; adding or removing members updates the entire list. This resource is authoritative for its role, meaning it replaces any existing members for that role on the App Engine version.

Beyond these examples

These snippets focus on specific IAP access control features: single-member and multi-member access grants, and time-based access with IAM Conditions. They’re intentionally minimal rather than full identity management solutions.

The examples reference pre-existing infrastructure such as App Engine applications with deployed versions, and IAP enabled on the application. They focus on granting access rather than provisioning the underlying App Engine resources.

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

  • Full policy replacement (AppEngineVersionIamPolicy)
  • Group-based access (group:{emailid} member format)
  • Service account and federated identity grants
  • Domain-wide access grants

These omissions are intentional: the goal is to illustrate how IAP access grants are wired, not provide drop-in identity modules. See the AppEngineVersionIamMember resource reference for all available configuration options.

Let's manage GCP Identity-Aware Proxy IAM for App Engine

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 these IAM resources together?
AppEngineVersionIamPolicy cannot be used with AppEngineVersionIamBinding or AppEngineVersionIamMember as they will conflict. However, AppEngineVersionIamBinding and AppEngineVersionIamMember can be used together only if they don’t grant privileges to the same role.
Resource Selection & Use Cases
What's the difference between the three IAM resource types?
AppEngineVersionIamPolicy is authoritative and replaces the entire IAM policy. AppEngineVersionIamBinding is authoritative for a specific role, replacing all members for that role while preserving other roles. AppEngineVersionIamMember is non-authoritative, adding a single member to a role without affecting other members.
Which IAM resource should I use?
Use AppEngineVersionIamMember to grant access to individual users without affecting existing permissions. Use AppEngineVersionIamBinding to manage all members for a specific role. Use AppEngineVersionIamPolicy only when you need complete control over the entire IAM policy.
Configuration & Identity Formats
What member identity formats are supported?
Supported formats include allUsers, allAuthenticatedUsers, user:{email}, serviceAccount:{email}, group:{email}, domain:{domain}, projectOwner/Editor/Viewer:{projectid}, and federated identities like principal://iam.googleapis.com/locations/global/workforcePools/{pool}/subject/{subject}.
How do I use custom IAM roles?
Custom roles must use the format [projects|organizations]/{parent-name}/roles/{role-name}. For example, projects/my-project/roles/my-custom-role or organizations/my-org/roles/my-custom-role.
Immutability & Lifecycle
Can I modify IAM bindings after creation?
No, all properties (appId, member, project, role, service, versionId, and condition) are immutable. To change any of these, you must destroy and recreate the resource.
How do I import existing IAM bindings?
Use the format: {resource-identifier} {role} {member}. For example: pulumi import gcp:iap/appEngineVersionIamMember:AppEngineVersionIamMember editor "projects/{{project}}/iap_web/appengine-{{appId}}/services/{{service}}/versions/{{versionId}} roles/iap.httpsResourceAccessor user:jane@example.com". The resource identifier can also use shortened forms like {{appId}}/{{service}}/{{versionId}}.
IAM Conditions
What should I know about IAM Conditions?
IAM Conditions are supported through the condition property, allowing time-based or attribute-based access control. However, they have known limitations. The condition field is immutable after creation.

Using a different cloud?

Explore security guides for other cloud providers: