-
Notifications
You must be signed in to change notification settings - Fork 366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[AzureAD] Add config to map Azure's concept of AppRoles to conclude if a user is allowed and/or if the user is an admin #446
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Igor Beliakov <[email protected]>
Thanks for submitting your first pull request! You are awesome! 🤗 |
Signed-off-by: Igor Beliakov <[email protected]>
@consideRatio Could you, please, let me know who can review the PR? :) Thanks! |
oauthenticator/azuread.py
Outdated
@@ -94,6 +111,16 @@ async def authenticate(self, handler, data=None): | |||
# results in a decoded JWT for the user data | |||
auth_state['user'] = decoded | |||
|
|||
roles = decoded.get("roles", []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for readability.
roles = decoded.get("roles", []) | |
roles = auth_state['user'].get("roles", []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 6dc41a6
I updated the z2jh Helm chart config example with some notes hub:
config:
JupyterHub:
admin_access: true
authenticator_class: azuread
# NOTE: This must be a class that has these traitlets defined, which isn't the base Authenticator class
AzureAdOAuthenticator:
# NOTE: I think we should name this similar to `admin_users`, suggestion below
# Hmm... Not sure on the name but I think we should include "admin_users" in it
app_roles_with_admin_users:
- admin
# NOTE: I think we should name this similar to `allowed_users`, suggestion below
# NOTE: I think we should make the admin app roles be assumed to be valid users as well
# Hmm... Not sure on the name but I think we should include "allowed_users" in it
app_roles_with_allowed_users:
- user What is an app role? Is it a list of users? Is it a property a user can have? Not sure on my naming suggestions because I don't know anything about app roles. |
oauthenticator/azuread.py
Outdated
admin_azure_app_roles = List( | ||
Unicode(), | ||
config=True, | ||
help="App roles that should give Jupyterhub admin privileges to a user", | ||
) | ||
|
||
allowed_azure_app_roles = List( | ||
Unicode(), | ||
config=True, | ||
help="Automatically allow users with selected app roles", | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should make these to be Set
instead of List
. Practically, if anyone passes a List, for example via a YAML configuration that is read and passed to the configuration - then it will be automatically casted to a Set so thats fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also like to have it clarified how to list the app roles. Is it their display name rather than id? That seems a bit odd to me but that is what matches your example configuration, but perhaps it is required to be unique as well or assumed to not risk causing problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a link or something as a reference to learn more about app roles this is relevant as well? I'm not sure, but in general I'm thinking the help text seem a bit too short and could be extended with another sentence or two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should make these to be Set instead of List. Practically, if anyone passes a List, for example via a YAML configuration that is read and passed to the configuration - then it will be automatically casted to a Set so thats fine.
In other implementations for IDPs, I saw both Set
and List
being used, so wasn't sure which of those was the best fit. I'll change that to Set
as per your recommendation.
I'd also like to have it clarified how to list the app roles. Is it their display name rather than id? That seems a bit odd to me but that is what matches your example configuration, but perhaps it is required to be unique as well or assumed to not risk causing problems.
It's rather value
that gets sent in the token claim. It's allowed to have the same displayName
, but not the value
. For the latter, Azure AD will throw an error like this:
Failed to update jupyterhub application. Error detail: It contains duplicate value. Please Provide unique value.
IDs are used by Azure AD internally and they're not exposed in the respective token claim.
Perhaps a link or something as a reference to learn more about app roles this is relevant as well? I'm not sure, but in general I'm thinking the help text seem a bit too short and could be extended with another sentence or two.
Sure, I'll think about what I can add here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to Set
and improved help messages in 6dc41a6
@weisdd thanks for the ping, sorry for the stale feedback you have received. I think this PR makes sense and isn't breaking in any way and is also an opt-in mechanism that is safe to use without breaking something else. I consider this PR to be quite merge-ready with my comments are addressed. Oh btw, I'd also like a consideration if a test could be added to verify some logic of this or if its too complicated or similar. Btw @weisdd and @thomafred, you have both created PRs (this, and #448) to AzureAD - perhaps you can review each others PR as well to help with the maintenance burden? |
This is quite neat, and I like how simple it is. It would certainly help our usecase. A few questions:
|
@consideRatio Thanks for the review :) When a user authenticates via an identity provider (IDP), the IDP may add a claim (=attribute) to identity token and/or access token sent to the client. In Azure AD, identity token is used for that. |
1. Let admins login without explicitly having an allowed app role; 2. Style change for getting a list of roles; 3. Moved to Set traitlets. Signed-off-by: Igor Beliakov <[email protected]>
* (Optionally) Revoke stale admin privileges; * Improve help messages. Signed-off-by: Igor Beliakov <[email protected]>
Signed-off-by: Igor Beliakov <[email protected]>
@consideRatio What do you think about this naming? hub:
config:
JupyterHub:
admin_access: true
authenticator_class: azuread
AzureAdOAuthenticator:
admin_users_app_roles:
- admin
allowed_users_app_roles:
- user
revoke_stale_admin_privileges: true I decided to add # Admin privileges will be revoked if a user is not listed in admin_users
# and then added back if the user has one of admin_users_app_roles.
if self.revoke_stale_admin_privileges:
if userdict["name"].lower() not in self.admin_users:
userdict["admin"] = False
if self.admin_users_app_roles:
if check_user_has_role(roles, self.admin_users_app_roles):
userdict["admin"] = True
return userdict A couple of notes:
if self.revoke_stale_admin_privileges:
admin_users = [admin.lower() for admin in self.admin_users]
if userdict["name"].lower() not in admin_users:
userdict["admin"] = False
Maybe we can only check the logic for assigning admin privileges, though the test is likely to be over-complicated unless the code is moved to a separate function. The latter will make the code look differently from other providers. So, not sure about that. |
@thomafred Unfortunately, I'm don't know Azure deep enough to answer any of those questions (I mostly used Keycloak in the past). I can only say that as long as you're using Azure AD and can create app roles and assign users/usergroups to them, it all should work just fine. :) |
That's the default but it's not mandatory, it's handled by an overridable function: |
Awwww haha I was so happy that this was potentially ready for merge but now there is another feature to consider and such that may not fit under the title of this PR. @weisdd would you mind waiting with adding the revoke admin status logic into a separate PR? I think it makes a lot of sense but I think it would be hard to help readers of a changelog or similar to manage understand the changes of the next release if it bundles into this PR for example. |
Signed-off-by: Igor Beliakov <[email protected]>
@consideRatio Ah, I had thought it would be a good idea to have it included in this PR, though you're right, better to move discussion on it to a separate PR then. I've just removed the feature :) |
@manics thanks for the info! |
Signed-off-by: Igor Beliakov <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for your patience with the review iterations @weisdd!
@mafloh Could you, please, take a look at the PR? :) Thanks! |
Is it possible you meant @manics? :) |
@consideRatio I'm leaving my current company in two weeks, might not have a proper test bed later on. Any chance to either merge the PR or to request additional changes soon? :) |
Any updates? |
@manics Would it possible to get this merged? |
This PR is a suggested solution for #445 (opened by me).
Since it's the first time I try to do something with Oauth2 in Python, I'd be grateful if you bear with me for all potential defects my implementation might have.
So, I suggest to rely on App roles to give admin privileges to a user and to decide whether he/she is even allowed to enter JupyterHub.
It requires the following configuration to be done on the AzureAD side:
Azure Active Directory -> App registrations -> [Name] -> Manifest:
Note: UUIDs can be generated with the help of
uuidgen
.The second step would be:
Enterprise applications -> [NAME] -> Users and Groups -> [assign some of the above-mentioned roles to user groups]
Then, you'll need to deploy jupyterhub (
zero-to-jupyterhub
) with the following values (requires to install the modified version ofazuread.py
):After that, any user with the
admin
role assigned would get admin permissions and only those who have eitheruser
oradmin
role would be allowed to enter JupyterHub.Additional notes: