OIDC - oidc.groups.claim is added as additional scope which breaks Microsoft Entra integration
I just worked on integrating LXD with Microsoft Entra ID. When you create an app registration in Entra for LXD, you can add a group claim to your token configuration. That claim is called "groups". If you then set oidc.groups.claim = "groups", when you attempted to log in, LXD adds "groups" as an additional requested scope. This caused Entra to return an error because that scope does not exist.
Claims and scopes are two different things, and you shouldn't assume that there will be a scope with the same name as your groups claim. I would suggest that you create a separate configuration parameter for users to specify any additional scopes that might be needed (maybe "oidc.additional_scopes"?)
As a work around, I had to choose a scope that Entra would consider valid, and then create a Custom claims provider to inject a claim with that same name into the token.
This makes sense, as you say claims and scopes are different things. I'm not sure about oidc.additional_scopes though, because LXD has no need for general additional information that may be requested.
A backwards compatible change might be oidc.groups.scope which will be used if present, falling back to oidc.groups.claim.
This issue is also relevant when trying to use GitLab as an identity provider. GitLab shares the groups a user is a member of as groups and groups_direct claims, which are included in the openid scope. However, requesting groups or groups_direct as a scope, will cause the requests to be rejected due to invalid scopes.
However, I also ran into another problem with GitLab due to the offline_access scope, which would neither be solved by having an oidc.additional_scopes setting, nor by introducing an oidc.groups.scope setting.
What do you think about instead introducing an oidc.scopes setting instead, which overwrites the current behavior of adding the groups claim as an additional scope as well as the default scopes that are requested. This change would be backword-compatible by reverting back to the current behavior if not defined, and enables lxd to work with more peculiar identity providers, such as GitLab.
I created a pull request to add this setting, as a starting point for discussion :)
Hi @ech0-de,
This issue is multifaceted so I'll summarise my thinking first:
- As you say, the
offline_accessscope is included in the OIDC specification, but is marked as optional. However, we do generally need this scope to request refresh tokens from most providers. This is especially necessary for the CLI. - Some scopes are absolutely required for LXD to function, these are
openid, andemail. Theprofilescope is not required, but is used to improve usability from an admin perspective (e.g. to identify users). - The
oidc.groups.claimconfig key should never have been used as a scope, but now we need to think about backwards compatibility. - The
oidc.groups.claimconfig key can't be used with Gitlab, since it expects the claim to be present with the same name in both the identity token and in the contents of the UserInfo response (whereas Gitlab differentiates these withgroupsandgroups_direct). - The extraction of IdP groups is quite naive and expects a
[]string. I've found that it cannot be used with Keycloak and I've no doubt it is incompatible with many more providers.
Your oidc.scopes PR goes a long way to address these shortfalls. However, I don't think a user of LXD should be able to remove the openid or email scopes that are required for LXD to function. So I'd like to go back to @mrbranan's initial request for oidc.additional_scopes with a few tweaks.
- Always request the
openidandemailscopes. This is not configurable. - Introduce an
oidc.additional_scopesconfig key. This will be a comma delimited list that includesprofileandoffline_accessby default. - Add a patch to LXD that will take the value of
oidc.groups.claimand add it tooidc.additional_scopes. - Remove
oidc.groups.claimfrom the list of scopes.
After implementing this, it will be possible to remove the offline_access scope and the oidc.groups.claim value will no longer be used as a scope. Is this amenable to you both?
Future work will also include allowing a comma delimited list of values in oidc.groups.claim. We will attempt to retrieve the claims in order to collect the IdP groups. Additionally, we will (likely) introduce another configuration key oidc.groups.claim_scriptlet which will be used to retrieve the identity provider groups.
@ech0-de @mrbranan With the addition of the oidc.scopes configuration key in #14935 you can now remove offline_access and the oidc.groups.claim config key is no longer used as a scope. The feature is only available in latest/edge at the moment.
Apologies for the turn around time on this. If you could confirm it is now working for you that would be great.
Apologies for the turn around time on this. If you could confirm it is now working for you that would be great.
Sorry for the delayed reply. I just tested the changes and it works great for my use case, thanks for working on this!