Commit 0ef5beb7 authored by xuwang's avatar xuwang
Browse files

doc(gke-iap-auth): add identity propagation + sign-in experience sections



Document the IAP-injected identity headers and JWT trust model, that groups are
not passed to the app, and why the Google sign-in page appears / cannot be
skipped without external-identity federation.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 46c0b211
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -53,6 +53,29 @@ No custom OAuth client, no Vault secret, and no per-namespace K8s Secret to
maintain. A managed backend shows `oauth2ClientId: ''` (empty) and the IAP
sign-in redirect uses Google's IAP project client (`369001918367-…`).

## Sign-in experience — why the Google page appears

When the org's domain is a Google Workspace domain (e.g. `stanford.edu`), signing
in "with Google" using a domain account **already federates to the org's SSO/IdP**
(e.g. SAML/Shibboleth) behind the scenes. The `accounts.google.com` screen is
Google's account chooser/sign-in; after picking the account, Google redirects to
the org IdP and back. If the browser already has an active domain Google session,
Google skips the chooser — the redirect is near-instant.

**Can the Google page be skipped (go straight to the org IdP)?** Not with standard
IAP. IAP authenticates *Google* identities, so the OAuth flow always goes through
`accounts.google.com`; IAP does not expose OAuth params (`hd`, `login_hint`) to
force-skip the chooser.

Landing directly on the org IdP requires moving off standard IAP auth to
**external identities (Identity Platform / GCIP)** or **Workforce Identity
Federation**, wiring the org's SAML/OIDC IdP with a custom sign-in page.
Note: both replace Google identities with external ones, which **breaks the
per-app group model**: `iap-authz` grants the role to Google groups, and external
identities don't key off Google IAM groups — authorization would need redesign.
For Google-backed domains the standard flow (the Google step *is* the org SSO,
just fronted by Google) is the recommended path.

## Authorization — per app, via `make iap-authz`

Authorization is **not** a project-wide binding. GCP IAM is *additive*, so a
@@ -83,6 +106,38 @@ Edit the list, run `make iap-authz`, and the live policy matches. An **empty**
`IAP_MEMBERS` is rejected by `iap-authz` (so you never wipe access by accident) —
use `iap-authz-revoke` to remove access intentionally.

## Identity propagation to the app

IAP passes the authenticated identity to the backend as HTTP request headers it
injects on every request (and strips any client-supplied copies):

| Header | Example | Notes |
|---|---|---|
| `X-Goog-Authenticated-User-Email` | `accounts.google.com:alice@example.edu` | convenience; strip the `accounts.google.com:` prefix |
| `X-Goog-Authenticated-User-Id` | `accounts.google.com:1234567890` | stable numeric Google user id |
| `X-Goog-IAP-JWT-Assertion` | signed ES256 JWT | verify this for defense-in-depth |

**Trust model.** The plain email/id headers are safe only because the backend is
unreachable except through the IAP-protected load balancer. In case the backend
can be reached directly, verify the JWT assertion:
* signature against Google's keys (`https://www.gstatic.com/iap/verify/public_key`)
* `iss = https://cloud.google.com/iap`
* `aud = /projects/<PROJECT_NUMBER>/global/backendServices/<BACKEND_SERVICE_ID>`
* `exp` / `iat` fresh

The JWT carries the verified `sub`, `email`, `iss`, `aud`, `exp`.

> **Groups are NOT passed to the app.** There is no `…-User-Groups` header and the
> JWT contains no group claims. Group-based access is resolved and enforced at the
> IAP layer — `make iap-authz` grants the role to a group, IAP checks membership,
> and the app only ever receives the individual user's email/id. If the app needs
> group/role info (e.g. admin vs read-only UI), it must look it up itself via the
> Cloud Identity Groups API / Admin SDK Directory API, or keep its own mapping.

A typical app reads `X-Goog-Authenticated-User-Email` to identify the user (e.g.
nginx forwards the header to the app, which maps it to a session); the group gate
lives entirely in IAP.

## Enable IAP for a new app

1. **BackendConfig**: `spec.iap.enabled: true` (no `oauthclientCredentials`), and