Skip to content

Conversation

@OlegErshov
Copy link
Contributor

@OlegErshov OlegErshov commented Nov 4, 2025

On-behalf-of: SAP [email protected]

Summary by CodeRabbit

  • Chores

    • Updated project dependencies (added indirect dependencies).
  • Configuration

    • Invite authentication switched to client-credentials flow.
    • Default Keycloak client identifier changed.
    • Added Keycloak client secret configuration; removed legacy password-file option.
  • Tests

    • Updated invite-related tests to reflect the new authentication approach and defaults.

@OlegErshov OlegErshov self-assigned this Nov 4, 2025
@github-actions github-actions bot added the fix label Nov 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

Switch Keycloak auth from password-based OAuth2 to client-credentials flow, add two indirect go.mod deps, replace Keycloak password fields with a client secret in InviteConfig, remove runtime password-file reading, and update New() signatures and call sites.

Changes

Cohort / File(s) Summary
Dependency Management
go.mod
Added indirect dependencies: github.com/oapi-codegen/runtime v1.1.2, github.com/apapsch/go-jsonmerge/v2 v2.0.0.
Configuration
internal/config/config.go
InviteConfig changed: KeycloakClientID default updated admin-clisecurity-operator; removed KeycloakUser and KeycloakPasswordFile; added KeycloakClientSecret (mapstructure:"invite-keycloak-client-secret").
Invite controller
internal/controller/invite_controller.go
Removed runtime file-read of Keycloak password and related error handling; call sites updated to stop passing a password to invite constructor; removed unused imports.
Invite subroutine implementation
internal/subroutine/invite/subroutine.go
Replaced password-based OAuth2 flow with clientcredentials flow (golang.org/x/oauth2/clientcredentials); removed use of oauth2.PasswordCredentialsToken; New signature no longer accepts a password and now creates an HTTP client via client credentials using KeycloakClientID and KeycloakClientSecret.
Invite subroutine tests
internal/subroutine/invite/subroutine_test.go
Updated test fixtures to use KeycloakClientID = security-operator; updated invite.New calls to remove the password argument and match new constructor signature.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check client-credentials configuration (scopes, endpoint, secret wiring) in internal/subroutine/invite/subroutine.go.
  • Validate config tag/name changes and any marshaling implications in internal/config/config.go.
  • Ensure controller wiring and tests reflect the new New signature in internal/controller/invite_controller.go and internal/subroutine/invite/subroutine_test.go.

Possibly related PRs

Suggested reviewers

  • aaronschweig
  • nexus49

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: migrating from password-based to client credentials (clientID/secret) OAuth2 authentication for Keycloak.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/keycloak-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

On-behalf-of: SAP [email protected]
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
cmd/operator.go (1)

8-8: Remove unused import.

The os package is imported but doesn't appear to be used in this file.

Apply this diff:

-	"os"
internal/controller/invite_controller.go (1)

24-27: Consider making namespace configurable.

The namespace platform-mesh-system is hardcoded, which reduces flexibility. If there's a possibility of deploying to different namespaces, consider making this configurable through the Config struct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d263179 and 2d09477.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • cmd/operator.go (3 hunks)
  • go.mod (2 hunks)
  • internal/config/config.go (1 hunks)
  • internal/controller/invite_controller.go (2 hunks)
  • internal/subroutine/invite/subroutine.go (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 139
File: internal/subroutine/invite/subroutine.go:182-188
Timestamp: 2025-10-27T08:44:10.551Z
Learning: In the platform-mesh/security-operator repository, when creating Keycloak realms, a client is created with the same identifier as the realm name. Therefore, using the realm name as the `client_id` parameter in Keycloak API calls (such as execute-actions-email) is correct and intentional.
📚 Learning: 2025-10-24T06:20:22.789Z
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 136
File: internal/subroutine/authorization_model.go:74-81
Timestamp: 2025-10-24T06:20:22.789Z
Learning: In internal/subroutine/authorization_model.go, the NewDiscoveryClientFunc factory does not need to return an error because the *rest.Config passed to it is copied from an already validated manager config (a.mgr.GetLocalManager().GetConfig()), so the config is guaranteed to be valid at that point.

Applied to files:

  • cmd/operator.go
  • internal/subroutine/invite/subroutine.go
🧬 Code graph analysis (2)
cmd/operator.go (2)
internal/subroutine/invite/subroutine.go (1)
  • New (51-73)
internal/controller/invite_controller.go (1)
  • NewInviteReconciler (33-57)
internal/controller/invite_controller.go (2)
internal/config/config.go (1)
  • Config (11-45)
internal/subroutine/invite/subroutine.go (1)
  • New (51-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: pipe / lint / lint
  • GitHub Check: pipe / dockerBuild / docker
  • GitHub Check: pipe / testSource / test
🔇 Additional comments (5)
internal/config/config.go (1)

5-7: Configuration changes align with client credentials flow.

The default changes properly reflect the switch from admin CLI authentication to service client credentials. The KeycloakClientSecret field correctly stores the Kubernetes Secret name rather than the secret value itself.

cmd/operator.go (2)

151-156: In-cluster client creation looks good.

The runtime client is properly initialized with error handling. Using ctrl.GetConfigOrDie() is appropriate for the operator initialization context.


169-169: Reconciler initialization properly updated.

The call to NewInviteReconciler correctly includes the new runtimeClient parameter, matching the updated function signature.

internal/subroutine/invite/subroutine.go (1)

51-72: OAuth2 client credentials flow properly implemented.

The switch from password credentials to client credentials flow is correctly implemented. The clientcredentials.Config will automatically handle token acquisition and refresh for all HTTP requests made with the returned client.

internal/controller/invite_controller.go (1)

33-46: Secret loaded at startup only.

Note that the Keycloak client secret is loaded once during operator initialization. If the Secret is updated in Kubernetes, the operator will need to be restarted to pick up the new value. This is typical behavior for service credentials.

Confirm this startup-time loading behavior aligns with your operational requirements for credential rotation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d09477 and 53e99ff.

📒 Files selected for processing (1)
  • cmd/operator.go (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 139
File: internal/subroutine/invite/subroutine.go:182-188
Timestamp: 2025-10-27T08:44:10.551Z
Learning: In the platform-mesh/security-operator repository, when creating Keycloak realms, a client is created with the same identifier as the realm name. Therefore, using the realm name as the `client_id` parameter in Keycloak API calls (such as execute-actions-email) is correct and intentional.
📚 Learning: 2025-10-24T06:20:22.789Z
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 136
File: internal/subroutine/authorization_model.go:74-81
Timestamp: 2025-10-24T06:20:22.789Z
Learning: In internal/subroutine/authorization_model.go, the NewDiscoveryClientFunc factory does not need to return an error because the *rest.Config passed to it is copied from an already validated manager config (a.mgr.GetLocalManager().GetConfig()), so the config is guaranteed to be valid at that point.

Applied to files:

  • cmd/operator.go
🧬 Code graph analysis (1)
cmd/operator.go (2)
internal/subroutine/invite/subroutine.go (1)
  • New (51-73)
internal/controller/invite_controller.go (1)
  • NewInviteReconciler (33-57)
🔇 Additional comments (1)
cmd/operator.go (1)

168-168: LGTM!

The runtimeClient is correctly passed to NewInviteReconciler as the third parameter, matching the updated constructor signature. This enables the reconciler to fetch the Keycloak client secret from a Kubernetes Secret.

@github-actions github-actions bot added the chore label Nov 4, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
internal/config/config.go (1)

7-7: Consider validation for required KeycloakClientSecret.

The KeycloakClientSecret is now essential for authentication but has no default value or validation. If this field is empty at runtime, authentication will fail silently.

Consider adding validation in the config loading process or at the point where invite.New() is called to ensure KeycloakClientSecret is non-empty.

internal/subroutine/invite/subroutine_test.go (2)

350-356: Consider updating test client ID to match new default.

The test uses "admin-cli" as the KeycloakClientID, but the new default in internal/config/config.go line 5 is "security-operator". While this doesn't affect test functionality (due to mocking), aligning tests with production defaults improves consistency.

Apply this diff to align with the new default:

 		s, err := invite.New(ctx, &config.Config{
 			Invite: config.InviteConfig{
 				KeycloakBaseURL:  srv.URL,
-				KeycloakClientID: "admin-cli",
+				KeycloakClientID: "security-operator",
 			},
 			BaseDomain: "portal.dev.local:8443",
 		}, mgr)

382-387: Consider updating test client ID to match new default.

Same as the previous test case, this test uses "admin-cli" instead of the new default "security-operator".

Apply this diff:

 	s, err := invite.New(ctx, &config.Config{
 		Invite: config.InviteConfig{
 			KeycloakBaseURL:  srv.URL,
-			KeycloakClientID: "admin-cli",
+			KeycloakClientID: "security-operator",
 		},
 	}, nil)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e99ff and f184cf2.

📒 Files selected for processing (4)
  • internal/config/config.go (1 hunks)
  • internal/controller/invite_controller.go (1 hunks)
  • internal/subroutine/invite/subroutine.go (2 hunks)
  • internal/subroutine/invite/subroutine_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/subroutine/invite/subroutine.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 139
File: internal/subroutine/invite/subroutine.go:182-188
Timestamp: 2025-10-27T08:44:10.551Z
Learning: In the platform-mesh/security-operator repository, when creating Keycloak realms, a client is created with the same identifier as the realm name. Therefore, using the realm name as the `client_id` parameter in Keycloak API calls (such as execute-actions-email) is correct and intentional.
📚 Learning: 2025-10-24T06:20:22.789Z
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 136
File: internal/subroutine/authorization_model.go:74-81
Timestamp: 2025-10-24T06:20:22.789Z
Learning: In internal/subroutine/authorization_model.go, the NewDiscoveryClientFunc factory does not need to return an error because the *rest.Config passed to it is copied from an already validated manager config (a.mgr.GetLocalManager().GetConfig()), so the config is guaranteed to be valid at that point.

Applied to files:

  • internal/subroutine/invite/subroutine_test.go
🧬 Code graph analysis (1)
internal/controller/invite_controller.go (1)
internal/subroutine/invite/subroutine.go (1)
  • New (51-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: pipe / dockerBuild / docker
  • GitHub Check: pipe / lint / lint
🔇 Additional comments (1)
internal/controller/invite_controller.go (1)

25-40: LGTM! Simplified initialization.

The removal of password file reading and the simplified call to invite.New(ctx, cfg, mgr) is a good improvement. The authentication is now handled through client credentials flow configured via cfg.Invite.KeycloakClientSecret.

Note: The past review comment about validating Secret data keys doesn't appear to apply to the current code, as there's no Kubernetes Secret reading in this file.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f184cf2 and 6923256.

📒 Files selected for processing (1)
  • internal/config/config.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 139
File: internal/subroutine/invite/subroutine.go:182-188
Timestamp: 2025-10-27T08:44:10.551Z
Learning: In the platform-mesh/security-operator repository, when creating Keycloak realms, a client is created with the same identifier as the realm name. Therefore, using the realm name as the `client_id` parameter in Keycloak API calls (such as execute-actions-email) is correct and intentional.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: pipe / dockerBuild / docker
  • GitHub Check: pipe / lint / lint
🔇 Additional comments (1)
internal/config/config.go (1)

3-7: LGTM! Client secret is securely handled.

The struct changes migrate cleanly to client credentials flow:

  • Removed password-based fields (KeycloakUser, KeycloakPasswordFile)
  • Added KeycloakClientSecret with no default value (correct security practice)

Verification confirms secure handling:

  • Secret is passed directly to the oauth2 clientcredentials library and never logged
  • HTTP client is created once and cached; secret is not repeatedly exposed
  • Error logging only captures specific safe fields (email, user ID, client ID), never the config or secret
  • No String() or MarshalJSON() methods exist that could serialize the secret

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/subroutine/invite/subroutine_test.go (1)

41-50: Consider validating grant_type in the mock token endpoint.

The mock token endpoint returns a token without validating the grant_type parameter. Since the PR switches from password flow to client credentials flow, consider adding validation to ensure the correct grant type is being used.

For example:

 mux.HandleFunc("/realms/master/protocol/openid-connect/token", func(w http.ResponseWriter, r *http.Request) {
+	err := r.ParseForm()
+	assert.NoError(t, err)
+	assert.Equal(t, "client_credentials", r.FormValue("grant_type"))
 
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusOK)
 
 	err := json.NewEncoder(w).Encode(&map[string]string{
 		"access_token": "token",
 	})
 	assert.NoError(t, err)
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6923256 and c34ac82.

📒 Files selected for processing (1)
  • internal/subroutine/invite/subroutine_test.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: aaronschweig
Repo: platform-mesh/security-operator PR: 139
File: internal/subroutine/invite/subroutine.go:182-188
Timestamp: 2025-10-27T08:44:10.551Z
Learning: In the platform-mesh/security-operator repository, when creating Keycloak realms, a client is created with the same identifier as the realm name. Therefore, using the realm name as the `client_id` parameter in Keycloak API calls (such as execute-actions-email) is correct and intentional.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: pipe / dockerBuild / docker
🔇 Additional comments (2)
internal/subroutine/invite/subroutine_test.go (2)

382-387: Same verification needed for KeycloakClientSecret.

Similar to the previous test case, this configuration also doesn't specify KeycloakClientSecret. While this test only exercises helper functions (GetName, Finalizers, Finalize) and may not trigger authentication, it's worth confirming the intended behavior for consistency.


350-356: No action required—KeycloakClientSecret handling in test is correct.

Both test cases consistently omit KeycloakClientSecret because the mocked OIDC provider doesn't validate credentials—it returns a hardcoded token regardless of the secret value. This is intentional and correct for unit testing with mocks. Providing a dummy secret is unnecessary.

Copy link
Contributor

@aaronschweig aaronschweig left a comment

Choose a reason for hiding this comment

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

LGTM, is there already a GitOps way of setting that client up in keycloak or how did you do it?

@OlegErshov
Copy link
Contributor Author

LGTM, is there already a GitOps way of setting that client up in keycloak or how did you do it?

Yes, it's already set up here client roles and client

@OlegErshov OlegErshov merged commit c54d794 into main Nov 5, 2025
11 checks passed
@OlegErshov OlegErshov deleted the fix/keycloak-auth branch November 5, 2025 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants