LDAP Authenticator for JupyterHub

repository·main·Indexed 42 days ago

https://github.com/jupyterhub/ldapauthenticator

A JupyterHub plugin that enables authentication via LDAP (Lightweight Directory Access Protocol) or Active Directory. It allows administrators to manage access using enterprise directory services, supporting DN templates, reverse lookups via lookup_dn, group restrictions with allowed_groups, and SSL/TLS security configurations.

Tokens
3K
Snippets
9
Records
11
Agent score
3%

What's inside LDAP Authenticator

  1. How to handle user logout when changing authentication settings

    main

    If you modify authentication settings that change which users are allowed to log in (e.g., changing allowed_groups or enabling LDAPAuthenticator), existing logged-in users might still be able to access the system.

    To force all users to log out and re-authenticate under the new rules, you must change the JupyterHub cookie secret. You can do this by deleting the jupyterhub_cookie_secret file.

  2. Enable LDAP Authenticator in JupyterHub

    main

    To enable the LDAP Authenticator, add the following line to your jupyterhub_config.py file:

    Note for TLJH users: If jupyterhub_config.py does not exist, create it in /opt/tljh/config/jupyterhub_config.d/.

    c.JupyterHub.authenticator_class = 'ldap'
  3. Configure required LDAPAuthenticator settings

    main

    At a minimum, you must configure server_address and a method to resolve the user's Distinguished Name (DN).

    1. Set the Server Address

    LDAPAuthenticator.server_address: The hostname or IP of the LDAP server (do not include protocol or port).

    2. Resolve the User DN

    You have two primary methods to acquire the DN required for binding:

    Method A: Using bind_dn_template (when lookup_dn = False)

    If you know the structure of your DNs, provide a list of templates. The {username} placeholder is replaced by the user's input.

    c.LDAPAuthenticator.bind_dn_template = [
        "uid={username},ou=people,dc=wikimedia,dc=org",
        "uid={username},ou=developers,dc=wikimedia,dc=org",
    ]

    Method B: Using lookup_dn = True (Reverse Lookup)

    This is recommended for servers like Active Directory where the DN is not easily predictable from the username. When lookup_dn = True, the authenticator performs a search to find the user's DN first.

    Note: When using lookup_dn = True, you must also configure user_search_base, user_attribute, lookup_dn_user_dn_attribute, and lookup_dn_search_filter (though defaults may work).

    c.LDAPAuthenticator.server_address = 'ldap.example.com'
    c.LDAPAuthenticator.lookup_dn = True
  4. Integrate with Active Directory (AD)

    main

    When using Active Directory, you may need to perform a DN lookup if the login attribute is not part of the DN, or if the LDAP search requires a technical account to query the database.

    To implement this, enable lookup_dn and configure a search filter and a technical service account. The process works as follows:

    1. LDAP searches for a user matching the user_attribute (e.g., sAMAccountName) using the lookup_dn_search_user credentials.
    2. The DN is then constructed using the value found in the lookup_dn_user_dn_attribute (e.g., cn).
    c.LDAPAuthenticator.lookup_dn = True
    c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'
    c.LDAPAuthenticator.lookup_dn_search_user = 'ldap_search_user_technical_account'
    c.LDAPAuthenticator.lookup_dn_search_password = 'secret'
    c.LDAPAuthenticator.user_search_base = 'ou=people,dc=wikimedia,dc=org'
    c.LDAPAuthenticator.user_attribute = 'sAMAccountName'
    c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'
  5. Configure advanced user lookup with `lookup_dn = True`

    main

    When lookup_dn = True is enabled, you can perform a dynamic search to find a user's DN. This is common in Active Directory environments.

    Required/Common Configuration Keys:

    • user_search_base: The base DN where the search begins.
    • user_attribute: The attribute in LDAP that contains the username (e.g., sAMAccountName for AD, uid for OpenLDAP).
    • lookup_dn_search_filter: The LDAP query used to find the user. Default is '({login_attr}={login})'.
    • lookup_dn_search_user / lookup_dn_search_password: Credentials for a technical account used to perform the lookup. If both are None, an anonymous query is attempted.
    • lookup_dn_user_dn_attribute: The attribute used to build the DN string (e.g., cn for Active Directory, uid for most others).
    • use_lookup_dn_username: If True, the value of lookup_dn_user_dn_attribute is used as the JupyterHub username instead of the login string.

    Example Configuration (Active Directory):

    c.LDAPAuthenticator.lookup_dn = True
    c.LDAPAuthenticator.user_search_base = 'ou=people,dc=example,dc=com'
    c.LDAPAuthenticator.user_attribute = 'sAMAccountName'
    c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'
    c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'
  6. Configure LDAP group restrictions with `allowed_groups`

    main

    To restrict login access to specific LDAP groups, set LDAPAuthenticator.allowed_groups to a list of full DNs. The authenticator checks if the user is a member of these groups using the member attribute.

    Example for restricting access to researcher or operations groups:

    c.LDAPAuthenticator.allowed_groups = [
        "cn=researcher,ou=groups,dc=wikimedia,dc=org",
        "cn=operations,ou=groups,dc=wikimedia,dc=org",
    ]
  7. Configure LDAP connection security (SSL/TLS)

    main

    Use tls_strategy to define how the connection to the LDAP server is secured.

    Supported tls_strategy values:

    • "before_bind" (default): Upgrades the connection to SSL/TLS before sending credentials. Recommended for LDAP v3.
    • "on_connect" (deprecated): Establishes SSL/TLS directly (associated with port 636). Setting this automatically sets server_port to 636.
    • "insecure": No encryption.

    Additional TLS settings:

    • LDAPAuthenticator.server_port: The port to contact. Defaults to 389 (or 636 if on_connect is used).
    • LDAPAuthenticator.tls_kwargs: A dictionary of keyword arguments passed to the ldap3 package's Tls object (e.g., for specifying CA certificates).

    Example using a custom CA certificate:

    c.LDAPAuthenticator.tls_kwargs = {
        "ca_certs_file": "/path/to/ca.crt",
    }
  8. Configure TLS cipher suites via tls_kwargs

    main

    If you need to support older, less secure cipher suites to resolve handshake errors, pass them through the tls_kwargs dictionary using the ciphers key.

    # Example using a list of older cipher suites
    old_ciphers_list = "AES128-SHA:AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES256-GCM-SHA384:AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-CHACHA20-POLY1305:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"
    
    c.LDAPAuthenticator.tls_kwargs = {
        "ciphers": old_ciphers_list,
    }
  9. Handle SSL/TLS handshake errors

    main

    SSL/TLS handshake errors often occur because the LDAP server uses older cipher suites that are not accepted by the modern defaults in newer Python versions (e.g., Python 3.10+).

    While it is recommended to modernize the LDAP server's cipher suites, you can force LDAPAuthenticator to accept older suites by configuring the tls_kwargs option with a ciphers string.

    To check the default cipher suites for your current Python version, you can run the following command in a Docker container:

    docker run -it --rm python:3.13 python -c 'import ssl; c = ssl.create_default_context(); print(":".join(sorted([c["name"] for c in c.get_ciphers()])))'
  10. Test LDAPAuthenticator without JupyterHub

    main

    You can verify your LDAPAuthenticator configuration independently of a running JupyterHub instance by using a standalone Python script. This script initializes the LDAPAuthenticator with a traitlets.config.Config object and calls the authenticate method directly.

    If authentication is successful, the script will print the username; otherwise, it will print None.

    import asyncio
    import getpass
    
    from traitlets.config import Config
    from ldapauthenticator import LDAPAuthenticator
    
    # Configure LDAPAuthenticator below to work against your ldap server
    c = Config()
    c.LDAPAuthenticator.server_address = "ldap.organisation.org"
    c.LDAPAuthenticator.server_port = 636
    c.LDAPAuthenticator.bind_dn_template = "uid={username},ou=people,dc=organisation,dc=org"
    c.LDAPAuthenticator.user_attribute = "uid"
    c.LDAPAuthenticator.user_search_base = "ou=people,dc=organisation,dc=org"
    c.LDAPAuthenticator.attributes = ["uid", "cn", "mail", "ou", "o"]
    
    # Example of a complex search_filter
    sf = "(&(o={o})(ou={ou}))".format(o="yourOrganisation", ou="yourOrganisationalUnit")
    sf += "(&(o={o})(mail={mail}))".format(o="yourOrganisation", mail="yourMailAddress")
    c.LDAPAuthenticator.search_filter = f"(&({{userattr}}={{username}})(|{sf}))"
    
    # Run test
    authenticator = LDAPAuthenticator(config=c)
    username = input("Username: ")
    password = getpass.getpass()
    data = dict(username=username, password=password)
    return_value = asyncio.run(authenticator.authenticate(None, data))
    print(return_value)