@@ -370,6 +370,53 @@ JupyterHub create local accounts using the LDAPAuthenticator.
370
370
Issue [ #19 ] ( https://github.com/jupyterhub/ldapauthenticator/issues/19 ) provides
371
371
additional discussion on local user creation.
372
372
373
+ ## Handling SSL/TLS handshake errors
374
+
375
+ If you have received a SSL/TLS handshake error, it could be that no [ cipher
376
+ suite] accepted by LDAPAuthenticator is also accepted by the LDAP server. This
377
+ is likely because LDAPAuthenticator is stricter than the LDAP server and only
378
+ accepts modern cipher suites than the LDAP server doesn't accept. Due to this,
379
+ you should from a security perspective ideally modernize the LDAP server's
380
+ accepted cipher suites rather than expand the LDAPAuthenticator accepted cipher
381
+ suites to include older cipher suites.
382
+
383
+ The cipher suites that LDAPAuthenticator accepted by default come from
384
+ [ ssl.create_default_context().get_ciphers()] , which in turn can change with
385
+ Python version. Upgrading Python from 3.7 - 3.9 to 3.10 - 3.13 is known to
386
+ strictly reduce the set of accepted cipher suites from 30 to 17 for example. Due
387
+ to this, upgrading Python could lead to observing a handshake error previously
388
+ not observed.
389
+
390
+ If you want to configure LDAPAuthenticator to accept older cipher suites instead
391
+ of updating the LDAP server to accept modern cipher suites, you can do it using
392
+ ` LDAPAuthenticator.tls_kwargs ` as demonstrated below.
393
+
394
+ ``` python
395
+ # default cipher suites accepted by LDAPAuthenticator in Python 3.7 - 3.9
396
+ # it includes 30 cipher suites, where 13 of them were considered less secure
397
+ # and removed as default cipher suites in Python 3.10
398
+ old_ciphers_list_considered_less_secure = " 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"
399
+
400
+ # default cipher suites accepted by LDAPAuthenticator in Python 3.10 - 3.13
401
+ # this list includes 17 cipher suites out of the 30 in the old list, with no
402
+ # new additions
403
+ new_ciphers_list = " DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256: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"
404
+
405
+ c.LDAPAuthenticator.tls_kwargs = {
406
+ " ciphers" : old_ciphers_list_considered_less_secure,
407
+ }
408
+ ```
409
+
410
+ For reference, you can use a command like below to see what the default cipher
411
+ suites LDAPAuthenticator will use in various Python versions.
412
+
413
+ ``` shell
414
+ 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()])))'
415
+ ```
416
+
417
+ [ cipher suite ] : https://en.wikipedia.org/wiki/Cipher_suite#Full_handshake:_coordinating_cipher_suites
418
+ [ ssl.create_default_context().get_ciphers() ] : https://docs.python.org/3/library/ssl.html#ssl.create_default_context
419
+
373
420
## Testing LDAPAuthenticator without JupyterHub
374
421
375
422
This script can be written to a file such as ` test_ldap_auth.py ` , and run with
0 commit comments