Skip to content

security: verify ALB OIDC JWT signatures; org-scope pg_stat_activity - #1155

Merged
fuziontech merged 1 commit into
mainfrom
sec/admin-sso-and-pgstat
Sep 4, 2026
Merged

security: verify ALB OIDC JWT signatures; org-scope pg_stat_activity#1155
fuziontech merged 1 commit into
mainfrom
sec/admin-sso-and-pgstat

Conversation

@fuziontech

Copy link
Copy Markdown
Member

Summary

Two fixes from a security review of the managed-warehouse query layer:

  • Admin SSO header forgery (High). The admin API trusted the ALB-injected X-Amzn-Oidc-Data header without verifying its signature, and accepted the unsigned X-Amzn-Oidc-Identity header as a fallback. The control-plane pod is reachable without crossing the ALB (any pod in the namespace per k8s/networkpolicy.yaml, and kubectl port-forward bypasses NetworkPolicy entirely), so a forged header yielded viewer — or, with a known operator email, admin: org CRUD, tenant root password resets, service-credential mints, worker image pinning, user impersonation.
  • pg_stat_activity cross-tenant disclosure (High). On the multitenant control plane all orgs' connections register in one process-wide map; a full-power org principal (nil QueryAccessPolicy) received every row — other orgs' query text (with literal values), usernames, database names, and client IPs.

Changes

  • New controlplane/admin/alb_oidc.go: ALBOIDCVerifier checks the ES256 signature against the ALB's regional AWS public keys (public-keys.auth.elb.<region>.amazonaws.com/<kid>, cached per kid), plus exp, signer (must be an ALB ARN in the configured region), iss, and client claims. Algorithm pinned to ES256; kid charset-restricted before it enters a URL.
  • AuthMiddleware now takes the verifier. The unsigned X-Amzn-Oidc-Identity header is no longer consulted. Without DUCKGRES_ADMIN_SSO_ISSUER the SSO path is off entirely (fail closed): only bearer tokens authenticate; a startup warning is logged. Region falls back to DUCKGRES_AWS_REGION.
  • visiblePgStatActivityConns now always filters by orgID; project-scoped users keep the stricter per-username filter. Standalone mode is unchanged (empty orgIDs match).
  • e2e (tests/mw-dev/e2e/harness.sh): the forged-SSO test now asserts 401 for unsigned X-Amzn-Oidc-Data and for X-Amzn-Oidc-Identity.

Deployment ordering

The companion charts PR sets DUCKGRES_ADMIN_SSO_ISSUER / DUCKGRES_ADMIN_SSO_CLIENT_ID (derived from the existing adminUI.ingress.cognito values) on the control-plane Deployment. The charts change must be synced in each environment before this image rolls there — otherwise SSO login 401s (break-glass token login still works). Dev auto-deploys on merge, so merge the charts PR first and confirm Argo sync.

Testing

  • New unit tests: valid signed JWT accepted; wrong-key, tampered-payload, unsigned, expired, wrong-region-signer, wrong-iss, wrong-client all rejected; identity-only header rejected; nil verifier ignores SSO headers entirely.
  • pg_stat_activity: org root sees exactly its own org's connections; standalone sees all.
  • go test -tags kubernetes ./... green for all affected packages (tests/integration and tests/trino-ducklake-smoke fail identically on main in this sandbox — they need Docker services).

Rollback

Revert the image pin via the state pipeline; with the charts envs in place the old image simply ignores them.

Admin SSO: the admin API trusted the ALB-injected X-Amzn-Oidc-Data header
without verifying its signature, and accepted the unsigned
X-Amzn-Oidc-Identity header as a fallback. Anything that reaches the
control-plane pod without crossing the ALB (same-namespace pod, kubectl
port-forward) could forge an operator identity, including admin. The admin
layer now verifies the ES256 signature against the ALB's regional AWS
public-key endpoint and checks exp, signer, iss, and client claims. The
unsigned identity header is no longer consulted. SSO is configured with
DUCKGRES_ADMIN_SSO_ISSUER / _CLIENT_ID / _REGION; without the issuer env,
SSO headers are ignored and only bearer tokens authenticate.

pg_stat_activity: on the multitenant control plane, a full-power org
principal (nil QueryAccessPolicy) received every connection on the shared
process, including other orgs' query text, usernames, and client addresses.
Rows are now always scoped to the caller's org; project-scoped users keep
the stricter per-username filter. Standalone mode is unchanged (empty
orgIDs match).

The mw-dev e2e forged-SSO test now asserts 401 for unsigned headers.

Co-authored-by: Shelley <shelley@exe.dev>
@fuziontech
fuziontech requested a review from a team September 3, 2026 23:38
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Test Impact Plan

Deterministic summary of how this PR changes tests, CI runners, and coverage-risk signals.

Summary

Area Added Changed Deleted
Test files 1 4 0
E2E/journey files 0 0 0
Workflow files 0 0 0

Signals

  • Test cases: +6 / -0
  • Assertions: +24 / -3
  • Skips or known failures added: 0
  • Workflow continue-on-error added: 0
  • Workflow path filters added: 0
  • Test commands removed from justfile: 0
  • E2E/journey retry lines added: 0

Coverage risk: neutral or increased

No coverage-reduction warnings detected.

@fuziontech
fuziontech merged commit 717e63b into main Sep 4, 2026
35 checks passed
@fuziontech
fuziontech deleted the sec/admin-sso-and-pgstat branch September 4, 2026 00:15
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.

2 participants