#2047

Sidecar credential swap: client identity, upstream credential pools, lane credentials and owner-named reviews

Draftmatheusfranciscowants to merge intofrom

📝 Description

A sidecar lane can now terminate the client's login and log in upstream with a credential the client never holds. The lane names a verifier for the client leg (identity: none on a unix socket, a Kubernetes projected token, a control plane credential, a SPIFFE JWT-SVID), a pool of upstream credentials drawn from process-wide sources (secret_files, gcp_impersonation, x509, spiffe, kerberos) and the rules that pick one from the identity and the lane context. The relay runs a per-protocol login driver before the pumps (Postgres, MySQL, MongoDB, SQL Server; Oracle relays pass-through users only), ClickHouse swaps the Hello in its codec, HTTP and gRPC swap the authorization per request. A refused login is audited as a one-event session under the claimed user before any upstream byte.

The control plane learns the person: a sidecar row carries owner_email (written by the GitOps reconciler for Kumo pods), reviews are filed under the sidecar's principal or the owner instead of a placeholder, and the requester is DMed on Slack. Users mint lane credentials after SSO when a grant names them or their group; the plane serves the hashes and SCRAM material to that sidecar as credential_set, which the heartbeat swaps in place, so a mint or a revocation is in force at the next login with no restart.

Every new configuration key is cap:-gated: the plane never serves one to a build that did not declare it, and a lane with none of them is byte-identical to today.

Design: sidecar/README.md "Client identity and upstream credentials"; reconciler contract in deploy/helm-chart/chart/sidecar/RECONCILER.md.

Depends on hoophq/libhoop#140 (login toolkits). This branch pins its branch commit a59ec27; merge #140 first, then re-pin the squash commit here.

📣 User-facing impact

A sidecar can give a person access to a database or an API without handing them its credential: they log in with a key hoop issued, the sidecar logs in upstream for them, and the session, the hold and the Slack message name the person.

🔗 Related Issue

Fixes #

🚀 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🎨 Style/UI update
  • ♻️ Code refactor
  • ⚡ Performance improvement
  • ✅ Test update
  • 🔧 Build configuration change
  • 🧹 Chore

📋 Changes Made

  • sidecar/proxy: LoginFunc/RegisterLogin hook and Config.Authenticate; the relay adopts the verified identity before session_start, audits refusals as one-event sessions, maps Postgres cancel requests through a registry. Drivers: login_postgres.go (cleartext on unix/TLS or SCRAM-SHA-256 server; SCRAM, MD5, cleartext, cert or GSS upstream), login_mysql.go (on the generalized RSA auth bridge), login_mongodb.go (hello answered locally, SCRAM server, SCRAM or X.509 upstream), login_mssql.go (login-only TLS terminated, LOGIN7 rewritten or SSPI via the Kerberos source; TDS 8 strict refused with the setting named), login_oracle.go (pass-through users relayed, every other login refused with ORA-01017: O5LOGON never carries the client's secret, the verifier shape a follow-up needs is on the refusal).
  • sidecar/daemon: identity, credentials (sources, pool, select rules or an OPA credential field), context, passthrough_users, propagate; credential_set swapped on every heartbeat; verifiers none, kubernetes (OIDC discovery + JWKS, aud/exp/iss, pod UID pin), hoop_credential (constant-time hash match, SCRAM lookup); sources secret_files, gcp_impersonation, x509; registries for nested-module verifiers and sources; capabilities header entries; validator matrix; a built-in guardrail on pooled SQL lanes refusing SET ROLE, RESET ROLE, SET SESSION AUTHORIZATION, EXECUTE AS, REVERT; HOOP_SIDECAR_IDENTITY_TYPE=spiffe for the plane leg; schema regenerated; README sections.
  • sidecar/codec/http: per-request Swap of Authorization (bearer or basic) and Impersonate-User/-Group from the identity; client-sent Impersonate-* always dropped on a swap lane. sidecar/codec/clickhouse: the Hello swap and the login identity to the gate. sidecar/daemon/grpc.go: per-RPC authorization through libhoop's UpstreamAuthorization hook.
  • sidecar/identity/spiffe, sidecar/identity/krb5: nested modules (go-spiffe, gokrb5), linked into hoop-inspect only, added to go.work.
  • gateway: migration 000145 sidecars.owner_email; review body principal; newSidecarReview resolves principal → owner → placeholder and the requester's linked Slack id; DM on hold, approval, denial; owner on anonymous sidecar sessions; flag experimental.sidecar_review_owner (default on). Migrations 000146 (connection_credentials.scram_sha256) and 000147 (sidecar_lane_grants); grants CRUD, mint/list/revoke routes, SCRAM-SHA-256 material at mint, credential_set composed per sidecar; analytics events; OpenAPI regenerated; hoop sidecar credential <sidecar>/<lane>.
  • webapp_v2: owner field on the sidecar settings page.
  • deploy: sidecar chart controlPlane.identity: spiffe (Workload API socket via CSI or hostPath), credentials.secretMounts, kerberos.ccache, downward API POD_UID/POD_NAME/POD_NAMESPACE; RECONCILER.md; envoy-stack credentials/ overlay with a fake IAM credentials endpoint; sidecar/scripts/dev/credentials-check.sh.
  • libhoop pinned to a59ec27 (interim).

🧪 Testing

Test Configuration:

  • Browser(s): n/a
  • OS: macOS 15 (arm64), Go 1.26, CGO_ENABLED=0

Tests performed:

  • Unit tests pass

  • Integration tests pass

  • Manual testing completed

  • sidecar root module and every nested module (cmd, config/yaml, pii/alcatraz, store/sqlite, analyzer/vertex, descriptors/gcs, mcp, ica, lexer/conformance, codec/wasm, identity/spiffe, identity/krb5): go test ./... green. go mod tidy leaves sidecar/go.mod unchanged (one-dependency invariant).

  • gateway: models, services, api/sidecar, api/sidecarcredentials, api/review, slack, transport/plugins/slack green when run one package at a time (the PGlite suites time out when run concurrently on one machine; CI shards them); go vet ./... clean.

  • webapp_v2: npm run lint 0 errors, npm run build ok.

  • Fuzz, committed seed corpora, no crashers: FuzzLogin (pgwire), FuzzMySQLLogin, FuzzMongoLogin, FuzzMSSQLLogin, FuzzOracleLogin in sidecar/proxy; the libhoop toolkits' own targets in #140.

  • Manual: real mongod:7 + mongosh through proxy.Server (swap, wrong key, pass-through, monitor connections, OP_QUERY and OP_MSG handshakes); real go-mssqldb login through the relay; hoop-inspect -validate accepts identity: {type: none} on unix with a secret_files pool and refuses the same on tcp; helm lint and helm template for default, kubernetes, spiffe, spiffe+hostPath, and mounts value sets.

  • Not run here: make test-oss, make test-sidecar-compat, make test-sidecar-e2e (CI).

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

📄 Additional Notes

  • Feature flag: experimental.sidecar_review_owner gates the gateway behaviour (reviews under the person, Slack DM); off restores the placeholder. The sidecar keys need no flag: they are cap:-gated and absent keys mean today's behaviour.
  • Gateway ↔ agent contract untouched. Sidecar ↔ plane: additive keys only, all with cap:; test-sidecar-compat is the check to watch.
  • Oracle ships pass-through only; MSSQL integrated (Kerberos) logins relay only when passthrough_users lists integrated; MongoDB upstream SCRAM authenticates on admin.
  • Commits are unsigned; the author signs before marking ready.
  • Review focus: sidecar/proxy/login.go (the hook and the adopt/start order), sidecar/daemon/select.go (verify → select → source), gateway/api/sidecar/reviews.go (person resolution), sidecar/codec/http/credential.go (header swap and Impersonate-* stripping).

Activity

43
qodo-code-review[bot]4d ago
sandromello4d ago

✅ Build Completed with Success, Version=2047.0.0-g25c9843

qodo-code-review[bot]4d ago
qodo-code-review[bot]4d ago
sandromello4d ago

✅ Build Completed with Success, Version=2047.0.0-gbead962

qodo-code-review[bot]3d ago

This pull request is a draft

Merging is unavailable until it is marked ready for review.