The gcp:iap/appEngineServiceIamBinding:AppEngineServiceIamBinding resource, part of the Pulumi GCP provider, manages IAM role bindings for Identity-Aware Proxy (IAP) protected App Engine services. This guide focuses on three capabilities: authoritative role binding that replaces all members for a role, non-authoritative member addition that preserves existing members, and time-based access with IAM Conditions.
This resource manages access to existing App Engine services with IAP enabled. The examples are intentionally small. Combine them with your own App Engine services and IAM role definitions.
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.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const binding = new gcp.iap.AppEngineServiceIamBinding("binding", {
project: version.project,
appId: version.project,
service: version.service,
role: "roles/iap.httpsResourceAccessor",
members: ["user:jane@example.com"],
});
import pulumi
import pulumi_gcp as gcp
binding = gcp.iap.AppEngineServiceIamBinding("binding",
project=version["project"],
app_id=version["project"],
service=version["service"],
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.NewAppEngineServiceIamBinding(ctx, "binding", &iap.AppEngineServiceIamBindingArgs{
Project: pulumi.Any(version.Project),
AppId: pulumi.Any(version.Project),
Service: pulumi.Any(version.Service),
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.AppEngineServiceIamBinding("binding", new()
{
Project = version.Project,
AppId = version.Project,
Service = version.Service,
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.AppEngineServiceIamBinding;
import com.pulumi.gcp.iap.AppEngineServiceIamBindingArgs;
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 AppEngineServiceIamBinding("binding", AppEngineServiceIamBindingArgs.builder()
.project(version.project())
.appId(version.project())
.service(version.service())
.role("roles/iap.httpsResourceAccessor")
.members("user:jane@example.com")
.build());
}
}
resources:
binding:
type: gcp:iap:AppEngineServiceIamBinding
properties:
project: ${version.project}
appId: ${version.project}
service: ${version.service}
role: roles/iap.httpsResourceAccessor
members:
- user:jane@example.com
The AppEngineServiceIamBinding resource is authoritative for the specified role: it replaces any existing members for that role with the list you provide. The members array accepts user accounts, service accounts, groups, and special identifiers like allAuthenticatedUsers. The appId and service properties identify which App Engine service to protect.
Add time-based access with IAM Conditions
Access requirements sometimes include temporal constraints, such as granting temporary access for contractors or time-limited testing.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const binding = new gcp.iap.AppEngineServiceIamBinding("binding", {
project: version.project,
appId: version.project,
service: version.service,
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.AppEngineServiceIamBinding("binding",
project=version["project"],
app_id=version["project"],
service=version["service"],
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.NewAppEngineServiceIamBinding(ctx, "binding", &iap.AppEngineServiceIamBindingArgs{
Project: pulumi.Any(version.Project),
AppId: pulumi.Any(version.Project),
Service: pulumi.Any(version.Service),
Role: pulumi.String("roles/iap.httpsResourceAccessor"),
Members: pulumi.StringArray{
pulumi.String("user:jane@example.com"),
},
Condition: &iap.AppEngineServiceIamBindingConditionArgs{
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.AppEngineServiceIamBinding("binding", new()
{
Project = version.Project,
AppId = version.Project,
Service = version.Service,
Role = "roles/iap.httpsResourceAccessor",
Members = new[]
{
"user:jane@example.com",
},
Condition = new Gcp.Iap.Inputs.AppEngineServiceIamBindingConditionArgs
{
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.AppEngineServiceIamBinding;
import com.pulumi.gcp.iap.AppEngineServiceIamBindingArgs;
import com.pulumi.gcp.iap.inputs.AppEngineServiceIamBindingConditionArgs;
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 AppEngineServiceIamBinding("binding", AppEngineServiceIamBindingArgs.builder()
.project(version.project())
.appId(version.project())
.service(version.service())
.role("roles/iap.httpsResourceAccessor")
.members("user:jane@example.com")
.condition(AppEngineServiceIamBindingConditionArgs.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:AppEngineServiceIamBinding
properties:
project: ${version.project}
appId: ${version.project}
service: ${version.service}
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")
IAM Conditions let you express temporal constraints directly in the binding. The condition block requires a title, an expression using Common Expression Language (CEL), and an optional description. The expression request.time < timestamp("2020-01-01T00:00:00Z") grants access only until the specified timestamp. This extends the basic binding pattern with automatic expiration.
Add a single member to an existing role
When you need to grant access to one additional user without affecting other members, AppEngineServiceIamMember adds that member non-authoritatively.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const member = new gcp.iap.AppEngineServiceIamMember("member", {
project: version.project,
appId: version.project,
service: version.service,
role: "roles/iap.httpsResourceAccessor",
member: "user:jane@example.com",
});
import pulumi
import pulumi_gcp as gcp
member = gcp.iap.AppEngineServiceIamMember("member",
project=version["project"],
app_id=version["project"],
service=version["service"],
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.NewAppEngineServiceIamMember(ctx, "member", &iap.AppEngineServiceIamMemberArgs{
Project: pulumi.Any(version.Project),
AppId: pulumi.Any(version.Project),
Service: pulumi.Any(version.Service),
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.AppEngineServiceIamMember("member", new()
{
Project = version.Project,
AppId = version.Project,
Service = version.Service,
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.AppEngineServiceIamMember;
import com.pulumi.gcp.iap.AppEngineServiceIamMemberArgs;
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 AppEngineServiceIamMember("member", AppEngineServiceIamMemberArgs.builder()
.project(version.project())
.appId(version.project())
.service(version.service())
.role("roles/iap.httpsResourceAccessor")
.member("user:jane@example.com")
.build());
}
}
resources:
member:
type: gcp:iap:AppEngineServiceIamMember
properties:
project: ${version.project}
appId: ${version.project}
service: ${version.service}
role: roles/iap.httpsResourceAccessor
member: user:jane@example.com
Unlike AppEngineServiceIamBinding, the AppEngineServiceIamMember resource is non-authoritative: it adds a single member to a role without replacing existing members. Use this when you want to grant access incrementally. The member property accepts the same identity formats as the members array in bindings.
Beyond these examples
These snippets focus on specific IAM binding features: role-based access control (Binding vs Member resources) and IAM Conditions for time-based access. They’re intentionally minimal rather than full access control configurations.
The examples assume pre-existing infrastructure such as App Engine services with IAP enabled and a GCP project with appropriate IAM permissions. They focus on configuring access bindings rather than provisioning the underlying services.
To keep things focused, common IAM patterns are omitted, including:
- Full policy replacement (AppEngineServiceIamPolicy resource)
- Audit logging and access monitoring
- Custom role definitions
- Federated identity configuration details
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 AppEngineServiceIamBinding resource reference for all available configuration options.
Let's manage GCP App Engine IAP Service Permissions
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Resource Selection & Conflicts
AppEngineServiceIamPolicy is fully authoritative and replaces the entire IAM policy. AppEngineServiceIamBinding is authoritative for a specific role, managing all members for that role while preserving other roles. AppEngineServiceIamMember is non-authoritative, adding individual members without affecting other members or roles.AppEngineServiceIamPolicy with AppEngineServiceIamBinding or AppEngineServiceIamMember, as they will conflict. However, you can use AppEngineServiceIamBinding and AppEngineServiceIamMember together only if they manage different roles.AppEngineServiceIamPolicy to manage the complete policy, AppEngineServiceIamBinding to manage all members for specific roles, or AppEngineServiceIamMember to add individual members without affecting existing permissions. Choose based on whether you need full control or incremental changes.IAM Configuration
members property supports: allUsers, allAuthenticatedUsers, user:{email}, serviceAccount:{email}, group:{email}, domain:{domain}, projectOwner/Editor/Viewer:{projectid}, and federated identities like principal://iam.googleapis.com/....[projects|organizations]/{parent-name}/roles/{role-name}. For example: projects/my-project/roles/my-custom-role or organizations/my-org/roles/my-custom-role.IAM Conditions
condition property with title, description, and expression fields. For example, to expire access at a specific time: expression: "request.time < timestamp(\"2020-01-01T00:00:00Z\")".