diff --git a/kagenti-operator/internal/controller/clientregistration_controller_test.go b/kagenti-operator/internal/controller/clientregistration_controller_test.go index ef404587..646b1c41 100644 --- a/kagenti-operator/internal/controller/clientregistration_controller_test.go +++ b/kagenti-operator/internal/controller/clientregistration_controller_test.go @@ -425,10 +425,17 @@ func TestClientRegistration_EndToEnd_CredentialsAuthenticate(t *testing.T) { clusterFeatureGatesConfigMap(true), dep, authbridgeConfigMapForTest(clientRegistrationTestNamespace, idp.URL()), - keycloakAdminSecretForTest(clientRegistrationTestNamespace), + // keycloak-admin-secret lives in the operator namespace (not the + // agent namespace) after PR #321's security hardening. The + // reconciler reads it from there via OperatorNamespace. + keycloakAdminSecretForTest(clientRegistrationTestOperatorNS), ).Build() - r := &ClientRegistrationReconciler{Client: c, Scheme: scheme} + r := &ClientRegistrationReconciler{ + Client: c, + Scheme: scheme, + OperatorNamespace: clientRegistrationTestOperatorNS, + } res, err := r.Reconcile(ctx, req) if err != nil || res != (ctrl.Result{}) { t.Fatalf("Reconcile: result=%v err=%v", res, err) diff --git a/kagenti-operator/internal/webhook/injector/pod_mutator.go b/kagenti-operator/internal/webhook/injector/pod_mutator.go index 590546e0..4cc83d2e 100644 --- a/kagenti-operator/internal/webhook/injector/pod_mutator.go +++ b/kagenti-operator/internal/webhook/injector/pod_mutator.go @@ -570,9 +570,16 @@ func perAgentConfigMapName(crName string) string { // // The synthesized shape matches what plugins expect: // - jwt-validation.config.issuer from NamespaceConfig.Issuer -// (rest of the plugin's config comes from its defaults — +// (for matching the JWT `iss` claim — the PUBLIC Keycloak URL in +// split-horizon deployments). keycloak_url + keycloak_realm are +// also passed through so the plugin can derive jwks_url from the +// INTERNAL URL — the sidecar actually GETs this URL from inside +// the cluster, and the public hostname typically won't resolve +// from inside the mesh. See kagenti-extensions#383 for why the +// jwt-validation plugin needs its own copy of these fields. +// Other plugin settings fall back to their own defaults — // audience_file=/shared/client-id.txt, bypass_paths=standard -// probes, jwks_url derived from issuer). +// probes. // - token-exchange.config with Keycloak URL/realm, default_policy, // and identity block keyed off ClientAuthType. File paths fall // through to plugin defaults so operators don't have to @@ -586,6 +593,15 @@ func synthesizePipeline(nsConfig *NamespaceConfig) map[string]interface{} { if nsConfig.Issuer != "" { jwtCfg["issuer"] = nsConfig.Issuer } + // Pass keycloak_url + keycloak_realm through to jwt-validation so + // it can derive jwks_url from the internal URL rather than the + // public issuer. Mirrors the pair already handed to token-exchange. + if nsConfig.KeycloakURL != "" { + jwtCfg["keycloak_url"] = nsConfig.KeycloakURL + } + if nsConfig.KeycloakRealm != "" { + jwtCfg["keycloak_realm"] = nsConfig.KeycloakRealm + } tokenCfg := map[string]interface{}{} if nsConfig.KeycloakURL != "" { diff --git a/kagenti-operator/internal/webhook/injector/pod_mutator_test.go b/kagenti-operator/internal/webhook/injector/pod_mutator_test.go index 30e46bb2..ed16937e 100644 --- a/kagenti-operator/internal/webhook/injector/pod_mutator_test.go +++ b/kagenti-operator/internal/webhook/injector/pod_mutator_test.go @@ -1098,6 +1098,16 @@ func TestEnsurePerAgentConfigMap_EmptyBaseYAML_FallbackFromNsConfig(t *testing.T if got, want := jwtCfg["issuer"], "http://keycloak:8080/realms/kagenti"; got != want { t.Errorf("jwt-validation.config.issuer = %v, want %v", got, want) } + // keycloak_url + keycloak_realm are passed to jwt-validation so the + // plugin derives jwks_url from the internal URL. Required for + // split-horizon deployments where `issuer` (public) isn't reachable + // from inside the pod. See kagenti-extensions#383. + if got, want := jwtCfg["keycloak_url"], "http://keycloak:8080"; got != want { + t.Errorf("jwt-validation.config.keycloak_url = %v, want %v", got, want) + } + if got, want := jwtCfg["keycloak_realm"], "kagenti"; got != want { + t.Errorf("jwt-validation.config.keycloak_realm = %v, want %v", got, want) + } tokCfg := pluginConfigAt(t, cfg, "outbound", "token-exchange") if got, want := tokCfg["keycloak_url"], "http://keycloak:8080"; got != want {