Give service-account OIDC clients Factory+ principals#688
Merged
Conversation
The Auth service's legacy authz/acl endpoint routes UUID principals via /v2/acl/<uuid> when by-uuid is set. JWT-authenticated MQTT clients are identified by fp_principal_uuid - the only identity a Keycloak service account has - so the broker needs this form.
preferred_username on a service-account token is a Keycloak-local name F+ Auth has never heard of, so ACL lookup by UPN finds nothing. Prefer the fp_principal_uuid claim (stamped on every token our realm issues) and route UUID-shaped principals through the Auth service's by-uuid lookup; kerberos UPNs always contain '@' so the shapes never collide. preferred_username survives as a fallback for tokens minted before the claim mappers existed, and the claims verifier no longer hard-requires it since client-credentials tokens may omit it.
A Keycloak service account is a local user, not one federated from F+ Auth, so the SPI claim mappers had nothing to serve and its tokens carried no F+ identity: the HTTP services rejected them outright and the MQTT broker found an empty ACL. Create a Principal object in ConfigDB for each serviceAccountsEnabled client and stamp its UUID as a user attribute on the service-account user; the mappers read attributes generically so no SPI change is needed, and admins grant the principal permissions through the standard ACL editor. The Keycloak attribute is the source of truth for the UUID so re-runs and redeploys keep it stable.
AlexGodbehere
added a commit
that referenced
this pull request
Jul 23, 2026
## Summary The ACL editor's principal list was built from `auth.list_principals()`, which only returns principals holding an identity record (kerberos/sparkplug). The F+ principals service-setup creates for OIDC service accounts (#688) deliberately have no identity, so they could hold grants but were invisible in the editor - there was no way to grant a wall display its MQTT read permission from the UI. - `usePrincipalStore` unions in the members of the ConfigDB Principal class (recursive membership, so all principals are covered); identity-less ones get `kerberos: null`. - The Principal column renders a muted "No identity" placeholder for them; the kerberos filter list drops nulls; the management sidebar hides the empty copyable; group-removal dialogs fall back to the principal's name. - Grant creation needed no changes: it operates on `principal.uuid` via the principal's Permissions tab, which now simply becomes reachable for service accounts. ## How to test 1. On a cluster with a service-account client (amrc-fp has polyptic-kiosk), open the admin UI's Access Control → Principals. 2. "Service account: Polyptic kiosk" should appear, Principal column showing "No identity". 3. Open it → Permissions tab → grant MQTT ReadWholeNamespace with wildcard target. Confirm the grant lands (`/v2/acl/<uuid>` returns it). 4. Existing kerberos principals render and filter exactly as before. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Client-credentials (service-account) tokens previously carried no F+ identity: a Keycloak service account is a local user, not one federated from F+ Auth, so the SPI claim mappers omitted
fp_principal_uuidentirely. The HTTP services rejected such tokens outright ("JWT missing fp_principal_uuid claim") and the MQTT broker looked uppreferred_username(service-account-<client>) and found an empty ACL. This blocked the intended visualiser-wall pattern: a kiosk client that mints its own long-lived JWTs via client-credentials.serviceAccountsEnabledclient ("Service account: " in the ACL editor) and stamps its UUID as a user attribute on the Keycloak service-account user. The SPI mappers read attributes generically, so no SPI change is needed - the claim appears in tokens automatically. The Keycloak attribute is the source of truth, so the UUID is stable across re-runs.fp_principal_uuid(falling back topreferred_username) and routes UUID principals through the Auth service's by-uuid ACL lookup. UPNs always contain@so the shapes never collide.getACLgains a by-uuid variant using the existingby-uuidparameter onauthz/acl- no Auth service changes.A fresh service account holds no grants: admins grant its principal MQTT read (plus Directory/ConfigDB read) via the standard ACL editor before a wall shows anything.
How to test
visualiser-kioskwithserviceAccountsEnabled: true,standardFlowEnabled: false, longaccessTokenLifespan), upgrade, let service-setup run.curl -d client_id=visualiser-kiosk -d client_secret=<from keycloak-clients secret> -d grant_type=client_credentials .../token- the JWT payload should containfp_principal_uuid.https://visualiser.<base>/?auth_token=<JWT>- the wall should authenticate AND see data; broker logs show the ACL for the principal UUID.🤖 Generated with Claude Code