Skip to content

[3/3] Self-mint IAP tokens for Oz runners via Workload Identity Federation#13624

Draft
IsaiahWitzke wants to merge 2 commits into
masterfrom
iw/oz-iap-native-mint
Draft

[3/3] Self-mint IAP tokens for Oz runners via Workload Identity Federation#13624
IsaiahWitzke wants to merge 2 commits into
masterfrom
iw/oz-iap-native-mint

Conversation

@IsaiahWitzke

@IsaiahWitzke IsaiahWitzke commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Gives IapManager a runner-context refresh path so sandboxed Oz runners self-mint an IAP-valid ID token via Workload Identity Federation, and removes the old server-injected WARP_IAP_TOKEN path from the client entirely.

Why

After IAP was enabled on staging, sandboxed runners 403 at the proxy:

403 Forbidden: Access denied. For user warp-server@warp-server-staging.iam.gserviceaccount.com

warp-server minted the injected token as its own service-account identity (MintIAPTokenForContaineridtoken.NewTokenSource), which isn't an authorized IAP member — so the injected token 403s and offers no working fallback. It also landed in the terminal EnvInjected state, which never refreshed (REMOTE-1370). The runner container has no gcloud/ADC, so the existing gcloud path can't run there either.

How

New fetch_iap_token_via_wif mirrors the REV-1599 GEAP mint chain, but ends in an ID token for IAP:

  1. Warp OIDC JWT via issue_task_identity_token (audience = the oz-agents WIF provider resource name)
  2. GCP STS token exchange → federated access token
  3. IAM Credentials generateIdToken impersonating iap-access@ (audience = the IAP OAuth client ID) — iap-access@ is an authorized IAP member

The result flows through the existing IapManager lifecycle (proactive refresh before exp, reactive on 403). All native HTTP — no gcloud.

Also removes the WARP_IAP_TOKEN / EnvInjected handling from the client (state variant, seed, challenge/refresh special-cases, settings-page arm), since that token is the broken warp-server@ identity and provided no working fallback. Runner IAP now depends entirely on the WIF mint.

Key details:

  • IapConfig gains federation_audience (the WIF provider resource name). The private staging channel config must populate it for this to activate (same overlay that sets audiences/service_account_email); OSS production() leaves IAP None.
  • Gated on runner context (ambient-agent run id), so local staging clients keep using the gcloud impersonation path unchanged.
  • Identity-token minting is abstracted behind IapIdentityTokenMinter (impl in the app crate over the managed-secrets client) so warp_server_client doesn't depend on the managed-secrets stack.

Dependencies / rollout

  • Requires the warp-terraform grant giving the staging Oz Warp-team WIF principalSet serviceAccountTokenCreator on iap-access@ (separate PR).
  • Requires the private channel config to set federation_audience.
  • warp-server should stop injecting WARP_IAP_TOKEN (separate change); the client already ignores it as of this PR.

Testing

  • cargo test -p warp_server_client --lib iap — 18 passing (WIF request/response serde, camelCase includeEmail, expiry parsing).
  • cargo check -p warp_server_client and cargo check -p warp pass; cargo clippy -p warp_server_client --all-targets clean.
  • Not exercised end-to-end (needs the terraform grant + config value live).

Conversation

Co-Authored-By: Oz oz-agent@warp.dev

Sandboxed Oz runners authenticate to staging through IAP using a
server-injected WARP_IAP_TOKEN minted as the warp-server service account.
That identity isn't an authorized IAP member (403s), and the injected token
lands in the terminal EnvInjected state which never refreshes (REMOTE-1370).

This gives IapManager a runner-context refresh path that self-mints an
IAP-valid ID token via WIF, mirroring the REV-1599 GEAP mint chain:
  1. Warp OIDC JWT (audience = the oz-agents WIF provider resource name)
  2. GCP STS token exchange -> federated access token
  3. IAM Credentials generateIdToken impersonating iap-access@ (audience =
     the IAP OAuth client ID), which IS an authorized IAP member

The minted token flows through the existing IapManager lifecycle (proactive
refresh before expiry, reactive refresh on 403), closing the no-refresh gap.
Native HTTP is used throughout since the runner image ships without gcloud.

- IapConfig gains federation_audience (the WIF provider resource name); the
  private staging channel config must populate it for this to activate.
- The mint is gated on runner context (ambient-agent run id), so local
  staging clients keep using the gcloud impersonation path unchanged.
- The env-injected token is kept as an initial seed and served until the
  first WIF mint lands, then refreshed like any managed token.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Jul 13, 2026
@oz-for-oss

oz-for-oss Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@IsaiahWitzke

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR adds a runner-context IAP refresh path that mints tokens via Workload Identity Federation and wires the app-layer managed-secrets minter into IapManager. No approved spec context was provided, so this review focuses on the implementation and security posture of the diff.

Concerns

  • The new app-layer minter uses FutureExt::boxed(), which requires a Send future even though this module is compiled for wasm where ManagedSecretsClient methods are async_trait(?Send).
  • The IAP challenge path now suppresses the existing stale env-token warning whenever a managed minter exists, even when the channel config is missing federation_audience and the refresh path will still no-op.

Verdict

Found: 0 critical, 2 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

.await?;
Ok(token.token)
}
.boxed()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This module is compiled on wasm, but .boxed() requires the async block to be Send; ManagedSecretsClient::issue_task_identity_token is async_trait(?Send) on wasm, so wasm builds can fail here. Use a wasm-local boxed future (or cfg-gate this module/impl to native) so the return matches the target-specific BoxFuture alias.

Comment thread crates/warp_server_client/src/iap.rs Outdated
if matches!(state.state(), IapCredentialsState::EnvInjected { .. }) {
// An env-injected token with no managed-mint fallback cannot be
// refreshed (no gcloud in the runner, no WIF configured).
if self.managed_mint.is_none()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This suppresses the stale env-token warning whenever a runner minter exists, even if federation_audience is absent; start_refresh then hits the EnvInjected no-op below, so a missing rollout config silently keeps failing IAP challenges. Treat missing federation config as no refresh path here, or emit a specific warning before returning.

@IsaiahWitzke IsaiahWitzke marked this pull request as draft July 13, 2026 00:23
@IsaiahWitzke IsaiahWitzke changed the title Self-mint IAP tokens for Oz runners via Workload Identity Federation [3/3] Self-mint IAP tokens for Oz runners via Workload Identity Federation Jul 13, 2026
The env-injected token is minted as the warp-server service account, which
isn't an authorized IAP member, so it 403s today and provides no working
fallback. With WIF self-minting in place there's no reason to keep it.

Drops the EnvInjected state variant and all its handling (seed, get_cached,
previous_token, challenge/refresh special-cases, and the settings-page
status arm). Runner IAP now depends entirely on the WIF mint.

Co-Authored-By: Oz <oz-agent@warp.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant