Skip to content

fix(gateway): pass NamespaceConfig by pointer so OIDC config reaches gateway.toml - #132

Merged
bsquizz merged 2 commits into
mainfrom
fix/keycloak-oidc-pass-by-pointer
Aug 14, 2026
Merged

fix(gateway): pass NamespaceConfig by pointer so OIDC config reaches gateway.toml#132
bsquizz merged 2 commits into
mainfrom
fix/keycloak-oidc-pass-by-pointer

Conversation

@bsquizz

@bsquizz bsquizz commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two bugs in the Keycloak OIDC provisioning path:

  • OIDC config lost on first provision: reconcileKeycloakClient took nsConfig NamespaceConfig by value, so its OIDC mutation was invisible to the caller. deployGateway rendered gateway.toml without the [openshell.gateway.oidc] section. The UpdateOIDC callback persisted OIDC to the API server, but the phase gate blocked re-reconciliation, so gateways never self-healed. Fix: pass *NamespaceConfig.

  • Keycloak client ID missing resource ID suffix: The spec requires clientId = "{name}-{id}" to prevent collisions when gateways share a name across fleets or are deleted/recreated. The code used just the gateway name everywhere — provisioning, deletion, and role assignment. Fix: compute fmt.Sprintf("%s-%s", name, id) in all three paths.

Observed on openshell-fc593ad6a828a608 — gateway deployed with allow_unauthenticated_users = false but no [openshell.gateway.oidc] section.

Test plan

  • Deploy a new gateway with Keycloak enabled and verify gateway.toml contains [openshell.gateway.oidc] on first provision
  • Verify the Keycloak client ID is {name}-{id} format (e.g., my-gateway-2FhMpQzXBz)
  • Verify RoleBinding reconciler resolves the same {name}-{id} client ID when assigning roles
  • Delete a gateway and verify the correct {name}-{id} Keycloak client is cleaned up
  • Existing gateways need re-provisioning (reset phase or delete+recreate) since existing Keycloak clients use the old name-only format
  • go vet ./... passes
  • go build ./... passes

🤖 Generated with Claude Code

bsquizz and others added 2 commits August 14, 2026 16:52
reconcileKeycloakClient received nsConfig by value, so its OIDC
mutation (nsConfig.Gateway.OIDC = oidcConfig) was invisible to the
caller.  deployGateway then rendered gateway.toml with an empty OIDC
config, producing gateways without an [openshell.gateway.oidc] section.

The UpdateOIDC callback persisted the OIDC data to the API server, but
the phase gate prevented re-reconciliation, so the gateway never
self-healed.

Pass *NamespaceConfig so the OIDC config is visible when deployGateway
renders the ConfigMap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec requires clientId = "{name}-{id}" to prevent name clashes
when gateways share a name across fleets or are deleted and recreated.
The code was using just the gateway name, so two gateways named
"my-gateway" would collide in Keycloak.

Apply the {name}-{id} format in all three paths:
- reconcileKeycloakClient (provisioning)
- DeleteGatewayResources (cleanup)
- RoleBindingReconciler.resolveKeycloakClientID (role assignment)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@markturansky

Copy link
Copy Markdown
Collaborator

claude didn't make any tests?

@bsquizz bsquizz left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Amber Review

Both changes are correct and spec-aligned. The pass-by-pointer fix resolves the OIDC config not reaching gateway.toml, and the {name}-{id} client ID format matches the Keycloak provisioning spec. Three inline observations below — none are blockers.

Confidence: High (95%)

Finding Severity Verdict
Duplicated {name}-{id} format string across 3 sites Minor Consider extracting a helper to prevent drift
DeleteGatewayResources silently skips Keycloak cleanup when GatewayID is empty Minor Correct guard, but could orphan old clients
No test coverage for reconcileKeycloakClient or resolveKeycloakClientID Major Pre-existing gap, not introduced here

if err := opts.KeycloakClient.DeleteGatewayClient(ctx, opts.GatewayName); err != nil {
log.Printf("WARN failed to delete keycloak client %s (orphaned): %v", opts.GatewayName, err)
if opts.KeycloakClient != nil && opts.GatewayName != "" && opts.GatewayID != "" {
kcClientID := fmt.Sprintf("%s-%s", opts.GatewayName, opts.GatewayID)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Amber — Minor: The {name}-{id} format string (fmt.Sprintf("%s-%s", ...)) now appears in three places: here, reconcileKeycloakClient (line 903), and resolveKeycloakClientID in role_binding_reconciler.go. If the format ever changes (e.g. a separator change for Keycloak compatibility), all three must be updated in lockstep.

Consider extracting a small helper:

func keycloakClientID(gatewayName, gatewayID string) string {
    return fmt.Sprintf("%s-%s", gatewayName, gatewayID)
}

Not a blocker — three call sites is borderline — but it would eliminate a drift risk.

if opts.KeycloakClient != nil && opts.GatewayName != "" {
if err := opts.KeycloakClient.DeleteGatewayClient(ctx, opts.GatewayName); err != nil {
log.Printf("WARN failed to delete keycloak client %s (orphaned): %v", opts.GatewayName, err)
if opts.KeycloakClient != nil && opts.GatewayName != "" && opts.GatewayID != "" {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Amber — Minor: Adding opts.GatewayID != "" to the guard is correct — without it we'd construct a malformed client ID. However, this is a behavioral change: gateways that were previously cleaned up using only GatewayName will now silently skip Keycloak cleanup if GatewayID is empty.

If any gateways were provisioned under the old {name}-only format (before this PR), their Keycloak clients would be orphaned on deletion since the new code won't find them. If this is the first deployment with Keycloak enabled, this is a non-issue. Otherwise, a one-time migration/cleanup script for existing clients may be needed.

Confidence: Medium — depends on whether any gateways exist with old-format client IDs.


if existingUUID != "" {
log.Printf("INFO keycloak client %s already exists (uuid=%s), skipping provisioning", gatewayName, existingUUID)
log.Printf("INFO keycloak client %s already exists (uuid=%s), skipping provisioning", kcClientID, existingUUID)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Amber — Note (pre-existing): This skipping provisioning path is a create-or-skip pattern. Per CLAUDE.md: "Reconcile, don't create-or-skip: Use update-or-create patterns." Ideally this would verify the existing client's configuration (roles, mappers, fullScopeAllowed, redirect URIs) matches the spec and update if needed.

Not introduced by this PR and not a blocker, but worth noting since the Keycloak spec requires specific client properties (fullScopeAllowed=false, PKCE, scopes) that could drift if the client was provisioned by a previous code version.

return "", fmt.Errorf("get gateway %s: %w", gatewayID, err)
}
return resp.GetGateway().GetName(), nil
return fmt.Sprintf("%s-%s", resp.GetGateway().GetName(), gatewayID), nil

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Amber — Looks good. The format matches the gateway reconciler and the spec (clientId = "{name}-{id}"). The updated comment accurately describes the returned value.

@bsquizz bsquizz added amber/approved The Amber review agent has approved this PR. amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR. labels Aug 14, 2026
@bsquizz
bsquizz added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit 743cbc5 Aug 14, 2026
12 of 13 checks passed
@bsquizz
bsquizz deleted the fix/keycloak-oidc-pass-by-pointer branch August 14, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amber/approved The Amber review agent has approved this PR. amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants