Identity-Aware Proxy (IAP)¶
Complete guide to Identity-Aware Proxy - application-level access control without VPNs in Google Cloud Platform.
📚 Overview¶
Identity-Aware Proxy (IAP) lets you establish a central authorization layer for applications accessed by HTTPS, enabling you to adopt an application-level access control model instead of relying on network-level firewalls.
Key Benefits: - Zero Trust Security: Verify identity and context for every request - No VPN Required: Access internal apps from anywhere securely - Centralized Access Control: Manage access in one place - Context-Aware: Consider device, location, and other factors - Easy Integration: Works with existing applications
🎯 What is IAP?¶
┌────────────────────────────────────────────────────────┐
│ Traditional Access vs IAP │
├────────────────────────────────────────────────────────┤
│ │
│ Traditional VPN Model: │
│ User → VPN → Network → Application │
│ • Network-level trust │
│ • Once inside, access everything │
│ • Complex VPN management │
│ • Poor user experience │
│ │
│ IAP Model: │
│ User → IAP → Application │
│ • Application-level trust │
│ • Per-app access control │
│ • No VPN needed │
│ • Better user experience │
│ • Context-aware decisions │
│ │
│ IAP Verifies: │
│ ✓ User identity (who) │
│ ✓ User authorization (what they can access) │
│ ✓ Context (device, location, time) │
│ ✓ Request validity │
└────────────────────────────────────────────────────────┘
🏗️ IAP Architecture¶
┌─────────────────────────────────────────────────────────────────┐
│ IAP Request Flow │
└─────────────────────────────────────────────────────────────────┘
Step 1: User Request
┌─────────────────────┐
│ User Browser │
│ https://app.com │
└──────────┬──────────┘
│
▼
Step 2: IAP Authentication
┌─────────────────────┐
│ Identity-Aware │
│ Proxy │
│ • Check auth │
│ • Verify identity │
└──────────┬──────────┘
│
▼
Step 3: Authorization Check
┌─────────────────────┐
│ IAM Policy │
│ • Check IAP access │
│ • Verify role │
└──────────┬──────────┘
│
▼
Step 4: Context Evaluation (Optional)
┌─────────────────────┐
│ Access Context │
│ Manager │
│ • Device policy │
│ • IP range │
│ • Access level │
└──────────┬──────────┘
│
▼
Step 5: Forward Request
┌─────────────────────┐
│ Backend │
│ Application │
│ • Receives request │
│ • With IAP headers │
└─────────────────────┘
IAP adds headers:
X-Goog-IAP-JWT-Assertion: <signed JWT>
X-Goog-Authenticated-User-Email: user@company.com
X-Goog-Authenticated-User-ID: 123456789
🚀 Setting Up IAP¶
1. Enable IAP for App Engine¶
# Enable IAP API
gcloud services enable iap.googleapis.com
# Enable IAP for App Engine
gcloud app services update default \
--ingress=internal-and-cloud-load-balancing
# Grant IAP access to users
gcloud iap web add-iam-policy-binding \
--resource-type=app-engine \
--service=default \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor'
# Grant access to group
gcloud iap web add-iam-policy-binding \
--resource-type=app-engine \
--service=default \
--member='group:developers@company.com' \
--role='roles/iap.httpsResourceAccessor'
2. Enable IAP for Compute Engine¶
# Create backend service with IAP
gcloud compute backend-services create web-backend \
--protocol=HTTP \
--port-name=http \
--health-checks=web-health-check \
--global \
--iap=enabled
# Configure OAuth consent screen (one-time setup)
# Go to: APIs & Services → OAuth consent screen
# Configure: App name, support email, authorized domains
# Create OAuth credentials
gcloud iap oauth-brands create \
--application_title="My Application" \
--support_email="support@company.com"
# Enable IAP
gcloud iap web enable \
--resource-type=backend-services \
--service=web-backend
# Grant access
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=web-backend \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor'
3. Enable IAP for GKE¶
# Create BackendConfig with IAP
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: iap-backendconfig
namespace: default
spec:
iap:
enabled: true
oauthclientCredentials:
secretName: oauth-client-secret
---
# Create OAuth secret
apiVersion: v1
kind: Secret
metadata:
name: oauth-client-secret
namespace: default
type: Opaque
stringData:
client_id: YOUR_CLIENT_ID
client_secret: YOUR_CLIENT_SECRET
---
# Service with BackendConfig annotation
apiVersion: v1
kind: Service
metadata:
name: web-service
annotations:
cloud.google.com/backend-config: '{"default": "iap-backendconfig"}'
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 8080
# Grant IAP access for GKE service
gcloud iap web add-iam-policy-binding \
--resource-type=backend-services \
--service=SERVICE_NAME \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor'
🔐 IAP for SSH and TCP¶
1. Enable IAP for SSH¶
# Enable IAP for TCP forwarding
gcloud services enable iap.googleapis.com
# Grant IAP tunnel user role
gcloud projects add-iam-policy-binding PROJECT_ID \
--member='user:alice@company.com' \
--role='roles/iap.tunnelResourceAccessor'
# Also need compute instance access
gcloud compute instances add-iam-policy-binding my-vm \
--zone=us-central1-a \
--member='user:alice@company.com' \
--role='roles/compute.instanceAdmin.v1'
# SSH via IAP tunnel (no external IP needed!)
gcloud compute ssh my-vm \
--zone=us-central1-a \
--tunnel-through-iap
# Or use standard SSH with IAP
ssh -o ProxyCommand="gcloud compute start-iap-tunnel %h %p" my-vm
2. Enable IAP for RDP (Windows)¶
# Grant IAP access
gcloud projects add-iam-policy-binding PROJECT_ID \
--member='user:alice@company.com' \
--role='roles/iap.tunnelResourceAccessor'
# Start IAP tunnel for RDP
gcloud compute start-iap-tunnel windows-vm 3389 \
--local-host-port=localhost:3389 \
--zone=us-central1-a
# Connect with RDP client to localhost:3389
3. IAP for Database Access¶
# Access Cloud SQL via IAP tunnel
gcloud compute start-iap-tunnel bastion-vm 3306 \
--local-host-port=localhost:3306 \
--zone=us-central1-a
# Connect to MySQL
mysql -h 127.0.0.1 -P 3306 -u root -p
# Access PostgreSQL
gcloud compute start-iap-tunnel bastion-vm 5432 \
--local-host-port=localhost:5432 \
--zone=us-central1-a
psql -h 127.0.0.1 -p 5432 -U postgres
🛡️ Verifying IAP Requests¶
1. Validate JWT in Application¶
# Python: Verify IAP JWT
from google.auth.transport import requests
from google.oauth2 import id_token
def verify_iap_jwt(iap_jwt, expected_audience):
"""Verify IAP JWT token"""
try:
decoded_jwt = id_token.verify_token(
iap_jwt,
requests.Request(),
audience=expected_audience,
certs_url='https://www.gstatic.com/iap/verify/public_key'
)
# Extract user info
user_email = decoded_jwt.get('email')
user_id = decoded_jwt.get('sub')
return user_email, user_id
except Exception as e:
print(f"JWT verification failed: {e}")
return None, None
# In your Flask/Django app
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
# Get IAP JWT from header
iap_jwt = request.headers.get('X-Goog-IAP-JWT-Assertion')
if not iap_jwt:
return 'No IAP JWT found', 401
# Verify JWT
expected_audience = '/projects/PROJECT_NUMBER/apps/PROJECT_ID'
user_email, user_id = verify_iap_jwt(iap_jwt, expected_audience)
if not user_email:
return 'Invalid IAP JWT', 401
return f'Hello {user_email}!'
// Node.js: Verify IAP JWT
const { OAuth2Client } = require('google-auth-library');
async function verifyIapJwt(iapJwt, expectedAudience) {
const oAuth2Client = new OAuth2Client();
try {
const ticket = await oAuth2Client.verifyIdToken({
idToken: iapJwt,
audience: expectedAudience
});
const payload = ticket.getPayload();
return {
email: payload.email,
userId: payload.sub
};
} catch (error) {
console.error('JWT verification failed:', error);
return null;
}
}
// Express middleware
app.use(async (req, res, next) => {
const iapJwt = req.headers['x-goog-iap-jwt-assertion'];
if (!iapJwt) {
return res.status(401).send('No IAP JWT');
}
const expectedAudience = '/projects/PROJECT_NUMBER/apps/PROJECT_ID';
const user = await verifyIapJwt(iapJwt, expectedAudience);
if (!user) {
return res.status(401).send('Invalid IAP JWT');
}
req.user = user;
next();
});
2. Get User Information¶
# Extract user info from IAP headers
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
# IAP automatically adds these headers
user_email = request.headers.get('X-Goog-Authenticated-User-Email')
user_id = request.headers.get('X-Goog-Authenticated-User-ID')
# Remove 'accounts.google.com:' prefix
if user_email:
user_email = user_email.split(':')[1]
if user_id:
user_id = user_id.split(':')[1]
return f'Hello {user_email} (ID: {user_id})'
🎯 Context-Aware Access¶
1. Access Levels¶
# Create access level based on IP range
gcloud access-context-manager levels create corporate_network \
--title="Corporate Network" \
--basic-level-spec=ip_subnetworks=203.0.113.0/24,198.51.100.0/24 \
--policy=POLICY_ID
# Create access level based on device policy
gcloud access-context-manager levels create managed_devices \
--title="Managed Devices" \
--basic-level-spec=require_screen_lock=true,require_corp_owned=true \
--policy=POLICY_ID
# Create access level combining conditions
gcloud access-context-manager levels create secure_access \
--title="Secure Access" \
--basic-level-spec=ip_subnetworks=203.0.113.0/24,require_screen_lock=true \
--combine-function=AND \
--policy=POLICY_ID
2. Apply Access Levels to IAP¶
# Create IAM condition with access level
gcloud iap web add-iam-policy-binding \
--resource-type=app-engine \
--service=default \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor' \
--condition='expression=accessPolicies/POLICY_ID/accessLevels/corporate_network,title=Corporate Network Only'
# Multiple access levels
gcloud iap web add-iam-policy-binding \
--resource-type=app-engine \
--service=default \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor' \
--condition='expression=accessPolicies/POLICY_ID/accessLevels/corporate_network || accessPolicies/POLICY_ID/accessLevels/managed_devices,title=Corporate or Managed Device'
📊 Monitoring and Logging¶
1. IAP Access Logs¶
# View IAP access logs
gcloud logging read \
'resource.type="gae_app" AND
protoPayload.resourceName=~"iap"' \
--limit=50 \
--format=json
# View denied access attempts
gcloud logging read \
'resource.type="gae_app" AND
protoPayload.status.code!=0 AND
protoPayload.resourceName=~"iap"' \
--limit=50
# View specific user access
gcloud logging read \
'protoPayload.authenticationInfo.principalEmail="alice@company.com" AND
protoPayload.resourceName=~"iap"' \
--limit=50
2. Create Alerts¶
# Alert on IAP access denials
gcloud logging sinks create iap-denials \
--log-filter='resource.type="gae_app" AND protoPayload.status.code!=0 AND protoPayload.resourceName=~"iap"' \
--destination=pubsub.googleapis.com/projects/PROJECT_ID/topics/security-alerts
# Alert on new IAP users
gcloud logging sinks create iap-new-users \
--log-filter='protoPayload.methodName="SetIamPolicy" AND protoPayload.request.policy.bindings.role="roles/iap.httpsResourceAccessor"' \
--destination=pubsub.googleapis.com/projects/PROJECT_ID/topics/iam-changes
🔧 Troubleshooting¶
Common Issues¶
Issue: "You don't have access"
# Check if user has IAP role
gcloud iap web get-iam-policy \
--resource-type=app-engine \
--service=default
# Grant access
gcloud iap web add-iam-policy-binding \
--resource-type=app-engine \
--service=default \
--member='user:alice@company.com' \
--role='roles/iap.httpsResourceAccessor'
# Check OAuth consent screen configuration
# Go to: APIs & Services → OAuth consent screen
Issue: IAP not enabled
# Check if IAP is enabled
gcloud iap web get-iam-policy \
--resource-type=app-engine \
--service=default
# Enable IAP
gcloud app services update default \
--ingress=internal-and-cloud-load-balancing
Issue: JWT verification fails
# Common causes:
# 1. Wrong audience in verification
# 2. Clock skew
# 3. Expired token
# 4. Invalid signature
# Debug JWT
import jwt
import json
iap_jwt = request.headers.get('X-Goog-IAP-JWT-Assertion')
decoded = jwt.decode(iap_jwt, options={"verify_signature": False})
print(json.dumps(decoded, indent=2))
# Check audience matches your project
# audience should be: /projects/PROJECT_NUMBER/apps/PROJECT_ID
✅ Best Practices¶
Security¶
- [ ] Always verify JWT in application code
- [ ] Use context-aware access when possible
- [ ] Enable audit logging
- [ ] Regular access reviews
- [ ] Use groups instead of individual users
- [ ] Implement least privilege
- [ ] Monitor for unusual access patterns
Configuration¶
- [ ] Configure OAuth consent screen properly
- [ ] Use meaningful OAuth client names
- [ ] Document IAP-protected resources
- [ ] Test access from different contexts
- [ ] Have break-glass procedures
Operations¶
- [ ] Monitor IAP access logs
- [ ] Set up alerts for denials
- [ ] Regular security audits
- [ ] Keep documentation updated
- [ ] Train users on IAP access
🎓 Next Steps¶
- Explore Advanced IAM features
- Review Best Practices for enterprise IAM
- Return to Least Privilege for access control strategies
- Learn about Service Accounts for application identity
Last Updated: March 2026 Version: 2.0