jupyterhub / ldapauthenticator

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

Password update from LDAP break authentication #217

Closed oeilnc closed 2 months ago

oeilnc commented 1 year ago

Hello, Thank you so much for your work, this is very nice.

Bug description

I'm using your lib for my Jupyterhub and LDAP but I'm facing a weird issue. I suppose this is a misconfiguration, but I cannot see what.

I use Docker to run my Jupyterhub and my Dockerfile looks like this :

FROM jupyterhub/jupyterhub:3.1.1

# Configure jupyterhub
RUN pip install jupyterhub-ldapauthenticator jupyter-server dockerspawner
COPY config/jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py

# Required to disable TLS with LDAP
RUN sed -i 's/ldap3.AUTO_BIND_NO_TLS if self.use_ssl else ldap3.AUTO_BIND_TLS_BEFORE_BIND/ldap3.AUTO_BIND_NO_TLS if not self.use_ssl else ldap3.AUTO_BIND_TLS_BEFORE_BIND/g' \
/usr/local/lib/python3.10/dist-packages/ldapauthenticator/ldapauthenticator.py

As you can see I need to change your code in order to make LDAP working without TLS.

Here my jupyter config :

import os

# launch with docker
c.JupyterHub.spawner_class = "docker"

# we need the hub to listen on all ips when it is in a container
c.JupyterHub.hub_ip = '0.0.0.0'
# the hostname/ip that should be used to connect to the hub
# this is usually the hub container's name
c.JupyterHub.hub_connect_ip = os.getenv('JUPYTER_CONTAINER_NAME') or 'jupyterhub'

# pick a docker image. This should have the same version of jupyterhub
# in it as our Hub.
c.DockerSpawner.image = os.getenv('NOTEBOOK_IMAGE_NAME') or 'oeil-notebook'

# tell the user containers to connect to our docker network
c.DockerSpawner.network_name = os.getenv('JUPYTER_NETWORK_NAME') or 'jupyterhub_network'

# delete containers when the stop
c.DockerSpawner.remove = True

# required to mount and persist user volume
NOTEBOOK_DIR = '/home/jovyan/work'
COMMUN_DATA_PATH = os.getenv('JUPYTER_DATA_PATH')
c.DockerSpawner.notebook_dir = NOTEBOOK_DIR
c.DockerSpawner.extra_create_kwargs = {'user': 'root'}
c.DockerSpawner.environment = {
    "CHOWN_HOME": "yes",
    "CHOWN_EXTRA": "/home/jovyan",
    "CHOWN_HOME_OPTS": "-R",
    "NB_UID": 1000,
    "NB_GID": 1000,
}

c.DockerSpawner.volumes = {
    f"{COMMUN_DATA_PATH}": f"{NOTEBOOK_DIR}"

}

# Doc : https://github.com/jupyterhub/ldapauthenticator
c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
c.LDAPAuthenticator.server_address = 'my_ip'
c.LDAPAuthenticator.server_port = 389
c.LDAPAuthenticator.use_ssl = False
c.LDAPAuthenticator.lookup_dn = True
c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'
c.LDAPAuthenticator.lookup_dn_search_user = 'CN=my_user,OU=COMPTES DE SERVICE,OU=OEIL,DC=oeil,DC=local'
c.LDAPAuthenticator.lookup_dn_search_password = 'mypassword'
c.LDAPAuthenticator.user_search_base = 'OU=UTILISATEURS,OU=OEIL,DC=oeil,DC=local'
c.LDAPAuthenticator.user_attribute = 'sAMAccountName'
c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'
c.LDAPAuthenticator.escape_userdn = False
c.LDAPAuthenticator.bind_dn_template = '{username}'

I only put what I changed in config file, not the entire config file.

Everything was working fine, except when one of my colleague changed his password in AD.

Actual behaviour

Since that, he cannot log into my Jupyterhub serveur because of "Invalid password for user ...". Even with his old password it's not working and I cannot understand why.

I tried to remove and restart my container in order to recreate sql database and cookie but still the same issue, he cannot log in anymore no matter what password is used.

How to reproduce

You can reproduce by running Jupyterhub with my Dockerfile, dockerspawn will fail but you can test it with your own LDAP server.

Thank you for your time.

Your personal set up

Full environment ``` # paste output of `pip freeze` or `conda list` here ```
Configuration ```python # jupyterhub_config.py ```
Logs ``` # paste relevant logs here, if any ```
welcome[bot] commented 1 year ago

Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! :hugs:
If you haven't done so already, check out Jupyter's Code of Conduct. Also, please try to follow the issue template as it helps other other community members to contribute more effectively. welcome You can meet the other Jovyans by joining our Discourse forum. There is also an intro thread there where you can stop by and say Hi! :wave:
Welcome to the Jupyter community! :tada:

consideRatio commented 2 months ago

I'm not sure what has has gone wrong =/

consideRatio commented 2 months ago

Hey @oeilnc, are you still around?

I'm curious if the new password contained non-trivial characters such as @ for example, while the old didn't? I'm looking into if there could be a bug that we need to escape such characters before passing them onwards to the LDAP server or similar.

consideRatio commented 2 months ago

Reading from https://datatracker.ietf.org/doc/html/rfc4511#section-4.2, I wonder if we are doing everything below:

Textual passwords (consisting of a character sequence with a known character set and encoding) transferred to the server using the simple AuthenticationChoice SHALL be transferred as UTF-8 [RFC3629] encoded [Unicode]. Prior to transfer, clients SHOULD prepare text passwords as "query" strings by applying the SASLprep [RFC4013] profile of the stringprep [RFC3454] algorithm. Passwords consisting of other data (such as random octets) MUST NOT be altered. The determination of whether a password is textual is a local client matter.

Searching for saslprep, I found this code: https://github.com/cannatag/ldap3/blob/86a9e7afedba7f7ca9eeb43710054abaf654539b/ldap3/protocol/sasl/sasl.py#L125

Maybe we need to apply that to passwords before passing them to the LDAP3 server?

Hmm I think this is done automatically... I see that the validate_simple_password function is called by bind_operation, which is called when our Connection object does bind, as seen from here: https://github.com/cannatag/ldap3/blob/86a9e7afedba7f7ca9eeb43710054abaf654539b/ldap3/core/connection.py#L627-L636

With this, I've run out of ideas on what could have gone wrong.

consideRatio commented 2 months ago

It would be good if this could be reproduced again. If done, it would be great if the password is verified to work without using this JupyterHub authenticator class in some way, and then verified it doesn't work specifically with this JupyterHub authenticator using a version 2.0.0 beta 1 or higher.

Until then, I think I'll go for a close on this issue as I couldn't figure out a bug and don't have ideas on how to proceed.


Thanks for an excellent writeup about this @oeilnc!!!