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:
getOrCreateAudienceClientScope → scope exists → calls ensureAudienceMapper
ensureAudienceMapper POSTs a new mapper → Keycloak returns 409 Conflict (name collision with a stale/corrupted mapper)
- On 409 →
updateAudienceMapperIfNeeded lists mappers → finds none matching Name == scopeName AND ProtocolMapper == "oidc-audience-mapper"
- Returns error
"no matching audience mapper found" — propagated up through getOrCreateAudienceClientScope
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
Bug
PR #350 added
verifyAudienceMapperas 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 becausegetOrCreateAudienceClientScopereturns an error first, causingEnsureAudienceScopeto early-return before reachingverifyAudienceMapper.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 missingoidc-audience-mapper). The operator logs show repeated failures:The operator retries on each reconcile trigger but never self-heals.
Root Cause
In
audience.goat the rc.4 code (EnsureAudienceScope):The control flow:
getOrCreateAudienceClientScope→ scope exists → callsensureAudienceMapperensureAudienceMapperPOSTs a new mapper → Keycloak returns 409 Conflict (name collision with a stale/corrupted mapper)updateAudienceMapperIfNeededlists mappers → finds none matchingName == scopeName AND ProtocolMapper == "oidc-audience-mapper""no matching audience mapper found"— propagated up throughgetOrCreateAudienceClientScopeEnsureAudienceScopereturns the error without ever reachingverifyAudienceMapperExpected Behavior
The operator should self-heal the broken scope by either:
oidc-audience-mapper, ORverifyAudienceMapperregardless of whetherensureAudienceMappersucceeds (since verify already handles the "mapper missing" case by callingensureAudienceMapperitself)Suggested Fix
Option A: In
updateAudienceMapperIfNeeded, when no matchingoidc-audience-mapperis found, delete any mapper with the conflicting name and re-POST the correct one.Option B: Restructure
getOrCreateAudienceClientScopeto return(scopeID, error)even onensureAudienceMapperfailure (since the scope ID IS known), and letverifyAudienceMapperhandle 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
a2a-currency-converterin team1 namespace