Identity-Aware Proxy (IAP) for Secure Access

Cloud & Infrastructure intermediate Building with AI ~20 min read

Overview

Google Cloud’s Identity-Aware Proxy (IAP) is a managed service that controls access to cloud applications and virtual machines without requiring a traditional VPN. Instead of exposing services through open firewall rules or relying on SSH keys distributed across teams, IAP verifies user identity and context before granting access. In a production infrastructure codebase, IAP serves as the primary mechanism for securing admin interfaces, internal dashboards, and direct machine access across a cloud-native platform.

The fundamental shift IAP introduces is moving from network-perimeter security to per-request identity verification — a core tenet of Google’s BeyondCorp security model. Every request to an IAP-protected resource must carry a valid OAuth 2.0 token, and IAP evaluates that token against configured IAM policies before forwarding traffic to the backend. This eliminates the need to manage SSH keys, bastion hosts, or VPN concentrators, reducing both operational overhead and attack surface.

In the infrastructure project, IAP is provisioned declaratively using Pulumi’s Go SDK. The infrastructure code defines IAP settings alongside backend services, IAM bindings for Google Groups, and firewall rules that restrict ingress to Google’s IAP forwarding ranges only. This approach ensures that every environment — from development to production — enforces consistent access policies through code review rather than manual configuration.

Core Concepts

IAP Tunnel

IAP tunnels provide encrypted TCP connections to VM instances and GKE nodes without requiring public IP addresses. The gcloud compute start-iap-tunnel command establishes a WebSocket-based tunnel through Google’s front-end infrastructure, forwarding local ports to the target instance. This completely replaces traditional SSH bastion host patterns and eliminates the need to distribute or rotate SSH keys across teams.

IAP-Protected Resources

Any HTTP(S) backend service behind a Google Cloud Load Balancer can be IAP-protected. When IAP is enabled on a backend service, Google’s infrastructure intercepts every request, redirects unauthenticated users to a Google sign-in flow, and only forwards authenticated & authorized requests to the backend. The backend receives a signed JWT header (x-goog-iap-jwt-assertion) that it can independently verify for defense in depth.

Context-Aware Access

Beyond simple identity checks, IAP supports access levels that evaluate device posture, IP address ranges, and other contextual signals. Access levels are defined in Access Context Manager and can be attached to IAP policies to enforce conditions like “corporate-managed device with encrypted disk” or “originating from a known office IP range.” This enables fine-grained zero-trust policies without modifying application code.

IAP TCP Forwarding

TCP forwarding extends IAP beyond HTTP traffic, enabling secure tunnels for arbitrary TCP protocols such as SSH, RDP, and database connections. The roles/iap.tunnelResourceAccessor IAM role grants permission to create tunnels, and firewall rules must allow ingress from the 35.235.240.0/20 CIDR range (Google’s IAP forwarding IP pool). In a production infrastructure setup, this is the standard mechanism for operator access to cluster nodes.

OAuth 2.0 Backend

IAP acts as an OAuth 2.0 authorization server, managing the consent flow and token lifecycle transparently. When a user accesses an IAP-protected resource, IAP issues a short-lived credential scoped to that specific backend service. The backend can extract the user’s verified email and subject identifier from the JWT without needing its own authentication system, enabling clean separation between identity verification and application logic.

How It Works

Enabling IAP on a GKE Backend Service

In the infrastructure project, IAP is enabled declaratively on backend services that front GKE-hosted admin interfaces. The Pulumi code configures the backend service with an IAP block containing an OAuth2 client ID and secret, which are stored in Google Secret Manager and referenced at deploy time.

package infra

import (
    "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/compute"
    "github.com/pulumi/pulumi-gcp/sdk/v7/go/gcp/iap"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func createIAPBackendService(ctx *pulumi.Context) error {
    // Create the backend service with IAP enabled
    backendSvc, err := compute.NewBackendService(ctx, "admin-backend", &compute.BackendServiceArgs{
        Protocol:            pulumi.String("HTTP"),
        PortName:            pulumi.String("http"),
        TimeoutSec:          pulumi.Int(30),
        HealthChecks:        pulumi.String(healthCheck.ID()),
        LoadBalancingScheme: pulumi.String("EXTERNAL_MANAGED"),
        Iap: &compute.BackendServiceIapArgs{
            Oauth2ClientId:     pulumi.String(oauthClientId),
            Oauth2ClientSecret: pulumi.String(oauthClientSecret),
        },
        Backends: compute.BackendServiceBackendArray{
            &compute.BackendServiceBackendArgs{
                Group:          neg.SelfLink,
                BalancingMode:  pulumi.String("RATE"),
                MaxRatePerEndpoint: pulumi.Float64(100),
            },
        },
    })
    if err != nil {
        return err
    }

    ctx.Export("adminBackendId", backendSvc.ID())
    return nil
}

IAP Tunnel Configuration for SSH Replacement

Rather than exposing SSH ports to the internet or maintaining bastion hosts, the infrastructure project configures firewall rules that only permit ingress from Google’s IAP forwarding range. Operators use gcloud compute ssh --tunnel-through-iap to reach instances, and the IAP service handles authentication transparently.

// Firewall rule allowing IAP tunnel traffic only
func createIAPFirewallRule(ctx *pulumi.Context, network pulumi.StringInput) error {
    _, err := compute.NewFirewall(ctx, "allow-iap-tunnel", &compute.FirewallArgs{
        Network:   network,
        Direction: pulumi.String("INGRESS"),
        Priority:  pulumi.Int(1000),
        Allows: compute.FirewallAllowArray{
            &compute.FirewallAllowArgs{
                Protocol: pulumi.String("tcp"),
                Ports: pulumi.StringArray{
                    pulumi.String("22"),   // SSH
                    pulumi.String("3389"), // RDP
                },
            },
        },
        // Only allow traffic from Google’s IAP forwarding range
        SourceRanges: pulumi.StringArray{
            pulumi.String("35.235.240.0/20"),
        },
        TargetTags: pulumi.StringArray{
            pulumi.String("allow-iap-tunnel"),
        },
    })
    return err
}

// Usage from the command line (no VPN or bastion required):
// gcloud compute ssh instance-name --tunnel-through-iap --zone us-central1-a
// gcloud compute start-iap-tunnel instance-name 5432 --local-host-port=localhost:5432

Group-Based IAM Authorization

Access to IAP-protected resources is managed through Google Groups rather than individual user accounts. This pattern, used extensively in the infrastructure project, allows team membership changes to propagate instantly without modifying infrastructure code. Each environment has dedicated groups for operators, developers, and read-only viewers.

// IAM binding granting a Google Group access to IAP-protected web resources
func createIAPWebBinding(ctx *pulumi.Context, backendSvc *compute.BackendService) error {
    _, err := iap.NewWebBackendServiceIamBinding(ctx, "admin-access", &iap.WebBackendServiceIamBindingArgs{
        WebBackendService: backendSvc.Name,
        Role:              pulumi.String("roles/iap.httpsResourceAccessor"),
        Members: pulumi.StringArray{
            pulumi.String("group:platform-admins@example.com"),
            pulumi.String("group:sre-oncall@example.com"),
        },
    })
    return err
}

// IAM binding for IAP tunnel access to compute instances
func createIAPTunnelBinding(ctx *pulumi.Context, project string) error {
    _, err := iap.NewTunnelInstanceIAMBinding(ctx, "tunnel-access", &iap.TunnelInstanceIAMBindingArgs{
        Project: pulumi.String(project),
        Role:    pulumi.String("roles/iap.tunnelResourceAccessor"),
        Members: pulumi.StringArray{
            pulumi.String("group:infrastructure-ops@example.com"),
        },
    })
    return err
}

Implementation Patterns

Staged Rollout

Enabling IAP on existing backend services requires careful rollout to avoid disrupting active users. A production approach uses a three-phase strategy:

  1. Audit mode: Enable IAP in permissive mode with Cloud Audit Logs capturing all access decisions. Analyze logs to identify service accounts, automated clients, and users who would be affected.
  2. Soft enforcement: Enable IAP with broad group-based access, granting all authenticated organization members access. Monitor for access denials and adjust IAM bindings.
  3. Full enforcement: Restrict IAM bindings to specific groups and enable context-aware access levels. Remove overly broad bindings and verify all legitimate access paths are preserved.

Group-Based Access Policies

Rather than binding individual users to IAP resources, the infrastructure project uses a layered Google Groups structure:

Group membership is managed through an identity provider, and changes propagate to IAP within minutes. This eliminates the toil of updating Pulumi code for routine access changes.

Audit Logging and Compliance

Every IAP access decision generates a Cloud Audit Log entry containing the user’s identity, the resource accessed, the access decision (allow or deny), and the policy that governed the decision. In a production infrastructure setup, these logs are exported to BigQuery for long-term retention and to Cloud Monitoring for real-time alerting on anomalous access patterns. A compliance reporting tool can aggregate these logs to produce reports showing who accessed what, when, and from where.

Practical Applications

Securing an Admin Interface

A database cluster typically exposes an administrative HTTP interface for configuration changes, schema management, and operational commands. In a production setup, this interface is deployed behind a GKE Ingress with IAP enabled. Only members of the platform-admins group can access it, and every request is logged with the authenticated user’s identity. This replaces an earlier pattern where the admin interface was accessible only through a bastion host with shared SSH keys — a significant security and auditability improvement.

Database Access via IAP Tunnels

Operators needing direct access to Cloud SQL instances or database cluster nodes use IAP TCP forwarding to establish secure tunnels. Instead of whitelisting IP addresses or maintaining a VPN, the operator runs gcloud compute start-iap-tunnel to forward a local port to the database port on the target instance. IAP verifies their identity, checks their group membership against the tunnelResourceAccessor binding, and establishes an encrypted tunnel. All tunnel creation events are logged for audit.

Readiness Report Access Control

A cluster health and deployment readiness dashboard can be served as a static web application behind an IAP-protected load balancer. Different teams see different data based on their group membership, with IAP providing the authentication layer and the application reading the x-goog-iap-jwt-assertion header to determine authorization scope.

Common Pitfalls

Further Reading