Skip to content

feat: workload identity federation auth for gcp_pubsub destinations - #1145

Draft
alexluong wants to merge 9 commits into
mainfrom
feat/gcp-pubsub-workload-identity
Draft

alexluong wants to merge 9 commits into
mainfrom
feat/gcp-pubsub-workload-identity

Conversation

@alexluong

@alexluong alexluong commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Adds keyless authentication for GCP Pub/Sub destinations through Workload Identity Federation. Opt-in per Outpost instance: with it off (the default), the API, destination schema and behavior are unchanged.

A GCP Pub/Sub destination needs a service account key today. GCP organizations created since May 2024 block key creation by default, and some users can't get an exception. With Workload Identity Federation, Outpost signs a short-lived token for the tenant, the user's GCP project trusts Outpost's issuer, and Google exchanges the token for a short-lived access token. No key is created or stored.

How it works

  1. The operator sets WORKLOAD_IDENTITY_ISSUER (a public HTTPS URL) and a signing key. Outpost serves the discovery document and JWKS under /workload-identity.
  2. The user creates a workload identity pool + OIDC provider that trusts that issuer and only their tenant's subject (tenant:<tenant_id>), and grants that subject roles/pubsub.publisher on the topic. The portal shows the issuer and subject to copy.
  3. The destination stores the provider's resource name (and optionally a service account to impersonate). On delivery, Outpost signs a token with sub = tenant:<tenant_id>, exchanges it at Google STS, and publishes.

A tenant only ever gets a token with its own subject, so Google rejects it at another tenant's provider.

feature off (default) feature on
GCP Pub/Sub schema same as today auth_method select (service_account_key default, workload_identity) + fields with visible_when
Existing destinations unchanged unchanged (no auth_method = key)
auth_method=workload_identity rejected accepted
credentials.service_account_json in responses unchanged "" for workload identity destinations (stays required in OpenAPI so released SDKs still parse)
Tenant IDs any workload identity destinations need ^[A-Za-z0-9._:@-]+$, ≤120 chars (they go into the subject and IAM principal)
Issuer routes, GET /tenants/:tenant_id/workload-identity 404 served

Operators who render destination forms themselves need to support visible_when and show the issuer/subject, only if they turn the feature on. Turning it off again makes workload identity destinations fail delivery until switched to a key (documented).

Changes

  1. Operator config (internal/config, internal/workloadidentity): issuer, signing_key / signing_key_file, verification_keys / verification_keys_file (extra public keys for rotation). RSA ≥2048 (RS256) or EC P-256 (ES256); kid = RFC 7638 thumbprint. Partly configured = startup error.
  2. Conditional destination fields (internal/destregistry): new visible_when field property ({key, values[]}): a field applies only while another field holds one of those values. required applies to visible fields only; a value in a hidden field is rejected. Generic, usable by other providers.
  3. GCP Pub/Sub provider: auth_method select and credentials workload_identity_provider, service_account_email. Token exchange through golang.org/x/oauth2/google/externalaccount with a supplier that mints the tenant's token. The emulator endpoint keeps precedence.
  4. API: GET /tenants/:tenant_id/workload-identity returns {issuer, subject} for UIs.
  5. Portal: renders visible_when, shows issuer and subject when Workload Identity Federation is selected, clears the other method's credentials on switch.
  6. Docs: configuration reference, GCP Pub/Sub destination page, visible_when in the custom UI guide and OpenAPI. The portal setup guide for GCP Pub/Sub is rewritten to setup steps for both methods, like the other destinations' guides (334 → ~85 lines); its workload identity section is dropped when the feature is off.

Other improvements:

  • CopyButton gets type="button"; inside a form it submitted the form.
Verification
  • Feature off: tests pin that the destination-types response matches main, auth_method=workload_identity is rejected, and the new routes return 404.
  • Unit, apirouter and e2e tests for config, token claims against the published key (RSA + EC), visible_when validation, create/update/switch, and token exchange against a fake STS.
  • Manually against a real GCP project: direct grant, service account impersonation, another tenant rejected by the provider condition, missing IAM grant, key method unchanged, token reuse (2 exchanges for 21 publishes), Google fetching keys from a public issuer, key rotation, feature off, portal end to end.

Not in this PR

  • SDK regeneration.
  • Making service_account_json optional in the spec (breaking for typed SDKs).
  • Workload identity for other destination types (the same issuer works for AWS AssumeRoleWithWebIdentity and Azure federated credentials).

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant