Skip to content

fix(keycloak): verifyAudienceMapper defense-in-depth never runs due to early return in getOrCreateAudienceClientScope #358

Description

@pdettori

Bug

PR #350 added verifyAudienceMapper as a defense-in-depth self-healing check that runs on every reconcile. However, it never executes when the audience scope is in a broken state because getOrCreateAudienceClientScope returns an error first, causing EnsureAudienceScope to early-return before reaching verifyAudienceMapper.

Reproduction

Deploy an agent (e.g. a2a-currency-converter) where the Keycloak scope exists but has a corrupted/mismatched mapper (same name, different type or missing oidc-audience-mapper). The operator logs show repeated failures:

"error":"ensure audience mapper for existing scope \"agent-team1-a2a-currency-converter-aud\": no matching audience mapper found for scope \"agent-team1-a2a-currency-converter-aud\" (scopeID a4ad2709-399c-482d-804c-d699ae165df0)"

The operator retries on each reconcile trigger but never self-heals.

Root Cause

In audience.go at the rc.4 code (EnsureAudienceScope):

scopeID, err := a.getOrCreateAudienceClientScope(ctx, token, p.Realm, scopeName, p.AudienceClientID)
if err != nil {
    return err  // ← early return, verifyAudienceMapper never reached
}
if err := a.verifyAudienceMapper(ctx, token, p.Realm, scopeID, scopeName, p.AudienceClientID); err != nil {
    // ← defense-in-depth that would fix it, but unreachable
}

The control flow:

  1. getOrCreateAudienceClientScope → scope exists → calls ensureAudienceMapper
  2. ensureAudienceMapper POSTs a new mapper → Keycloak returns 409 Conflict (name collision with a stale/corrupted mapper)
  3. On 409 → updateAudienceMapperIfNeeded lists mappers → finds none matching Name == scopeName AND ProtocolMapper == "oidc-audience-mapper"
  4. Returns error "no matching audience mapper found" — propagated up through getOrCreateAudienceClientScope
  5. EnsureAudienceScope returns the error without ever reaching verifyAudienceMapper

Expected Behavior

The operator should self-heal the broken scope by either:

  • Deleting the stale/mismatched mapper and recreating the correct oidc-audience-mapper, OR
  • Running verifyAudienceMapper regardless of whether ensureAudienceMapper succeeds (since verify already handles the "mapper missing" case by calling ensureAudienceMapper itself)

Suggested Fix

Option A: In updateAudienceMapperIfNeeded, when no matching oidc-audience-mapper is found, delete any mapper with the conflicting name and re-POST the correct one.

Option B: Restructure getOrCreateAudienceClientScope to return (scopeID, error) even on ensureAudienceMapper failure (since the scope ID IS known), and let verifyAudienceMapper handle the repair. This preserves the defense-in-depth design from PR #350.

Impact

Any agent whose audience scope gets into this state (mapper name exists but type doesn't match oidc-audience-mapper) will permanently fail auth validation (401) with no self-healing. Manual Keycloak intervention is required.

Environment

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghigh priorityMust be address ASAP

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions