feat(webhook): Emit per-plugin authbridge config schema#335
Conversation
Follow-up to kagenti-extensions PR rossoctl#378, which replaced authbridge's top-level `inbound:` / `outbound:` / `identity:` / `bypass:` / `routes:` YAML blocks with per-plugin config under `pipeline.*.plugins[].config`. The new binary rejects any config whose pipeline is empty (which is what yaml.v3's silent drop of unknown keys produces when the top-level blocks are removed), so the webhook had to stop synthesizing the old shape. What changed in pod_mutator.go: ensurePerAgentConfigMap's fallback-synthesis branch is rewritten. Previously: if baseYAML was missing any of `inbound`, `outbound`, `identity`, or `bypass`, synthesize each from NamespaceConfig values. Now: if baseYAML has no `pipeline:` section at all, synthesize one with jwt-validation (inbound) and token-exchange (outbound) entries whose configs carry Issuer / Keycloak URL / realm / default policy / identity type from NamespaceConfig. File-path defaults (audience_file, client_id_file, client_secret_file, jwt_svid_path) are no longer emitted by the webhook — the authbridge plugin applies them from its own convention layer at Configure time (see authbridge/authlib/plugins/CONVENTIONS.md). This keeps the webhook schema-agnostic about paths and reduces duplication. When baseYAML already has a `pipeline:` section (the post-migration Kagenti Helm chart emits it), synthesis is skipped entirely. Only mode + listener overrides layer on top. The Helm chart owns plugin config contents; the webhook owns per-agent mode selection. Tests: - TestEnsurePerAgentConfigMap_EmptyBaseYAML_FallbackFromNsConfig: asserts the synthesized pipeline has the correct plugin names and config values. Navigates pipeline.<direction>.plugins[<name>] via a new pluginConfigAt helper to keep assertions compact. - TestEnsurePerAgentConfigMap_BaseYAML_PreservesExistingFields: baseYAML rewritten to new shape; asserts plugin config is preserved verbatim. - TestEnsurePerAgentConfigMap_ListenerOverrides_Merged: baseYAML rewritten to new shape; listener assertions unchanged (listener is top-level in both schemas). - TestEnsurePerAgentConfigMap_FederatedJWT_MapsToSpiffe: asserts identity.type = spiffe under token-exchange. Dropped assertions for file-path defaults (those moved into the plugin). E2E fixtures: - test/e2e/fixtures.go: two hardcoded ConfigMaps with old-shape YAML rewritten to new per-plugin shape. Image tags: none bumped. The operator references authbridge images as `:latest` throughout — once PR rossoctl#378 merges and CI publishes new `:latest`, operator CI picks it up. The version-pinning (and atomic authbridge+operator cutover) lives in the kagenti Helm chart PR that comes next. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
pdettori
left a comment
There was a problem hiding this comment.
Clean migration from flat authbridge config schema to per-plugin pipeline: structure. Logic is straightforward and well-tested — if cfg["pipeline"] is nil, synthesize from NamespaceConfig; otherwise let the Helm chart's config stand. Bypass-paths default removal is explicitly documented as moved to the plugin's convention layer.
All 16 CI checks pass.
Two minor suggestions:
-
pod_mutator.go—synthesizePipeline: WhenClientAuthTypeis non-empty and notFederatedJWT, its value is passed through directly asidentity["type"]. Might be worth a brief comment noting which values the plugin considers valid here — helps future readers understand whether the webhook should validate or just pass through. -
pod_mutator_test.go—pluginConfigAt: The local variablecfgin the return shadows the outercfgthat callers typically have in scope. Consider naming itpluginCfgto avoid confusion when reading tests.
Summary
Follow-up to kagenti/kagenti-extensions#378,
which replaces authbridge's top-level
inbound:/outbound:/identity:/bypass:/routes:YAML blocks with per-plugin configunder
pipeline.*.plugins[].config. The new authbridge binary rejectsany config whose pipeline is empty (the silent result of yaml.v3
dropping the old top-level keys), so the webhook has to stop
synthesizing the old shape.
Depends on #378 — once it merges and
:latestforauthbridge-envoy / authbridge-light is published, this PR's CI picks
up the new image.
What changes
pod_mutator.go—ensurePerAgentConfigMapfallback synthesisThe fallback branch that runs when
authbridge-runtime-configin thetarget namespace has no
pipeline:section is rewritten. Previouslysynthesized top-level
inbound:,outbound:,identity:,bypass:from
NamespaceConfig. Now synthesizes apipeline:section with:jwt-validation(inbound) — config carriesissuerfromNamespaceConfig.Issuer.token-exchange(outbound) — config carrieskeycloak_url,keycloak_realm,default_policy, andidentity.type(mappingfederated-jwt→spiffe).File-path defaults (audience_file, client_id_file, client_secret_file,
jwt_svid_path) are no longer emitted by the webhook — the plugin
applies them from its own convention layer at Configure time (see
authbridge/authlib/plugins/CONVENTIONS.md). Keeps the webhookschema-agnostic about filesystem paths and reduces duplication.
When
baseYAMLalready has apipeline:section (which thepost-migration Kagenti Helm chart emits), synthesis is skipped; only
mode+listeneroverrides layer on top. The chart owns pluginconfig contents; the webhook owns per-agent mode selection.
Tests updated
pod_mutator_test.go:TestEnsurePerAgentConfigMap_EmptyBaseYAML_FallbackFromNsConfignow asserts the synthesized pipeline via a new
pluginConfigAthelper that navigates
pipeline.<direction>.plugins[<name>].config.TestEnsurePerAgentConfigMap_BaseYAML_PreservesExistingFields— baseYAML rewritten to new shape; preservation assertions
updated accordingly.
TestEnsurePerAgentConfigMap_ListenerOverrides_Merged—baseYAML rewritten; listener assertions unchanged (listener is
top-level in both schemas).
TestEnsurePerAgentConfigMap_FederatedJWT_MapsToSpiffe—asserts
identity.type = spiffeunder token-exchange config.Dropped assertions on default file paths (those moved into
the plugin).
test/e2e/fixtures.go— two hardcodedauthbridge-runtime-configConfigMaps rewritten to emit the new per-plugin shape.
No image tag bumps
The operator references authbridge images as
:latestthroughout(values.yaml, webhook defaults, e2e suite). Once #378 merges and
publishes
:latest, operator CI picks it up. Version-pinning andthe atomic authbridge-operator cutover live in the kagenti Helm
chart PR that comes next (tracked separately).
Test plan
go build ./...clean.go vet ./...clean.Will run after Feat: MLflow operand controller for CR lifecycle and OTEL RBAC #378 merges and
:latestis published.the matched authbridge image — pre-merge gate alongside the
kagenti Helm chart PR.
Draft
Draft until #378 lands and the kagenti Helm chart PR is ready —
all three flip from draft to ready together.
Assisted-By: Claude (Anthropic AI) noreply@anthropic.com