Skip to content

Investigate swapping AuthBridge in as the egress data plane for dam-agents/dam (kagenti/platform) #529

Description

@huang195

Summary

Investigate swapping AuthBridge in as the egress data plane for dam-agents/dam — which is internally the Go module github.com/kagenti/platform, i.e. a Kagenti sister project. DAM's egress design turns out to be nearly isomorphic to AuthBridge's proxy-sidecar model, just built from stock Envoy instead of authbridge-proxy. This issue captures the architecture comparison and a phased swap-in plan.

What DAM does today (verified by source read)

  • Paired-pod, not sidecar. A Go controller (packages/controller, the only Go component; reconciles Agent/Fork CRDs) provisions, per agent: an agent StatefulSet + a separate -gateway StatefulSet running stock Envoy (gateway.go, envoy.go).
  • Egress capture = iptables + NetworkPolicy. An egress-lockdown init container DROPs all agent egress except loopback/ESTABLISHED/gateway-IP (iptables_init.go:64-67), plus a namespace default-deny + allow-only-gateway NetworkPolicy (network_policy.go:33-51). The agent opts out of Istio ambient (dataplane-mode: none) so NetworkPolicy sees real destinations. Agent env: HTTPS_PROXY=<gateway IP>, NODE_EXTRA_CA_CERTS=/etc/platform/ca/ca.crt (resources.go:159-180); no SA token.
  • The gateway Envoy does three things:
    1. CONNECT forward proxy + tls_inspector SNI.
    2. ext_authz callout to a TypeScript gRPC gate (apps/ext-authz/grpc.ts, ext-authz-gate.ts) — allow/deny plus a synchronous human-in-the-loop hold (holds the request up to holdSeconds for a Slack verdict, fail-closed). Allow/deny only; no header mutation.
    3. Per-host TLS MITM with a cert-manager leaf, then Envoy's native credential_injector filter pulling the real secret from an SDS file and writing it into Authorization/x-api-key (envoy.go:658-732). Credential Secrets mount only into the gateway pod — the agent never holds them.
  • Identity: cert-manager leaf certs + Istio-SPIFFE SA principals + Keycloak JWT. No SPIRE. No OBO/token-exchange — static API keys / OAuth access tokens injected verbatim (oauth-flow.ts is plain auth-code, not RFC-8693).

Mapping to AuthBridge

Concern DAM AuthBridge Swap difficulty
Egress capture iptables init + NetworkPolicy + HTTPS_PROXY proxy-init iptables + HTTP_PROXY reuse DAM's as-is
Data plane stock Envoy (paired pod) authbridge-proxy (Go) the swap point
CONNECT + SNI Envoy tls_inspector forward proxy parity
TLS interception Envoy terminate w/ cert-manager leaf tlsbridge: forge leaf from CA, re-originate SAN-verified parity
Credential injection Envoy credential_injector (static, SDS) pipeline (OBO/token-exchange) mismatch — static-inject plugin
Policy ext_authz → TS gate (allow/deny + HITL) pipeline allow/deny/reject (no HITL) gap — HITL
Cred isolation Secrets only in gateway pod OBO: agent never holds creds parity (same property)
Trust injection cert-manager CA → NODE_EXTRA_CA_CERTS cert-manager/ephemeral CA → trust env parity
Identity cert-manager + Istio-SPIFFE + Keycloak SPIRE/SPIFFE divergent (tlsbridge now SPIRE-decoupled, #523)

How the swap works

The clean cut point is one container. DAM's controller builds the gateway StatefulSet in gateway.go:BuildGatewayStatefulSetenvoy.go:envoyContainer(). Replace that container + its bootstrap renderer with an authbridge-proxy container + config. Everything around it is data-plane-agnostic and stays: the paired-pod model, NetworkPolicy, iptables lockdown, cert-manager leaf, the agent's HTTPS_PROXY/NODE_EXTRA_CA_CERTS env, warm-pools/hibernation. That is what makes this tractable — the hard k8s scaffolding is already built and doesn't care what the gateway binary is.

AuthBridge's forward proxy is a functional superset of DAM's Envoy on the data path (CONNECT, SNI, TLS bridge, re-origination all parity), so the real work is the two mismatches:

  1. Static credential injection. DAM injects static keys / OAuth access tokens; AuthBridge is OBO-centric. Add a small static-header-inject plugin to AuthBridge's pipeline, fed from the same mounted Secret. Do not migrate DAM's connections to OBO in phase 1 (that touches DAM's OAuth flow + UI).
  2. HITL hold. DAM's differentiator; AuthBridge lacks it. Cheapest path: have AuthBridge's pipeline make an ext_authz-style callout to DAM's existing TS gate service for the verdict — leaving DAM's Slack-approval UX untouched.

Bonus simplification: DAM cryptographically pins each per-instance ext-authz Service to the calling SA because policy (TS) is split from the Envoy data plane. If AuthBridge runs policy in-process, that pinning apparatus can be dropped — unless HITL is kept via callout, in which case only that one hop needs securing.

Proposed phases

  • Phase 1 — drop-in data-plane swap (contained): replace envoyContainer() with authbridge-proxy; add a static-cred-inject plugin; bridge HITL via a callout to DAM's existing gate. Reuse all surrounding scaffolding.
  • Phase 2 — unify identity: adopt OBO + SPIRE to align with the Kagenti identity stack. Bigger — connection-model/OAuth/UI churn + adds SPIRE to DAM's deploy.

Caveat / trade-off

DAM today rides 100% upstream Envoy filters (credential_injector, ext_authz, tls_inspector, SDS) with zero custom data-plane code. Swapping in authbridge-proxy trades that for custom Go: you gain OBO/redaction/richer pipeline and a single Kagenti-consistent data plane, but take on maintaining proxy code where DAM currently gets it free from Envoy. Decision-owners should weigh this explicitly.

Open questions

  • Does DAM's roadmap actually want OBO, or is static injection a deliberate choice? (Determines whether Phase 2 is desirable at all.)
  • Can AuthBridge's pipeline cleanly express the synchronous HITL hold semantics (timeout, fail-closed) via a callout, or does HITL need to become a first-class pipeline primitive?
  • Identity: keep DAM's cert-manager + Istio-SPIFFE for the gateway→upstream/leaf certs, or move to SPIRE? tlsbridge is now SPIRE-decoupled (Fix: Run the TLS bridge without SPIRE (need-driven SPIFFE provider; drop in-process trust self-check) #523), so Phase 1 can run without SPIRE.
  • Who owns the decision and where does cross-repo work get tracked (kagenti/platform vs kagenti-extensions)?

References


Assisted-By: Claude Code

Metadata

Metadata

Assignees

Labels

IdentityIssues related to agent identity attestation, SPIFFE, and Authorization bearer tokensenhancementNew feature or requeststale

Type

No type

Projects

Status
New/ToDo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions