client-registration uses external KEYCLOAK_URL for server-to-server admin calls → unreachable in-cluster on Kind (client-credentials Secret never created)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 18
- Forks
- 50
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 8
Description
Summary
The client-registration controller uses the external KEYCLOAK_URL (the workload issuer host) for its server-to-server Keycloak Admin API calls. On Kind that host (keycloak.localtest.me) resolves to 127.0.0.1 inside a pod, so the admin-token request fails, no client is registered, and the kagenti-keycloak-client-credentials-<hash> Secret is never created — leaving the injected agent pod stuck FailedMount/Init:0/1, never Ready.
Code trace (internal/controller/clientregistration_controller.go)
readAuthbridgeConfigMap→KeycloakURL = cm.Data["KEYCLOAK_URL"](~L434) — this is the external issuer host.reconcileOnebuilds the admin client with that same value (~L261):kc := keycloak.Admin{BaseURL: ab.KeycloakURL, HTTPClient: keycloak.DefaultHTTPClient()} // → admin token (password grant) + RegisterOrFetchClientWithToken- The same
KEYCLOAK_URLis also consumed by the webhook injector (internal/webhook/injector/namespace_config.go:73,container_builder.go:319) for the workload's JWT validation, where the external issuer is correct.
So one config key serves two incompatible purposes: server-to-server admin (needs in-cluster reachability) vs workload token validation (needs the external issuer).
Evidence
- Live dev cluster (Kind): operator log:
Keycloak admin token failed ... Post "https://keycloak.localtest.me/realms/master/protocol/openid-connect/token": dial tcp 127.0.0.1:443: connect: connection refusedkubectl get secrets -n tx-e2e | grep -c kagenti-keycloak-client-credentials→0; injected podInit:0/1,FailedMount: secret "kagenti-keycloak-client-credentials-..." not found. - CI (
release-validationKind): the token-exchange wait loggedWaiting for credentials (0/2)for the full loop; pod never Ready. (Confirm from the operator pod logs in the failure artifact.)
Why it looks "flaky"
It's platform-dependent, not random: on Kind, keycloak.localtest.me → 127.0.0.1 (unreachable in-cluster) → always fails; on OCP, the external route may be reachable from inside the cluster → happens to work. Matches the e2e-kind.yaml "client registration ... flaky/incomplete on Kind" note.
Proposed fix — decouple the admin endpoint from the issuer URL
The admin/registration calls must use an in-cluster-reachable URL, distinct from the external issuer KEYCLOAK_URL used for workload validation:
- Add
KEYCLOAK_INTERNAL_URLtoauthbridgeConfig+readAuthbridgeConfigMap. - In
reconcileOne, choose the admin base URL:
The operator already reads admin creds from the Keycloak namespace (adminBase := ab.KeycloakAdminURL // KEYCLOAK_INTERNAL_URL if adminBase == "" { adminBase = deriveInClusterURL(kcNamespace) } // http://keycloak-service.<kc-ns>.svc:8080 if adminBase == "" { adminBase = ab.KeycloakURL } // last resort (OCP where route is in-cluster reachable) kc := keycloak.Admin{BaseURL: adminBase, HTTPClient: keycloak.DefaultHTTPClient()}resolveKeycloakAdminCredentials), so deriving the in-cluster service URL needs no new lookup. - Populate
KEYCLOAK_INTERNAL_URLinauthbridge-configfrom the platform install + the token-exchange setup (kagenti-system already carrieshttp://keycloak-service.keycloak.svc:8080; the admin path just doesn't use it). KeepKEYCLOAK_URL= external issuer for the injector.
Secondary: surface a clear condition/event on the AgentRuntime when admin-token/registration fails (e.g. ClientRegistrationFailed: keycloak unreachable at <url>) — this was invisible until pod events + operator logs were read manually.
Impact / linkage
Blocks the two exec-based token-exchange E2E tests in kagenti/kagenti#2129 (they stay xfail until the Secret is reliably created). Related: this repo #417 (Keycloak client contract), #433 (per-namespace KEYCLOAK_REALM). Prior client-registration fix: kagenti/kagenti#2098 (namespaces:patch RBAC → client_not_found) — a different failure in the same path.
Assisted-By: Claude Code
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in internal/controller/clientregistration_controller.go at readAuthbridgeConfigMap and reconcileOne, then inspect internal/webhook/injector/namespace_config.go and container_builder.go to preserve the external issuer path. Check e2e-kind.yaml and the release-validation failure artifacts while tracing the token-exchange setup. Done means admin registration reaches Keycloak in Kind, the client-credentials Secret is created, and the injected pod becomes Ready.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100