Configuring Google Cloud Identity-Aware Proxy (IAP) to enforce per-request identity verification for Compute Engine, App Engine, Cloud Run, and GKE services using access levels, context-aware policies, and programmatic access with service accounts.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Configuring Google Cloud Identity-Aware Proxy (IAP) to enforce per-request identity verification for Compute Engine, App Engine, Cloud Run, and GKE services using access levels, context-aware policies, and programmatic access with service accounts.
When protecting Google Cloud applications (App Engine, Cloud Run, GKE, Compute Engine) with identity-based access
When implementing context-aware access requiring device posture and location verification
When providing secure access to internal tools without VPN or public IP exposure
When needing per-request authentication and authorization for web applications and TCP services
When configuring programmatic access to IAP-protected resources using service accounts
Do not use for non-HTTP applications that cannot be placed behind an HTTPS load balancer, for public-facing applications that need unauthenticated access, or when applications handle their own authentication and IAP would conflict with existing auth flows.
Common Misconfigurations & Verification
Backend reachable bypassing IAP: IAP only protects traffic arriving through the HTTPS load balancer. If the GCE/GKE instance has a public IP, or its VPC firewall allows the app port from 0.0.0.0/0, anyone hits the origin directly. Restrict ingress firewall rules to the IAP source range 35.235.240.0/20 ONLY and remove public IPs.
allUsers/allAuthenticatedUsers on the backend: a stray roles/run.invoker or roles/iap.httpsResourceAccessor binding to allUsers neuters every policy. Audit IAM bindings on each backend/Cloud Run service.
Access level attached but not enforced: granting iap.httpsResourceAccessor WITHOUT the --condition access-level expression authenticates identity but skips device posture/IP checks. Confirm finance/admin bindings carry request.auth.access_levels.exists(...).
App ignores the IAP JWT: if the app doesn't validate the x-goog-iap-jwt-assertion header, anyone reaching the origin directly is treated as authenticated.
Verify: from off-VPN run curl http://<instance-ip>:<port>/ against both public and private IPs; both must be unreachable (only 35.235.240.0/20 permitted). Sign in from a device that fails the access level and confirm protoPayload.status.code=16 / a denied entry via the iap-denied-access log metric. Confirm no IAM binding resolves to allUsers.
Prerequisites
Google Cloud project with billing enabled
IAP API enabled (gcloud services enable iap.googleapis.com)
Application deployed behind HTTPS Load Balancer, App Engine, or Cloud Run
Cloud Identity or Google Workspace for user management
Access Context Manager API enabled for access levels
OAuth consent screen configured for the project
Workflow
Step 1: Enable IAP on Backend Services
Configure IAP for different GCP compute platforms.
# Enable required APIs
gcloud services enable iap.googleapis.com
gcloud services enable accesscontextmanager.googleapis.com
# Create OAuth consent screen
gcloud iap oauth-brands create \
--application_title="Internal Applications" \
--support_email=security@company.com
# Create OAuth client
gcloud iap oauth-clients create \
projects/PROJECT_ID/brands/BRAND_ID \
--display_name="IAP Web Client"# === Enable IAP on Compute Engine Backend Service ===
gcloud compute backend-services update my-backend-service \
--iap=enabled,oauth2-client-id=CLIENT_ID,oauth2-client-secret=CLIENT_SECRET \
--global
# === Enable IAP on App Engine ===
gcloud iap web enable \
--resource-type=app-engine \
--oauth2-client-id=CLIENT_ID \
--oauth2-client-secret=CLIENT_SECRET
# === Enable IAP on Cloud Run ===# First grant IAP service account the Cloud Run Invoker role
gcloud run services add-iam-policy-binding my-service \
--member="serviceAccount:service-PROJECT_NUM@gcp-sa-iap.iam.gserviceaccount.com" \
--role="roles/run.invoker" \
--region=us-central1
# Enable IAP on the Cloud Run backend service
gcloud compute backend-services update my-cloud-run-backend \
--iap=enabled,oauth2-client-id=CLIENT_ID,oauth2-client-secret=CLIENT_SECRET \
--global
# === Enable IAP TCP Forwarding for SSH/RDP ===# No load balancer needed - uses IAP tunnel
gcloud compute instances add-iam-policy-binding my-vm \
--member="group:developers@company.com" \
--role="roles/iap.tunnelResourceAccessor" \
--zone=us-central1-a
# SSH through IAP tunnel
gcloud compute ssh my-vm --zone=us-central1-a --tunnel-through-iap
# RDP through IAP tunnel
gcloud compute start-iap-tunnel my-windows-vm 3389 \
--local-host-port=localhost:3390 \
--zone=us-central1-a
Step 2: Configure IAM Bindings for Access Control
Grant access to specific users and groups with optional access level conditions.
# Grant basic access to a group
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=my-backend-service \
--member="group:engineering@company.com" \
--role="roles/iap.httpsResourceAccessor"# Grant access with access level condition
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=finance-app \
--member="group:finance@company.com" \
--role="roles/iap.httpsResourceAccessor" \
--condition='expression=request.auth.access_levels.exists(x, x == "accessPolicies/POLICY_ID/accessLevels/corporate-device"),title=RequireCorporateDevice,description=Requires managed corporate device'# Grant access only during business hours
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=admin-console \
--member="group:admins@company.com" \
--role="roles/iap.httpsResourceAccessor" \
--condition='expression=request.time.getHours("America/New_York") >= 8 && request.time.getHours("America/New_York") <= 18 && request.time.getDayOfWeek("America/New_York") >= 1 && request.time.getDayOfWeek("America/New_York") <= 5,title=BusinessHoursOnly'# Grant access to a specific URL path
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=internal-api \
--member="group:api-consumers@company.com" \
--role="roles/iap.httpsResourceAccessor" \
--condition='expression=request.path.startsWith("/api/v2/"),title=APIv2Access'
Step 3: Create Access Levels with Access Context Manager
Define context-based access requirements using device attributes and network conditions.
Scenario: Securing 15 Internal GCP Services with IAP
Context: An e-commerce company runs 15 internal services on GKE and Cloud Run (admin dashboards, internal APIs, monitoring tools). Currently, these services are protected only by VPN and firewall rules, creating excessive network-level access.
Approach:
Deploy all services behind an HTTPS Load Balancer with managed SSL certificates
Enable IAP on each backend service with per-service OAuth clients
Create IAM bindings mapping Google Groups to specific services (admin group -> admin dashboard, engineering -> monitoring)
Apply managed-device access level to admin dashboard and financial tools
Configure IAP TCP tunneling for SSH access to GKE nodes (replacing SSH bastion host)
Set re-authentication to 4 hours for admin tools, 8 hours for monitoring
Configure Cloud Audit Logs and create alerting for repeated denials
Pitfalls: IAP adds 10-50ms latency per request; test application performance. WebSocket connections through IAP require specific backend service configuration. Service-to-service calls within GKE should bypass IAP using internal service mesh, not external IAP endpoints. Break-glass access should use a separate IAM binding without access level conditions.