Skip to content

feat(operator): operator-managed Keycloak client registration#247

Open
akram wants to merge 4 commits intokagenti:mainfrom
akram:feature/operator-managed-client-registration
Open

feat(operator): operator-managed Keycloak client registration#247
akram wants to merge 4 commits intokagenti:mainfrom
akram:feature/operator-managed-client-registration

Conversation

@akram
Copy link
Copy Markdown

@akram akram commented Mar 27, 2026

Summary

Registers Keycloak OAuth clients for agent/tool workloads that set kagenti.io/client-registration-inject: "false", creates a Secret with client-id.txt / client-secret.txt, and annotates the pod template so the kagenti-webhook mounts credentials into shared-data.

requires:

oc patch deployment weather-service  --type merge \
   -p '{"spec": { "template": { "metadata": { "labels": {"kagenti.io/client-registration-inject": "false" }}}}}'

requires: kagenti/kagenti-extensions#262

Changes

  • ClientRegistrationReconciler for Deployment/StatefulSet; Keycloak admin client (internal/keycloak).
  • Deterministic Secret name: kagenti-keycloak-client-credentials-<hash>; annotation kagenti.io/keycloak-client-credentials-secret-name.
  • Feature flag --enable-operator-client-registration; APIReader for authbridge-config / keycloak-admin-secret; RBAC for Secrets.
  • Docs: docs/operator-managed-client-registration.md.

Related

  • Requires matching kagenti-extensions / kagenti-webhook PR (mount + reinvocation).

Testing

  • Controller unit tests; deploy operator + webhook together on a cluster with Keycloak and namespace secrets.

fixes kagenti/kagenti#397

@akram akram requested a review from a team as a code owner March 27, 2026 12:39
@akram akram force-pushed the feature/operator-managed-client-registration branch from d8863ed to 3d8c0b5 Compare March 27, 2026 12:41
Copy link
Copy Markdown
Collaborator

@cwiklik cwiklik left a comment

Choose a reason for hiding this comment

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

Review Summary

Well-designed Keycloak client registration controller with sound architecture: Secret-before-annotation ordering, owner references for GC, feature-gated, and uncached API reads for namespace config. Thorough documentation.

Key concerns: (1) indentation bug in the StatefulSet path, (2) Made-with: Cursor trailer violates commit conventions, (3) defaulting the feature to true is aggressive for a new controller. The Keycloak client is create-only (no update path).

Areas reviewed: Go, Helm/K8s RBAC, Makefile, Docs
Commits: 3 commits, all signed-off: yes
CI status: DCO passing

// DefaultHTTPClient returns an HTTP client suitable for Keycloak admin calls.
func DefaultHTTPClient() *http.Client {
return &http.Client{Timeout: 60 * time.Second}
}
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: DefaultHTTPClient() uses http.DefaultTransport which verifies TLS (good), but there's no way to inject a custom CA bundle for in-cluster Keycloak with a private CA. Consider accepting a transport option or *tls.Config for production clusters with internal CAs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed: added *tls.Config

}
_, clientSecret, err := kc.RegisterOrFetchClientWithToken(ctx, token, keycloak.ClientRegistrationParams{
Realm: ab.KeycloakRealm,
ClientID: clientID,
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: The admin token is obtained fresh via PasswordGrantToken on every reconcile. For workloads reconciling frequently, this generates one Keycloak token request per reconcile. Consider caching the token (with expiry check) to reduce Keycloak load.

continue
}
if v, ok := m["globalEnabled"].(bool); ok {
globalOn = v
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.

nit: readClusterFeatureGates iterates cm.Data and returns after parsing the first non-empty YAML document. If the ConfigMap has multiple data keys, only the first is considered. A brief comment clarifying this assumption would help readability.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed: comment added

verbs:
- create
- get
- list
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 grants cluster-wide secrets read access (get/list/watch). The controller only needs secrets in agent namespaces. Consider whether a more restrictive RBAC binding is warranted (ClusterRole + per-namespace RoleBindings), or document why cluster-wide is needed.

Name: secretName,
Namespace: owner.GetNamespace(),
Labels: map[string]string{
LabelManagedBy: LabelManagedByValue,
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.

👍 Good use of controllerutil.CreateOrUpdate with SetControllerReference — ensures GC when the parent Deployment/StatefulSet is deleted. The Secret-before-annotation ordering is also well done.

akram added 4 commits March 27, 2026 16:45
Introduce ClientRegistrationReconciler for Deployments and StatefulSets
that use kagenti.io/client-registration-inject=false: register the workload
as an OAuth client in Keycloak, create credentials secrets, and annotate
the pod template so the webhook can mount them without injecting the
registration sidecar. Add a small Keycloak admin API client and RBAC;
gate the controller behind --enable-operator-client-registration.

Signed-off-by: Akram <akram.benaissi@gmail.com>
Describe motivation, webhook/operator contract, requirements,
rollout and rollback, and pointers to source files.

Signed-off-by: Akram <akram.benaissi@gmail.com>
- Use kagenti.io/keycloak-client-credentials-secret-name and kagenti-keycloak-client-credentials-<uniq-id> Secrets
- Rename inject/annotation helpers for Keycloak client credentials; update docs

Assisted-by: Cursor

Signed-off-by: Akram <akram.benaissi@gmail.com>
- Cache admin tokens via CachedAdminTokenProvider; wire from main when client registration is enabled.
- Default --enable-operator-client-registration to false (opt-in).
- Reconcile Keycloak client settings: GET client, PUT when desired state drifts from Keycloak.
- Add HTTPClientConfig / NewHTTPClient for TLS and custom transport (e.g. private CA).
- Document feature-gate ConfigMap iteration; fix StatefulSet branch indentation.
- Add integration-style reconciler tests and expand admin tests (drift, HTTP client).

Addresses review comments.

Assisted-by: Cursor

Signed-off-by: Akram <akram.benaissi@gmail.com>
@akram akram force-pushed the feature/operator-managed-client-registration branch 2 times, most recently from 3d8c0b5 to 7d9d86f Compare March 27, 2026 15:46
Copy link
Copy Markdown
Contributor

@Alan-Cha Alan-Cha left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR adds operator-managed Keycloak client registration as an alternative to sidecar-based registration. The implementation is well-designed with proper controller patterns, comprehensive tests (including envtest-style reconciler tests and mock Keycloak server), token caching, drift reconciliation, and thorough documentation.

The latest commit (7d9d86f) successfully addressed most of the previous review feedback: feature flag now defaults to false (opt-in), token caching is implemented, TLS config support added, indentation bug fixed, and test coverage expanded significantly.

One must-fix issue remains: Documentation inconsistency about the feature flag default value.

Areas reviewed: Go (controller, Keycloak admin client, tests), Makefile, RBAC, Documentation, Commit conventions

Commits: 4 commits, all DCO signed ✅

CI status: DCO passing ✅


Must-Fix Issues

Documentation default value mismatch (docs/operator-managed-client-registration.md, line ~77 in new file):

Line states the default is true, but cmd/main.go:112 sets the flag default to false. The docs should match the code:

-| Operator | `--enable-operator-client-registration` (default **true**) | Master switch for the ClientRegistration controller. |
+| Operator | `--enable-operator-client-registration` (default **false**) | Master switch for the ClientRegistration controller. |

Praise

Excellent reconciler patterns (clientregistration_controller.go):

  • CreateOrUpdate with owner references ensures proper GC
  • ✅ Secret-before-annotation ordering prevents pods from referencing missing Secrets
  • ✅ Clean reconciler structure following controller-runtime best practices

Well-implemented drift reconciliation (internal/keycloak/admin.go):

  • ✅ GET client, compare managed fields, merge desired state, PUT only when drifted
  • ✅ Properly handles Keycloak's JSON shape quirks (string arrays in attributes)

Strong test coverage (clientregistration_controller_test.go):

  • ✅ Reconciler integration tests with fake clients and mock Keycloak server
  • ✅ Covers feature gates disabled, missing dependencies (requeue), and happy path
  • ✅ Addresses previous review's testing concern

Suggestions

Token cache tuning (cmd/main.go): Token cache is instantiated with a hardcoded tokenCacheSkew. Consider exposing this as a flag if operators need to tune it for their Keycloak instance characteristics.

Commit 3 convention: Subject lacks emoji prefix: operator: Keycloak client credentials Secret naming and annotation. Should be feat(operator): or refactor(operator): per Kagenti conventions. Not blocking since commits can be squashed on merge.


Once the documentation is corrected, this PR is ready to merge. The implementation quality is high, test coverage is comprehensive, and all previous review concerns have been addressed.

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.

feature: Refactor Keycloak secrets handling for agent operator and centralized credential management

3 participants