jupyterhub / ldapauthenticator

LDAP Authenticator Plugin for Jupyter
BSD 3-Clause "New" or "Revised" License
202 stars 176 forks source link

How to specify another username than the one used for login #164

Open Kent1 opened 4 years ago

Kent1 commented 4 years ago

Hi,

I already saw the same question in multiple issues but never an answer.

I have an AD and i use the userPrincipalName for login (first.last@example.com). That's corporate policy, they want to use email address as login.

However, i need to create unix users like first.last@example.com while i would prefer to use sAMAccountName as username once logged in. Is there a way to do it ?

Thanks, Quentin

kinow commented 4 years ago

Hi @Kent1

That's quite common I think. I worked in two telecoms, both used different policies in their AD for which field to allow to use for log in, and which value should be displayed in systems (sometimes samaccountname, sometimes displayName, or another custom field from their schema, etc.).

I used dwimberger/ldap-ad-it docker image to quickly get an OpenLDAP server with structure similar to AD.

And created a user there with uid=bruno@email.com (corporate e-mail) and samaccountname=kinow (actual user name). I think that's similar to your use case?

image

Then used the following settings in jupyterhub_config.py:

c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
c.LDAPAuthenticator.server_address = 'localhost'
c.LDAPAuthenticator.lookup_dn = True
c.LDAPAuthenticator.server_port = 10389
c.LDAPAuthenticator.lookup_dn_search_user = 'uid=admin,ou=system'
c.LDAPAuthenticator.lookup_dn_search_password = 'secret'
c.LDAPAuthenticator.user_attribute = 'uid'
c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'
c.LDAPAuthenticator.user_search_base = 'ou=users,dc=wimpi,dc=net'
c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'samaccountname'
c.LDAPAuthenticator.escape_userdn = False
# https://emailregex.com/
c.LDAPAuthenticator.valid_username_regex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"
# c.LDAPAuthenticator.bind_dn_template = '{username}'

The c.LDAPAuthenticator.user_attribute = 'uid' value is what gets replaced in c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})', a simple LDAP search. You can tweak it to whatever you have in your company.

If you use an e-mail, then c.LDAPAuthenticator.valid_username_regex will have to be changed to permit e-mails with @ and other symbols.

Finally, c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'samaccountname' is what defines the value used in JupyterHub.

So once I logged in with my e-mail, the URL spawned was http://localhost:8000/user/kinow.

[D 2020-07-09T12:15:51.104 JupyterHub ldapauthenticator:379] Attempting to bind kinow with uid=bruno@email.com,ou=users,dc=wimpi,dc=net
[D 2020-07-09T12:15:51.253 JupyterHub ldapauthenticator:392] Status of user bind kinow with uid=bruno@email.com,ou=users,dc=wimpi,dc=net : True
[D 2020-07-09T12:15:51.260 JupyterHub base:522] Setting cookie jupyterhub-session-id: {'httponly': True}
[D 2020-07-09T12:15:51.260 JupyterHub base:526] Setting cookie for kinow: jupyterhub-hub-login
[D 2020-07-09T12:15:51.260 JupyterHub base:522] Setting cookie jupyterhub-hub-login: {'httponly': True, 'path': '/hub/'}
[I 2020-07-09T12:15:51.260 JupyterHub base:707] User logged in: kinow
[D 2020-07-09T12:15:51.260 JupyterHub user:242] Creating <class 'jupyterhub.spawner.LocalProcessSpawner'> for kinow:
[I 2020-07-09T12:15:51.263 JupyterHub log:174] 302 POST /hub/login?next= -> /hub/spawn (kinow@::ffff:127.0.0.1) 464.19ms
[D 2020-07-09T12:15:51.300 JupyterHub pages:194] Triggering spawn with default options for kinow
[D 2020-07-09T12:15:51.300 JupyterHub base:825] Initiating spawn for kinow
[D 2020-07-09T12:15:51.300 JupyterHub base:829] 0/100 concurrent spawns
[D 2020-07-09T12:15:51.300 JupyterHub base:834] 0 active servers
[D 2020-07-09T12:15:51.357 JupyterHub user:556] Calling Spawner.start for kinow
[I 2020-07-09T12:15:51.358 JupyterHub spawner:1417] Spawning cylc-uiserver --port=40277 -s /home/kinow/Development/python/workspace/cylc-uiserver/../cylc-ui/dist
Failed to set groups [Errno 1] Operation not permitted
[D 2020-07-09T12:15:51.378 JupyterHub spawner:1113] Polling subprocess every 30s
2020-07-09 12:15:52,024 cylc.uiserver.main INFO     JupyterHub Service Prefix: /user/kinow/

Hope that helps Bruno

Kent1 commented 4 years ago

Hey Bruno,

Thanks for taking the time to look into it. In the meantime, i went through with the full email.

In the official doc, they say to use CN for Active Directory. It just tested with setting sAMAccountName in the lookup_dn_user_dn_attribute but then i cannot login anymore. Jupyter is not able to validate the credentials. I think it is really specific problem when using an AD.

Quentin

edergillian commented 4 years ago

I have the exact same issue here, AD in my company has CN's with spaces, wish I could change the attribute I get the JupyterHub username from!

Krayalin commented 4 years ago

@kinow example is right. You can change which attribute is returned by the LDAPAuthenticator. I just ran an experiment against my LDAP setup which is using AD behind the scenes. Here's my config:

c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator' c.LDAPAuthenticator.server_address = 'SomeServer' c.LDAPAuthenticator.lookup_dn = True c.LDAPAuthenticator.lookup_dn_search_user = 'SomeUser' c.LDAPAuthenticator.lookup_dn_search_password = 'SomePassword' c.LDAPAuthenticator.user_search_base = 'DC=SomeDomainComponent,DC=SomeOtherDomainComponent' c.LDAPAuthenticator.user_attribute = 'mail' c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'sAMAccountName' c.LDAPAuthenticator.valid_usernameregex = '^[a-z][.a-z0-9@-]*$' c.LDAPAuthenticator.allowed_groups = ["SomeDNForAGroup"]

If I understand this right, when I'm searching the LDAP server I'm specifically searching for entries who's user_attribute (in this cause I'm looking for mail), matches what the user passed as their username (which would be their email address). This would follow suite with the lookup_dn_search_filter definition of ({login_attr}={login}), which translates to mail=SomeEmail. If there's a matching entry and the user passed the correct password then return the sAMAccountName property.

When I run this against my server I authenticate, but because LDAPAuthenticator doesn't make local user accounts, I bomb with getpwnam(): name not found: 'sAMAccountName'. That's okay, but I know for sure JupyterHub is getting the sAMAccountName instead of CN or the email address.

edergillian commented 4 years ago

I actually managed to get my setup working by using this extra config, as there's no direct translation from the auth hash to the ldap configuration in the Z2JH helm chart:

hub:
  extraConfig:
    ldap_username_fix.py: |
      c.LDAPAuthenticator.use_lookup_dn_username = False

That made the access URL go from /user/<CN>/... to /user/<sAMAccountName>/..., as my auth.ldap.dn.user.attribute is sAMAccountName.