diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -235,10 +235,8 @@ Connection Security : required Certificate Checks : optional How SSL certificates verification is handled -- this is only useful when `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security - while the other options are susceptible to man-in-the-middle attacks. SSL - certificates can be installed to /etc/openldap/cacerts so that the - DEMAND or HARD options can be used with self-signed certificates or - certificates that do not have traceable certificates of authority. + with mandatory certificate validation, while the other options are + susceptible to man-in-the-middle attacks. NEVER A serve certificate will never be requested or checked. @@ -260,6 +258,16 @@ Certificate Checks : optional HARD The same as DEMAND. +.. _Custom CA Certificates: + +Custom CA Certificates : optional + Directory used by OpenSSL to find CAs for validating the LDAP server certificate. + Python 2.7.10 and later default to using the system certificate store, and + this should thus not be necessary when using certificates signed by a CA + trusted by the system. + It can be set to something like `/etc/openldap/cacerts` on older systems or + if using self-signed certificates. + .. _Base DN: Base DN : required diff --git a/kallithea/lib/auth_modules/auth_ldap.py b/kallithea/lib/auth_modules/auth_ldap.py --- a/kallithea/lib/auth_modules/auth_ldap.py +++ b/kallithea/lib/auth_modules/auth_ldap.py @@ -49,7 +49,7 @@ except ImportError: class AuthLdap(object): def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, + tls_kind='PLAIN', tls_reqcert='DEMAND', cacertdir=None, ldap_version=3, ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', search_scope='SUBTREE', attr_login='uid'): if ldap is None: @@ -67,6 +67,8 @@ class AuthLdap(object): OPT_X_TLS_DEMAND = 2 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND) + self.cacertdir = cacertdir + # split server into list self.LDAP_SERVER_ADDRESS = server.split(',') self.LDAP_SERVER_PORT = port @@ -107,9 +109,11 @@ class AuthLdap(object): if "," in username: raise LdapUsernameError("invalid character in username: ,") try: - if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, - '/etc/openldap/cacerts') + if self.cacertdir: + if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.cacertdir) + else: + log.debug("OPT_X_TLS_CACERTDIR is not available - can't set %s", self.cacertdir) ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) ldap.set_option(ldap.OPT_TIMEOUT, 20) @@ -168,7 +172,8 @@ class AuthLdap(object): log.debug("LDAP says no such user '%s' (%s)", uid, username) raise LdapUsernameError() except ldap.SERVER_DOWN: - raise LdapConnectionError("LDAP can't access authentication server") + # [0] might be {'info': "TLS error -8179:Peer's Certificate issuer is not recognized.", 'desc': "Can't contact LDAP server"} + raise LdapConnectionError("LDAP can't connect to authentication server") class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin): @@ -231,6 +236,13 @@ class KallitheaAuthPlugin(auth_modules.K "formname": "Certificate Checks" }, { + "name": "cacertdir", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Optional: Custom CA certificate directory for validating LDAPS", + "formname": "Custom CA Certificates" + }, + { "name": "base_dn", "validator": self.validators.UnicodeString(strip=True), "type": "string", @@ -314,6 +326,7 @@ class KallitheaAuthPlugin(auth_modules.K 'bind_pass': settings.get('dn_pass'), 'tls_kind': settings.get('tls_kind'), 'tls_reqcert': settings.get('tls_reqcert'), + 'cacertdir': settings.get('cacertdir'), 'ldap_filter': settings.get('filter'), 'search_scope': settings.get('search_scope'), 'attr_login': settings.get('attr_login'), diff --git a/kallithea/tests/functional/test_admin_auth_settings.py b/kallithea/tests/functional/test_admin_auth_settings.py --- a/kallithea/tests/functional/test_admin_auth_settings.py +++ b/kallithea/tests/functional/test_admin_auth_settings.py @@ -30,6 +30,7 @@ class TestAuthSettingsController(TestCon 'auth_ldap_port': '999', 'auth_ldap_tls_kind': 'PLAIN', 'auth_ldap_tls_reqcert': 'NEVER', + 'auth_ldap_cacertdir': '', 'auth_ldap_dn_user': 'test_user', 'auth_ldap_dn_pass': 'test_pass', 'auth_ldap_base_dn': 'test_base_dn',