commit 895902073e255d04137342edd9aa617d94d68c41 Author: Florian Best Date: Mon Feb 1 21:59:55 2021 +0100 Bug #47932: move get_kerberos_ticket and gssapi bind to univention.uldap diff --git base/univention-lib/python/admember.py base/univention-lib/python/admember.py index 43f09fa9fe..8e55af266f 100644 --- base/univention-lib/python/admember.py +++ base/univention-lib/python/admember.py @@ -211,33 +211,10 @@ def is_domain_in_admember_mode(ucr=None): def _get_kerberos_ticket(principal, password, ucr=None): # type: (str, str, Optional[ConfigRegistry]) -> None - ud.debug(ud.MODULE, ud.INFO, "running _get_kerberos_ticket") - if not ucr: - ucr = ConfigRegistry() - ucr.load() - - # We need to remove the target credential cache first, - # otherwise kinit may use an old ticket and run into "krb5_get_init_creds: Clock skew too great". - cmd1 = ("/usr/bin/kdestroy",) - p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) - stdout, stderr = p1.communicate() - if p1.returncode != 0: - ud.debug(ud.MODULE, ud.ERROR, "kdestroy failed:\n%s" % stdout.decode('UTF-8', 'replace')) - - with tempfile.NamedTemporaryFile('w+') as f: - os.fchmod(f.fileno(), 0o600) - f.write(password) - f.flush() - - cmd2 = ("/usr/bin/kinit", "--no-addresses", "--password-file=%s" % (f.name,), principal) - p1 = subprocess.Popen(cmd2, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) - stdout, stderr = p1.communicate() - if p1.returncode != 0: - msg = "kinit failed:\n%s" % (stdout.decode('UTF-8', 'replace'),) - ud.debug(ud.MODULE, ud.ERROR, msg) - raise connectionFailed(msg) - if stdout: - ud.debug(ud.MODULE, ud.WARN, "kinit output:\n%s" % stdout.decode('UTF-8', 'replace')) + try: + return univention.uldap.access.get_kerberos_ticket(principal, password) + except ldap.LOCAL_ERROR as exc: + raise connectionFailed(str(exc)) def check_connection(ad_domain_info, username, password): diff --git base/univention-python/modules/uldap.py base/univention-python/modules/uldap.py index 72bef6d928..e154efee04 100644 --- base/univention-python/modules/uldap.py +++ base/univention-python/modules/uldap.py @@ -30,9 +30,12 @@ # /usr/share/common-licenses/AGPL-3; if not, see # . +import os import re from functools import wraps import random +import subprocess +from tempfile import NamedTemporaryFile import six import ldap @@ -319,6 +322,44 @@ class access(object): self.binddn = self.whoami() univention.debug.debug(univention.debug.LDAP, univention.debug.INFO, 'SAML bind binddn=%s' % self.binddn) + @_fix_reconnect_handling + def bind_sasl_gssapi(self, binddn, bindpw, credentials_cache=None): + # type: (str, str, Optional[str]) -> None + if credentials_cache: + os.environ['KRB5CCNAME'] = credentials_cache + + self.get_kerberos_ticket() + self.lo.sasl_interactive_bind_s("", ldap.sasl.gssapi("")) + + @staticmethod + def get_kerberos_ticket(principal, password): + # type: (str, str) -> None + ud = univention.debug + ud.debug(ud.MODULE, ud.INFO, "running _get_kerberos_ticket") + + # We need to remove the target credential cache first, + # otherwise kinit may use an old ticket and run into "krb5_get_init_creds: Clock skew too great". + cmd1 = ("/usr/bin/kdestroy",) + p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) + stdout, stderr = p1.communicate() + if p1.returncode != 0: + ud.debug(ud.MODULE, ud.ERROR, "kdestroy failed:\n%s" % stdout.decode('UTF-8', 'replace')) + + with NamedTemporaryFile('w+') as f: + os.fchmod(f.fileno(), 0o600) + f.write(password) + f.flush() + + cmd2 = ("/usr/bin/kinit", "--no-addresses", "--password-file=%s" % (f.name,), principal) + p1 = subprocess.Popen(cmd2, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) + stdout, stderr = p1.communicate() + if p1.returncode != 0: + msg = "kinit failed:\n%s" % (stdout.decode('UTF-8', 'replace'),) + ud.debug(ud.MODULE, ud.ERROR, msg) + raise ldap.LOCAL_ERROR(msg) + if stdout: + ud.debug(ud.MODULE, ud.WARN, "kinit output:\n%s" % stdout.decode('UTF-8', 'replace')) + def unbind(self): # type: () -> None """ diff --git services/univention-ad-connector/modules/univention/connector/ad/__init__.py services/univention-ad-connector/modules/univention/connector/ad/__init__.py index 95e60b9426..75fa2908eb 100644 --- services/univention-ad-connector/modules/univention/connector/ad/__init__.py +++ services/univention-ad-connector/modules/univention/connector/ad/__init__.py @@ -42,8 +42,6 @@ import time import calendar import string import base64 -import subprocess -from tempfile import NamedTemporaryFile import six import ldap @@ -665,18 +663,6 @@ class ad(univention.connector.ucs): sid = self.samr.LookupDomain(handle, sam_domain) self.dom_handle = self.samr.OpenDomain(handle, security.SEC_FLAG_MAXIMUM_ALLOWED, sid) - def get_kerberos_ticket(self): - p1 = subprocess.Popen(['kdestroy', ], close_fds=True) - p1.wait() - with NamedTemporaryFile('w') as fd: - fd.write(self.ad_ldap_bindpw) - fd.flush() - cmd_block = ['kinit', '--no-addresses', '--password-file=%s' % (fd.name,), self.ad_ldap_binddn] - p1 = subprocess.Popen(cmd_block, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) - stdout, stderr = p1.communicate() - if p1.returncode != 0: - raise kerberosAuthenticationFailed('The following command failed: "%s" (%s): %s' % (' '.join(cmd_block), p1.returncode, stdout.decode('UTF-8', 'replace'))) - def ad_search_ext_s(self, *args, **kwargs): return fix_dn_in_search(self.lo_ad.lo.search_ext_s(*args, **kwargs)) @@ -696,15 +682,20 @@ class ad(univention.connector.ucs): except Exception: # FIXME: which exception is to be caught self._debug_traceback(ud.ERROR, 'Failed to lookup AD LDAP base, using UCR value.') + self.lo_ad = univention.uldap.access( + host=self.ad_ldap_host, port=int(self.ad_ldap_port), + base=self.ad_ldap_base, binddn=None, bindpw=None, + start_tls=tls_mode, use_ldaps=ldaps, + ca_certfile=self.ad_ldap_certificate, + ) + if self.configRegistry.is_true('%s/ad/ldap/kerberos' % self.CONFIGBASENAME): - os.environ['KRB5CCNAME'] = '/var/cache/univention-ad-connector/krb5.cc' - self.get_kerberos_ticket() - auth = ldap.sasl.gssapi("") - self.lo_ad = univention.uldap.access(host=self.ad_ldap_host, port=int(self.ad_ldap_port), base=self.ad_ldap_base, binddn=None, bindpw=self.ad_ldap_bindpw, start_tls=tls_mode, use_ldaps=ldaps, ca_certfile=self.ad_ldap_certificate) - self.get_kerberos_ticket() - self.lo_ad.lo.sasl_interactive_bind_s("", auth) + try: + self.lo_ad.bind_sasl_gssapi(self.ad_ldap_binddn, self.ad_ldap_bindpw, '/var/cache/univention-ad-connector/krb5.cc') + except ldap.LOCAL_ERROR as exc: + raise kerberosAuthenticationFailed(str(exc)) else: - self.lo_ad = univention.uldap.access(host=self.ad_ldap_host, port=int(self.ad_ldap_port), base=self.ad_ldap_base, binddn=self.ad_ldap_binddn, bindpw=self.ad_ldap_bindpw, start_tls=tls_mode, use_ldaps=ldaps, ca_certfile=self.ad_ldap_certificate) + self.lo_ad.bind(self.ad_ldap_binddn, self.ad_ldap_bindpw) self.lo_ad.lo.set_option(ldap.OPT_REFERRALS, 0) diff --git test/ucs-test/lib/ldap_glue.py test/ucs-test/lib/ldap_glue.py index 155870de13..8b09e47cd0 100644 --- test/ucs-test/lib/ldap_glue.py +++ test/ucs-test/lib/ldap_glue.py @@ -1,5 +1,4 @@ import os -import subprocess import ldap import ldap.dn @@ -7,6 +6,8 @@ from univention.config_registry import ConfigRegistry from ldap.controls import LDAPControl import ldap.modlist as modlist +import univention.uldap + ucr = ConfigRegistry() ucr.load() @@ -72,7 +73,7 @@ class LDAPConnection(object): try: if self.kerberos: os.environ['KRB5CCNAME'] = '/tmp/ucs-test-ldap-glue.cc' - self.get_kerberos_ticket() + univention.uldap.access.get_kerberos_ticket(self.principal, open(self.pw_file).read().strip()) auth = ldap.sasl.gssapi("") self.lo.sasl_interactive_bind_s("", auth) else: @@ -88,15 +89,6 @@ class LDAPConnection(object): self.lo.set_option(ldap.OPT_REFERRALS, 0) - def get_kerberos_ticket(self): - p1 = subprocess.Popen(['kdestroy', ], close_fds=True) - p1.wait() - cmd_block = ['kinit', '--no-addresses', '--password-file=%s' % self.pw_file, self.principal] - p1 = subprocess.Popen(cmd_block, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) - stdout, stderr = p1.communicate() - if p1.returncode != 0: - raise Exception('The following command failed: "%s" (%s): %s' % (''.join(cmd_block), p1.returncode, stdout.decode('UTF-8'))) - def exists(self, dn): try: self.lo.search_ext_s(dn, ldap.SCOPE_BASE, timeout=10)