feat(webhook): Pass keycloak_url + keycloak_realm to jwt-validation in synthesizePipeline#336
Conversation
…n synthesizePipeline ## Why When a namespace's `authbridge-runtime-config` ConfigMap has no `pipeline:` of its own, the webhook synthesizes one from the namespace's `authbridge-config` env-var contract (NamespaceConfig). The synthesis passed `keycloak_url` + `keycloak_realm` only to the outbound token-exchange plugin. jwt-validation got just `issuer`. kagenti-extensions#383 extends the jwt-validation plugin to accept these two fields and derive jwks_url from the INTERNAL Keycloak URL — the sidecar actually reaches the JWKS endpoint from inside the cluster, and `issuer` is the PUBLIC hostname (required for `iss`-claim matching but typically unreachable from inside the pod). Without the keycloak_* hints, jwt-validation falls back to issuer-derivation and every inbound request fails with "connection refused" fetching the JWKS → 401. ## Scope Pure addition to `synthesizePipeline`: the same two fields that already feed token-exchange now also feed jwt-validation. Both plugins end up with the same "where is Keycloak internally" hint, mirroring the pre-PR-rossoctl#378 binary behavior where jwks_url was derived from outbound.token_url via a cross-plugin pass. Test: extend `TestEnsurePerAgentConfigMap_EmptyBaseYAML_Fallback FromNsConfig` to assert jwt-validation now receives keycloak_url and keycloak_realm. ## Compatibility Requires kagenti-extensions ≥ the tag that includes rossoctl#383 — older authbridge binaries reject the new fields at DisallowUnknownFields decode. The chart in rossoctl/rossoctl#1507 bumps both pins together. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
…ture PR rossoctl#321 moved keycloak-admin-secret reads to the OperatorNamespace (security hardening: agent namespaces should not hold realm admin credentials). The table-driven tests and the happy-path test were updated, but TestClientRegistration_EndToEnd_CredentialsAuthenticate was missed — it still seeds the secret in the agent namespace and constructs the reconciler with an empty OperatorNamespace. Result: post-merge CI on main and every downstream PR fails the same test with `result={false 30s <nil>}` — the reconciler can't find the admin secret in the (empty) operator namespace so it requeues after 30s, tripping the `res != ctrl.Result{}` assertion. Fix: seed the secret under clientRegistrationTestOperatorNS and set OperatorNamespace on the reconciler, matching the adjacent happy- path test at line 355. Not my PR's scope but it blocks CI and is a one-line structural fix symmetric with the sibling tests — bundling rather than waiting on a separate cleanup PR. 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, focused change. synthesizePipeline now passes keycloak_url + keycloak_realm to jwt-validation with the same guards as token-exchange — both plugins get the internal Keycloak hint needed for split-horizon deployments.
CI: all green except E2E which depends on the paired chart bump in kagenti#1507 (expected).
The bundled test fix for #321's admin-secret namespace mismatch is appreciated — symmetric with the sibling tests and unblocks CI.
E2E Failure Analysis — False Negative (not caused by this PR)The E2E failure ( Root CauseThe This happens because the E2E pulled Timeline
Evidence
FixRe-running CI should resolve this — |
Summary
Follow-up to rossoctl/cortex#383 on the operator side. When a namespace's
authbridge-runtime-configConfigMap has nopipeline:of its own,synthesizePipelinebuilds one fromNamespaceConfig. Today it passeskeycloak_url+keycloak_realmonly to the outbound token-exchange plugin; jwt-validation gets justissuer.kagenti-extensions#383 extends jwt-validation to accept those same two fields and derive
jwks_urlfrom the INTERNAL Keycloak URL (required so the sidecar can actually reach the JWKS endpoint from inside the cluster —issueris the public hostname, typically unreachable from inside the pod). This PR updates the operator to hand those fields to jwt-validation so synthesized configs work in split-horizon deployments.Why we hit this
Observed in rossoctl/rossoctl#1507 CI (weather-tool-advanced envoy-proxy stderr, with base chart-provided runtime-config — but synthesizePipeline is the fallback path for namespaces that bypass the chart entirely):
The synthesized config emitted only
issuer. jwt-validation fell back to issuer-derivation, got the public hostname, resolved it to 127.0.0.1 vialocaltest.mewildcard, and hitconnection refused.Pre-PR-#378 the binary ran
config.Resolve()→deriveJWKSURL()which derivedinbound.jwks_urlfromoutbound.token_urlvia a cross-plugin pass. Per-plugin decode gone that pass away; this PR pays that forward by handing the same hints to both plugins.Scope
pod_mutator.go: add two lines tosynthesizePipeline— passKeycloakURL/KeycloakRealmintojwtCfgalongside the existing pass intotokenCfg.pod_mutator_test.go: extendTestEnsurePerAgentConfigMap_EmptyBaseYAML_FallbackFromNsConfigto assert jwt-validation now receives both.Doc comment on
synthesizePipelineupdated to explain the split-horizon expectation and link to extensions#383.Compatibility
Requires the authbridge sidecar binary to accept the new jwt-validation fields (decoded with
DisallowUnknownFields). The chart in rossoctl/rossoctl#1507 bumps both pins together — the operator PR and the chart PR gate each other's merge.Test plan
go test ./internal/webhook/injector/...— all green (9 ensure-configmap tests, including the updated synthesize assertions)Assisted-By: Claude (Anthropic AI) noreply@anthropic.com