From 1162a3a0110a7022af8e178fe79dce18085a1710 Mon Sep 17 00:00:00 2001 From: gajus Date: Sat, 19 Apr 2025 17:02:45 +0200 Subject: [PATCH 1/3] Add support for start_tls --- DOCUMENTATION.md | 12 ++++++++--- config | 4 ++++ radicale/auth/ldap.py | 48 +++++++++++++++++++++++++++++++++---------- radicale/config.py | 8 ++++++-- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 891090a7..bd31773b 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1045,15 +1045,21 @@ Default: (unset) _(>= 3.3.0)_ -Use ssl on the ldap connection +Use ssl on the ldap connection (soon to be deprecated, use ldap_encryption instead) -Default: False +##### ldap_encryption + +_(>= 3.5.2)_ + +Use encryption on the ldap connection. none, ssl, start_tls + +Default: none ##### ldap_ssl_verify_mode _(>= 3.3.0)_ -The certificate verification mode. NONE, OPTIONAL or REQUIRED +The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL or REQUIRED Default: REQUIRED diff --git a/config b/config index a4c51932..eb7a85a1 100644 --- a/config +++ b/config @@ -102,8 +102,12 @@ #ldap_user_attribute = cn # Use ssl on the ldap connection +# Soon to be deprecated, use ldap_encryption instead #ldap_use_ssl = False +# the encryption mode to be used: ssl, start_tls, default is none +#ldap_encryption = none + # The certificate verification mode. NONE, OPTIONAL, default is REQUIRED #ldap_ssl_verify_mode = REQUIRED diff --git a/radicale/auth/ldap.py b/radicale/auth/ldap.py index ba991302..7d90e4ff 100644 --- a/radicale/auth/ldap.py +++ b/radicale/auth/ldap.py @@ -26,8 +26,9 @@ Following parameters are needed in the configuration: ldap_user_attribute The attribute to be used as username after authentication ldap_groups_attribute The attribute containing group memberships in the LDAP user entry Following parameters controls SSL connections: - ldap_use_ssl If the connection - ldap_ssl_verify_mode The certificate verification mode. NONE, OPTIONAL, default is REQUIRED + ldap_use_ssl If ssl encryption should be used (to be deprecated) + ldap_encryption The encryption mode to be used: *none*|ssl|start_tls + ldap_ssl_verify_mode The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED ldap_ssl_ca_file """ @@ -48,6 +49,7 @@ class Auth(auth.BaseAuth): _ldap_groups_attr: str _ldap_module_version: int = 3 _ldap_use_ssl: bool = False + _ldap_encryption: str = "none" _ldap_ssl_verify_mode: int = ssl.CERT_REQUIRED _ldap_ssl_ca_file: str = "" @@ -82,13 +84,20 @@ class Auth(auth.BaseAuth): self._ldap_secret = file.read().rstrip('\n') if self._ldap_module_version == 3: self._ldap_use_ssl = configuration.get("auth", "ldap_use_ssl") + self._ldap_encryption = configuration.get("auth", "ldap_encryption") + self._use_encryption = self._ldap_use_ssl or self._ldap_encryption in ("ssl", "start_tls") + if self._ldap_use_ssl and self._ldap_encryption == "start_tls": + raise RuntimeError("Cannot set both 'ldap_use_ssl = True' and 'ldap_encryption' = 'start_tls'") if self._ldap_use_ssl: + logger.warning("Configuration uses soon to be deprecated 'ldap_use_ssl', use 'ldap_encryption' ('none', 'ssl', 'start_tls') instead.") + if self._use_encryption: self._ldap_ssl_ca_file = configuration.get("auth", "ldap_ssl_ca_file") tmp = configuration.get("auth", "ldap_ssl_verify_mode") if tmp == "NONE": self._ldap_ssl_verify_mode = ssl.CERT_NONE elif tmp == "OPTIONAL": self._ldap_ssl_verify_mode = ssl.CERT_OPTIONAL + logger.info("auth.ldap_uri : %r" % self._ldap_uri) logger.info("auth.ldap_base : %r" % self._ldap_base) logger.info("auth.ldap_reader_dn : %r" % self._ldap_reader_dn) @@ -113,7 +122,8 @@ class Auth(auth.BaseAuth): logger.error("auth.ldap_secret : (not provided)") raise RuntimeError("LDAP authentication requires ldap_secret for ldap_reader_dn") logger.info("auth.ldap_use_ssl : %s" % self._ldap_use_ssl) - if self._ldap_use_ssl is True: + logger.info("auth.ldap_encryption : %s" % self._ldap_encryption) + if self._use_encryption: logger.info("auth.ldap_ssl_verify_mode : %s" % self._ldap_ssl_verify_mode) if self._ldap_ssl_ca_file: logger.info("auth.ldap_ssl_ca_file : %r" % self._ldap_ssl_ca_file) @@ -188,27 +198,37 @@ class Auth(auth.BaseAuth): """Connect the server""" try: logger.debug(f"_login3 {self._ldap_uri}, {self._ldap_reader_dn}") - if self._ldap_use_ssl: + if self._use_encryption: + logger.debug("_login3 using encryption (reader)") tls = self.ldap3.Tls(validate=self._ldap_ssl_verify_mode) if self._ldap_ssl_ca_file != "": tls = self.ldap3.Tls( validate=self._ldap_ssl_verify_mode, ca_certs_file=self._ldap_ssl_ca_file ) - server = self.ldap3.Server(self._ldap_uri, use_ssl=True, tls=tls) + if self._ldap_use_ssl or self._ldap_encryption == "ssl": + logger.debug("_login3 using ssl (reader)") + server = self.ldap3.Server(self._ldap_uri, use_ssl=True, tls=tls) + else: + server = self.ldap3.Server(self._ldap_uri, use_ssl=False, tls=tls) else: + logger.debug("_login3 not using encryption (reader)") server = self.ldap3.Server(self._ldap_uri) - conn = self.ldap3.Connection(server, self._ldap_reader_dn, password=self._ldap_secret) + try: + conn = self.ldap3.Connection(server, self._ldap_reader_dn, password=self._ldap_secret, auto_bind=False, raise_exceptions=True) + if self._ldap_encryption == "start_tls": + logger.debug("_login3 using start_tls (reader)") + conn.start_tls() + except self.ldap3.core.exceptions.LDAPStartTLSError as e: + raise RuntimeError(f"_login3 StartTLS Error: {e}") except self.ldap3.core.exceptions.LDAPSocketOpenError: raise RuntimeError("Unable to reach LDAP server") except Exception as e: - logger.debug(f"_login3 error 1 {e}") + logger.debug(f"_login3 error 1 {e} (reader)") pass - if not conn.bind(): - logger.debug("_login3 cannot bind") + logger.debug("_login3 cannot bind (reader)") raise RuntimeError("Unable to read from LDAP server") - logger.debug(f"_login3 bind as {self._ldap_reader_dn}") """Search the user dn""" escaped_login = self.ldap3.utils.conv.escape_filter_chars(login) @@ -230,7 +250,13 @@ class Auth(auth.BaseAuth): logger.debug(f"_login3 found LDAP user DN {user_dn}") try: """Try to bind as the user itself""" - conn = self.ldap3.Connection(server, user_dn, password=password) + try: + conn = self.ldap3.Connection(server, user_dn, password=password, auto_bind=False) + if self._ldap_encryption == "start_tls": + logger.debug("_login3 using start_tls (user)") + conn.start_tls() + except self.ldap3.core.exceptions.LDAPStartTLSError as e: + raise RuntimeError(f"_login3 StartTLS Error: {e}") if not conn.bind(): logger.debug(f"_login3 user '{login}' cannot be found") return "" diff --git a/radicale/config.py b/radicale/config.py index e74832cd..51ed729a 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -297,11 +297,15 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ "type": str}), ("ldap_use_ssl", { "value": "False", - "help": "Use ssl on the ldap connection", + "help": "Use ssl on the ldap connection. Soon to be deprecated, use ldap_encryption instead", "type": bool}), + ("ldap_encryption", { + "value": "none", + "help": "the encryption mode to be used: *none*|ssl|start_tls", + "type": str}), ("ldap_ssl_verify_mode", { "value": "REQUIRED", - "help": "The certificate verification mode. NONE, OPTIONAL, default is REQUIRED", + "help": "The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED", "type": str}), ("ldap_ssl_ca_file", { "value": "", From e6f0d7f48bc579234ee7a9d2dba82b4d0a7363bf Mon Sep 17 00:00:00 2001 From: gajus Date: Sat, 19 Apr 2025 17:07:24 +0200 Subject: [PATCH 2/3] Update config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index eb7a85a1..dbab07bd 100644 --- a/config +++ b/config @@ -108,7 +108,7 @@ # the encryption mode to be used: ssl, start_tls, default is none #ldap_encryption = none -# The certificate verification mode. NONE, OPTIONAL, default is REQUIRED +# The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED #ldap_ssl_verify_mode = REQUIRED # The path to the CA file in pem format which is used to certificate the server certificate From b805393bd9230b74fe1a8862e59ce263eb8f5df3 Mon Sep 17 00:00:00 2001 From: gajus Date: Mon, 21 Apr 2025 21:26:58 +0200 Subject: [PATCH 3/3] Standardize LDAP security configuration naming --- DOCUMENTATION.md | 8 ++++---- config | 8 ++++---- radicale/auth/ldap.py | 28 ++++++++++++++-------------- radicale/config.py | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index bd31773b..19304fd9 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1045,13 +1045,13 @@ Default: (unset) _(>= 3.3.0)_ -Use ssl on the ldap connection (soon to be deprecated, use ldap_encryption instead) +Use ssl on the ldap connection (soon to be deprecated, use ldap_security instead) -##### ldap_encryption +##### ldap_security _(>= 3.5.2)_ -Use encryption on the ldap connection. none, ssl, start_tls +Use encryption on the ldap connection. none, tls, starttls Default: none @@ -1059,7 +1059,7 @@ Default: none _(>= 3.3.0)_ -The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL or REQUIRED +The certificate verification mode. Works for tls and starttls. NONE, OPTIONAL or REQUIRED Default: REQUIRED diff --git a/config b/config index dbab07bd..0c926c7b 100644 --- a/config +++ b/config @@ -102,13 +102,13 @@ #ldap_user_attribute = cn # Use ssl on the ldap connection -# Soon to be deprecated, use ldap_encryption instead +# Soon to be deprecated, use ldap_security instead #ldap_use_ssl = False -# the encryption mode to be used: ssl, start_tls, default is none -#ldap_encryption = none +# the encryption mode to be used: tls, starttls, default is none +#ldap_security = none -# The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED +# The certificate verification mode. Works for ssl and starttls. NONE, OPTIONAL, default is REQUIRED #ldap_ssl_verify_mode = REQUIRED # The path to the CA file in pem format which is used to certificate the server certificate diff --git a/radicale/auth/ldap.py b/radicale/auth/ldap.py index 7d90e4ff..3ca4ddb3 100644 --- a/radicale/auth/ldap.py +++ b/radicale/auth/ldap.py @@ -27,8 +27,8 @@ Following parameters are needed in the configuration: ldap_groups_attribute The attribute containing group memberships in the LDAP user entry Following parameters controls SSL connections: ldap_use_ssl If ssl encryption should be used (to be deprecated) - ldap_encryption The encryption mode to be used: *none*|ssl|start_tls - ldap_ssl_verify_mode The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED + ldap_security The encryption mode to be used: *none*|tls|starttls + ldap_ssl_verify_mode The certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED ldap_ssl_ca_file """ @@ -49,7 +49,7 @@ class Auth(auth.BaseAuth): _ldap_groups_attr: str _ldap_module_version: int = 3 _ldap_use_ssl: bool = False - _ldap_encryption: str = "none" + _ldap_security: str = "none" _ldap_ssl_verify_mode: int = ssl.CERT_REQUIRED _ldap_ssl_ca_file: str = "" @@ -84,12 +84,12 @@ class Auth(auth.BaseAuth): self._ldap_secret = file.read().rstrip('\n') if self._ldap_module_version == 3: self._ldap_use_ssl = configuration.get("auth", "ldap_use_ssl") - self._ldap_encryption = configuration.get("auth", "ldap_encryption") - self._use_encryption = self._ldap_use_ssl or self._ldap_encryption in ("ssl", "start_tls") - if self._ldap_use_ssl and self._ldap_encryption == "start_tls": - raise RuntimeError("Cannot set both 'ldap_use_ssl = True' and 'ldap_encryption' = 'start_tls'") + self._ldap_security = configuration.get("auth", "ldap_security") + self._use_encryption = self._ldap_use_ssl or self._ldap_security in ("tls", "starttls") + if self._ldap_use_ssl and self._ldap_security == "starttls": + raise RuntimeError("Cannot set both 'ldap_use_ssl = True' and 'ldap_security' = 'starttls'") if self._ldap_use_ssl: - logger.warning("Configuration uses soon to be deprecated 'ldap_use_ssl', use 'ldap_encryption' ('none', 'ssl', 'start_tls') instead.") + logger.warning("Configuration uses soon to be deprecated 'ldap_use_ssl', use 'ldap_security' ('none', 'tls', 'starttls') instead.") if self._use_encryption: self._ldap_ssl_ca_file = configuration.get("auth", "ldap_ssl_ca_file") tmp = configuration.get("auth", "ldap_ssl_verify_mode") @@ -122,7 +122,7 @@ class Auth(auth.BaseAuth): logger.error("auth.ldap_secret : (not provided)") raise RuntimeError("LDAP authentication requires ldap_secret for ldap_reader_dn") logger.info("auth.ldap_use_ssl : %s" % self._ldap_use_ssl) - logger.info("auth.ldap_encryption : %s" % self._ldap_encryption) + logger.info("auth.ldap_security : %s" % self._ldap_security) if self._use_encryption: logger.info("auth.ldap_ssl_verify_mode : %s" % self._ldap_ssl_verify_mode) if self._ldap_ssl_ca_file: @@ -206,7 +206,7 @@ class Auth(auth.BaseAuth): validate=self._ldap_ssl_verify_mode, ca_certs_file=self._ldap_ssl_ca_file ) - if self._ldap_use_ssl or self._ldap_encryption == "ssl": + if self._ldap_use_ssl or self._ldap_security == "tls": logger.debug("_login3 using ssl (reader)") server = self.ldap3.Server(self._ldap_uri, use_ssl=True, tls=tls) else: @@ -216,8 +216,8 @@ class Auth(auth.BaseAuth): server = self.ldap3.Server(self._ldap_uri) try: conn = self.ldap3.Connection(server, self._ldap_reader_dn, password=self._ldap_secret, auto_bind=False, raise_exceptions=True) - if self._ldap_encryption == "start_tls": - logger.debug("_login3 using start_tls (reader)") + if self._ldap_security == "starttls": + logger.debug("_login3 using starttls (reader)") conn.start_tls() except self.ldap3.core.exceptions.LDAPStartTLSError as e: raise RuntimeError(f"_login3 StartTLS Error: {e}") @@ -252,8 +252,8 @@ class Auth(auth.BaseAuth): """Try to bind as the user itself""" try: conn = self.ldap3.Connection(server, user_dn, password=password, auto_bind=False) - if self._ldap_encryption == "start_tls": - logger.debug("_login3 using start_tls (user)") + if self._ldap_security == "starttls": + logger.debug("_login3 using starttls (user)") conn.start_tls() except self.ldap3.core.exceptions.LDAPStartTLSError as e: raise RuntimeError(f"_login3 StartTLS Error: {e}") diff --git a/radicale/config.py b/radicale/config.py index 51ed729a..cb2285c3 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -297,15 +297,15 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ "type": str}), ("ldap_use_ssl", { "value": "False", - "help": "Use ssl on the ldap connection. Soon to be deprecated, use ldap_encryption instead", + "help": "Use ssl on the ldap connection. Soon to be deprecated, use ldap_security instead", "type": bool}), - ("ldap_encryption", { + ("ldap_security", { "value": "none", - "help": "the encryption mode to be used: *none*|ssl|start_tls", + "help": "the encryption mode to be used: *none*|tls|starttls", "type": str}), ("ldap_ssl_verify_mode", { "value": "REQUIRED", - "help": "The certificate verification mode. Works for ssl and start_tls. NONE, OPTIONAL, default is REQUIRED", + "help": "The certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED", "type": str}), ("ldap_ssl_ca_file", { "value": "",