Skip to content

feat: render AuthBridge layer-3 plugin presets from AgentRuntime.spec - #524

Open
webchang wants to merge 2 commits into
rossoctl:mainfrom
webchang:preset-pipeline
Open

feat: render AuthBridge layer-3 plugin presets from AgentRuntime.spec#524
webchang wants to merge 2 commits into
rossoctl:mainfrom
webchang:preset-pipeline

Conversation

@webchang

@webchang webchang commented Aug 28, 2026

Copy link
Copy Markdown
Member

Closes #523.

What

Renders the full AuthBridge layer-3 plugin pipeline from AgentRuntime.spec when a preset is
requested, instead of the default two-plugin (jwt-validation + token-exchange) synthesis.
Consumes the fields set by the backend agent-create API (companion: rossoctl/rossoctl#2487).

New spec fields (CRD enums + ValidatePlugins): pluginPreset (auth-only | ibac-only | full),
plugins (name:policy override tokens), onError (enforce | observe | off).

How

synthesizePresetPipeline (a port of the harness pipeline-merge.py):

  • Emits every supported plugin in canonical order — INBOUND [a2a-parser, jwt-validation],
    OUTBOUND [token-exchange, token-broker, inference-parser, mcp-parser, ibac]; non-selected
    plugins get on_error: off.
  • Per-plugin policy → entry: enforce omits on_error; observe / off emitted explicitly.
    spec.onError is the chain default; spec.plugins tokens override per plugin.
  • Enforces the token-exchange XOR token-broker outbound mutex.
  • Seeds each plugin's config: from the already-rendered base (issuer / keycloak_* / identity)
    rather than replacing it, so base config survives sidecar reloads.
  • When ibac is active, stamps judge config from a new platform-config ibac block
    (judgeEndpoint / judgeModel / timeoutMs / agentLlmHost / judgeBearer) plus a baked
    system_prompt. A webhook warning fires if ibac is selected while judgeEndpoint is unset
    (the sidecar rejects ibac at config-build time without a judge endpoint).

Changed

  • api/v1alpha1/agentruntime_types.go (+ regenerated CRD + zz_generated.deepcopy.go),
    agentruntime_webhook.go — spec fields + validation.
  • internal/webhook/injector/preset_pipeline.go (new) + pod_mutator.go — pipeline synthesis.
  • internal/webhook/config/{types.go,defaults.go}, charts/operator/values.yamlibac
    platform-config block.
  • Unit tests: preset_pipeline_test.go, agentruntime_plugins_test.go.

Test plan

go test ./... green. Validated end-to-end cross-cluster: rendered per-agent config matched the
expected preset pipeline for auth-only / ibac-only / full; the AuthBridge sidecar loaded
without reload errors; and a small IBAC-comparison benchmark showed the enforce (blocks) vs
observe (passes) contrast.

Assisted-By: Claude Code

@webchang
webchang requested a review from a team as a code owner August 28, 2026 14:00
@webchang

Copy link
Copy Markdown
Member Author

Companion backend PR: rossoctl/rossoctl#2488 (sets the pluginPreset / plugins / onError fields consumed here). Both are required together.

@cwiklik cwiklik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid implementation — pipeline synthesis logic is correct, well-tested (7 focused unit tests covering canonical order, membership/policy, overrides, mutex, base-config seeding, IBAC judge stamping, and the missing-judge warning), and integrates cleanly with existing pod_mutator flow (seeds from base, doesn't replace). deepCopyMap/indexPipelineConfigs handle nil safely throughout.

Must-fix:

  1. Commit is missing a Signed-off-by: trailer — DCO CI check is failing. Amend with --signoff and force-push.
  2. Commit uses Co-Authored-By: Claude Opus 4.8 — org convention (CLAUDE.md) prohibits Co-authored-by for AI attribution (inflates GitHub contributor stats). Replace with Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>.

Suggestions (non-blocking):

  1. judgeBearer in ConfigMap — the bearer token sourced from values.yaml ends up in a ConfigMap (plaintext), readable by anyone with namespace access. Consider a secretKeyRef for a follow-up so the credential stays in a Secret.
  2. Mutex at admission — the token-exchange/token-broker mutex is enforced at render time (synthesizePresetPipeline), not at webhook admission. A CR with pluginPreset: full + plugins: ["token-broker:enforce"] is admitted but pod injection fails later with a confusing error. Moving the mutex check into checkPluginPresetValid would give fail-fast UX at CR creation time.

# LLM traffic from tool traffic. Omitted from the plugin config when empty.
agentLlmHost: ""
# Optional: bearer token for the judge endpoint. Omitted when empty.
judgeBearer: ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: judgeBearer will end up in a ConfigMap (plaintext). For a bearer token, consider sourcing from a Secret reference in a follow-up — avoids credential-in-ConfigMap for anyone with namespace read.

if !ok {
return nil, nil, fmt.Errorf("unknown plugin preset %q (want auth-only, ibac-only, or full)", preset)
}
if chainDefault != "" && !supportedPolicies[chainDefault] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This mutex check is only enforced at render time, not at webhook admission (ValidatePlugins/checkPluginPresetValid). A CR with pluginPreset: full + plugins: ["token-broker:enforce"] is admitted (no webhook error) but pod injection fails later. Consider adding the check to webhook validation for fail-fast UX.

Add spec.pluginPreset / spec.plugins / spec.onError to AgentRuntime and
render the full canonical AuthBridge plugin pipeline into the per-agent
authbridge-config-<name> ConfigMap when a preset is set (Option B).

Ported from the workload-harness pipeline-merge.py:
- Presets auth-only / ibac-only / full select plugin membership; every
  supported plugin is emitted in fixed canonical order (inbound
  [a2a-parser, jwt-validation]; outbound [token-exchange, token-broker,
  inference-parser, mcp-parser, ibac]), unselected ones at on_error: off.
- Per-plugin policy enforce|observe|off maps to on_error (enforce omits
  it); spec.onError sets the chain default, spec.plugins "NAME:POLICY"
  tokens override per plugin.
- token-exchange XOR token-broker on the outbound chain (mutex rejected).
- Per-plugin config is seeded from the operator's already-rendered base
  (jwt-validation issuer, token-exchange identity) so it survives — never
  replaced wholesale (prevents "issuer is required" reload failures).
- ibac judge config (endpoint/model/timeout/host/bearer) comes from a new
  platform IBACConfig (Helm defaults.ibac.*); the judge system prompt is
  baked into the operator binary.

Validating webhook rejects malformed spec.plugins tokens via
AgentRuntimeSpec.ValidatePlugins (in the leaf api package to avoid an
injector import cycle). CRD + deepcopy regenerated. Unit tests cover
synthesis, membership, overrides, mutex, base-config seeding, and ibac.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Rong Chang <rong@us.ibm.com>
@webchang

Copy link
Copy Markdown
Member Author

Thanks for the review, @cwiklik!

Must-fix items — done:

  • Added Signed-off-by: trailer (DCO now green).
  • Replaced Co-Authored-By: Claude Opus 4.8 with Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>, and updated the PR description footer to Assisted-By: Claude Code per CLAUDE.md convention.

Non-blocking suggestions — tracked as follow-up issues (kept out of this PR to avoid ballooning a focused change):

Heads-up: syncing this branch with main to pick up #517 (routes split into a separate ConfigMap). That changed ensurePerAgentConfigMap to a 3-value return, which this branch predates — the current CI red is that stale-branch conflict, not the review changes. Will push the merge fix shortly.

Resolve the semantic conflict with rossoctl#517: ensurePerAgentConfigMap now
returns (configCMName, routesCMName, err); update the preset-render
error path to the 3-value signature.

Signed-off-by: Rong Chang <rong@us.ibm.com>
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
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.

AgentRuntime cannot request a full AuthBridge layer-3 plugin pipeline (only the default 2-plugin synthesis)

2 participants