Skip to content

Add static credential injection plugin to AuthBridge outbound pipeline #530

Description

@huang195

Summary

Add a static credential injection plugin to AuthBridge's outbound pipeline: inject a static secret (API key / bearer token) from a mounted Secret/file into a configured outbound header (or URL query param), as a first-class alternative to the existing OBO / RFC-8693 token-exchange flow.

Motivation

AuthBridge's credential story today is OBO-centric — the token-exchange plugin rewrites Authorization with a freshly-exchanged token (authlib/plugins/tokenexchange/plugin.go:702-703). But many downstream tools agents call don't do OAuth at all — they take a static API key (OpenAI, Anthropic, GitHub PATs, most SaaS APIs). For those, the value AuthBridge provides ("agent never holds the credential; the proxy injects it on egress") still applies, but there's no plugin to do it.

This is also a Phase-1 prerequisite for the DAM swap (#529): dam-agents/dam (internally kagenti/platform) injects static API keys / OAuth access tokens via Envoy's native credential_injector filter into Authorization: Apikey {value} / x-api-key, with some providers moving the value into a URL query param. To swap authbridge-proxy in for DAM's Envoy gateway, AuthBridge needs an equivalent static-injection capability.

Proposed design

Model a new authlib/plugins/staticcred plugin on the existing token-exchange plugin — it's a strict simplification (no IdP round-trip):

  • Interface: implement pipeline.Plugin (authlib/pipeline/plugin.go:6-11); OnRequest mutates pctx.Headers in place and returns Continue/Reject.
  • Config struct (decoded in Configure, DisallowUnknownFields like tokenexchange/plugin.go:366-372): header_name, value_prefix (e.g. Bearer , Apikey , or empty), secret_file (and/or secret_env), optional query_param.
  • Secret sourcing: reuse the shared helpers verbatim — config.ReadCredentialFile and config.WaitForCredentialFile (authlib/config/resolve.go:25,58), with Readier.Ready() gating /readyz until the mounted Secret lands (same pattern as token-exchange Init/pollCredentials/Ready).
  • Injection: pctx.Headers.Set(cfg.HeaderName, cfg.ValuePrefix + secret) + pctx.Record(...).
  • Registration: one plugins.RegisterPlugin("static-cred", factory) in init() (registry.go) + a side-effect import in the binary's plugin set. Enabled per-agent via pipelines.outbound.plugins[] PluginEntry{Name, Config} (config.go:225,245). No core changes for registration/config.

The plugin logic itself is small (~a token-exchange clone minus the exchange).

The load-bearing constraint (defines real scope)

Plugins set pctx.Headers, but the listeners decide what reaches the wire — and today every listener hardcodes propagating only Authorization, by diffing original-vs-new:

  • forwardproxy server.go:274 (capture), :322-324 (set) — and :324 force-rewraps the value as Bearer ..., which would mangle a raw API key or Apikey -scheme value.
  • reverseproxy server.go:224,247-248; extproc server.go:171/180,498/508,550/560; extauthz server.go:86,92.

So injection target determines effort:

Target Effort Notes
Authorization: Bearer <token> Trivial — works today, zero listener changes But the :324 Bearer re-wrap means only bearer scheme works
Authorization: Apikey <key> / raw Small-medium — must fix the forwardproxy :324 forced-Bearer re-wrap
Custom header (x-api-key) Medium — listeners don't propagate non-Authorization headers; needs a general "apply pctx.Headers mutation" path across all 4 listeners
URL query param Medium-large — proxy listeners have no outbound-URL-rewrite path today DAM does this via a follow-on Lua filter

The plugin is the easy part; generalizing listener header/query propagation is the bulk of the work and the main design decision to settle first. Note: full DAM parity (#529) needs the x-api-key + Apikey + query-param cases — i.e. it does require the listener generalization, not just the trivial Bearer path.

Scope / phasing

  • Phase 1: the plugin + Authorization (bearer and non-bearer — fix the :324 re-wrap). Covers the most common static-key tools.
  • Phase 2: generalize listener propagation to arbitrary headers (x-api-key) — a single "apply header mutations from pctx.Headers" path shared across forwardproxy/reverseproxy/extproc/extauthz.
  • Phase 3 (only if needed): outbound URL query-param injection.

Open questions

  • Is a general header-mutation propagation path across all four listeners worth doing now (it would also benefit other plugins), or do we special-case a small allowlist of injectable headers?
  • Should value sourcing support env in addition to mounted file, or file-only to match the token-exchange security posture (Secret mounted only into the proxy, never the agent)?
  • Per-destination credential selection: one plugin instance per upstream host, or a single plugin with a host→secret map? (DAM is effectively per-connection/per-host.)

References


Assisted-By: Claude Code

Metadata

Metadata

Assignees

Labels

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

Type

No type
No fields configured for issues without a type.

Projects

Status
New/ToDo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions