Directory sync
Read the Identity overview first for the IdentityProvider
resource, userInfoPrefix, and claim mappings. A directory is an optional
addition to a provider you already have working for login.
Claims tell Hub who a caller is. A directory lets Hub ask the IdP what groups and users exist, so you can search for a subject when writing a role binding instead of copying strings out of a decoded token.
A directory also rescues the cases where the token can't carry the answer:
- Entra ID drops the
groupsclaim entirely for users in more than 200 groups — its JWT overage limit — substituting pointers Hub doesn't follow. - Okta only emits the groups its claim filter matches.
In both cases the directory reads membership from the provider's API, which isn't subject to token size limits.
Enable a directory
Add a directory block to the IdentityProvider. Hub queries the provider's own
API with credentials you supply:
kind: IdentityProvider
spec:
directory:
groupClaimType: DirectoryID # or DisplayName
userClaimType: DirectoryID # or Email
config:
apiVersion: directory.hub.upbound.io/v1alpha1
kind: EntraConfiguration
# ...provider-specific fields
Both claim types default to DirectoryID when omitted.
The kind selects the provider:
kind | Queries | Authenticates with |
|---|---|---|
EntraConfiguration | Microsoft Graph | Client secret (setup) |
OktaConfiguration | Okta Management API | Signed JWT assertion (setup) |
KeycloakConfiguration | Keycloak Admin REST API | Client secret |
Directory credentials are encrypted at rest and read back as ***. Send ***
in a PUT to keep the stored value, or a real value to rotate it. Provider URLs
must use HTTPS.
Matching directory subjects to role bindings
This is the part that most often goes wrong. Two names have to agree.
Everything the directory returns — groups and users alike — is named
<identityprovider-name>:<claim value>. The claim-type fields pick which
directory field becomes that value:
| Subject | Field | DirectoryID (default) | Alternative |
|---|---|---|---|
| Group | groupClaimType | The provider's internal group ID | DisplayName — the group's name |
| User | userClaimType | The provider's internal user ID | Email — the user's email address |
So a directory group is <identityprovider-name>:<group name or ID>, and a
directory user is <identityprovider-name>:<email or ID> — depending on which
side of that switch you're on.
A signed-in caller, meanwhile, is named <userInfoPrefix><claim value> — the
username and groups Hub read out of their token.
Role bindings match on the string, so a binding written against a subject you found in the directory only applies to real callers when both halves line up. In practice that means:
- Set
userInfoPrefixto<identityprovider-name>:so the two prefixes agree. - Set each claim type to match what your tokens actually carry. Entra ID sends
group object IDs, so it wants
groupClaimType: DirectoryID; Okta sends group names, so it wantsDisplayName. For users, matchuserClaimTypeto whateverclaimMappings.usernamemaps —Emailif you mapped theemailclaim,DirectoryIDif you left the username onsub.
Get this wrong and the binding is silently inert — it names a subject nobody
resolves to. Confirm a real caller's identity with kubectl auth whoami (see
Verifying your identity) and check it matches the
names the directory returns. This restriction is planned to be lifted in future
versions of the Hub.
A name-carrying claim type (DisplayName, Email) makes bindings readable but
depends on that field being populated: Hub rejects a group with no display name
when groupClaimType: DisplayName, and a user with no email when
userClaimType: Email. DirectoryID always resolves, at the cost of opaque
bindings.
Query the directory
GET /apis/authentication.hub.upbound.io/v1beta1/groups?identityProvider=<name>
GET /apis/authentication.hub.upbound.io/v1beta1/groups?identityProvider=<name>&displayNameQuery=<prefix>
GET /apis/authentication.hub.upbound.io/v1beta1/users?identityProvider=<name>&group=<group-resource-name>
identityProvider is required on both endpoints. Listing users additionally
requires group — there's no "every user in the directory" query, only
"members of this group". Pass the group's full resource name, prefix included,
as the groups endpoint returned it.
Behaviors to expect:
displayNameQueryis a prefix match, not a substring search. Query the start of a group's name, not a word in the middle.- Group search considers at most 200 matches per query — Hub's own cap, not the Entra overage limit above. A group missing from a broad search usually falls outside that set, so narrow the query rather than paging.
- Group results are cached for five minutes per provider and query, so a group you just created in the IdP may take that long to appear. User lists share that cache when they resolve the group.
- User lists page at 50 by default, up to 200 per page.
Related resources
- Identity overview — the
IdentityProviderresource these blocks attach to. - Verifying your identity — reading back the names a real caller resolves to.
- Microsoft Entra ID and Okta — the two providers whose directory setup needs its own app registration.
- Access management — writing the role bindings these names go into.