From 92658d9a466b4ab2dea4106d39b4339d79696db5 Mon Sep 17 00:00:00 2001 From: William Brown Date: Feb 21 2017 03:33:32 +0000 Subject: Ticket 49137 - Add sasl plain tests, lib389 support Bug Description: We need to test that SASL plain works in DS. Fix Description: To do this, we need to fix sasl in lib389, we correct openConnection to use more than just gssapi, we fix start tls to work over multiple connections. https://pagure.io/389-ds-base/issue/49137 Author: wibrown Review by: mreynolds (THanks!) --- diff --git a/lib389/__init__.py b/lib389/__init__.py index e189c83..14ea6ee 100644 --- a/lib389/__init__.py +++ b/lib389/__init__.py @@ -98,6 +98,8 @@ if MAJOR >= 3 or (MAJOR == 2 and MINOR >= 7): RE_DBMONATTR = re.compile(r'^([a-zA-Z]+)-([1-9][0-9]*)$') RE_DBMONATTRSUN = re.compile(r'^([a-zA-Z]+)-([a-zA-Z]+)$') +TRACE_LEVEL = 0 + # My logger log = logging.getLogger(__name__) @@ -273,56 +275,15 @@ class DirSrv(SimpleLDAPObject, object): self.instdir = self.ds_paths.inst_dir self.dbdir = self.ds_paths.db_dir - def __localinit__(self): - ''' - Establish a connection to the started instance. It binds with the - binddn property, then it initializes various fields from DirSrv - (via __initPart2) - - @param - self - - @return - None - - @raise ldap.LDAPError - if failure during initialization - ''' - uri = self.toLDAPURL() - - SimpleLDAPObject.__init__(self, uri) - - # see if binddn is a dn or a uid that we need to lookup - if self.binddn and not is_a_dn(self.binddn): - self.simple_bind_s("", "") # anon - ent = self.getEntry(CFGSUFFIX, ldap.SCOPE_SUBTREE, - "(uid=%s)" % self.binddn, - ['uid']) - if ent: - self.binddn = ent.dn - else: - raise ValueError("Error: could not find %s under %s" % ( - self.binddn, CFGSUFFIX)) - - needtls = False - while True: - try: - if needtls: - self.start_tls_s() - try: - self.simple_bind_s(ensure_str(self.binddn), self.bindpw) - except ldap.SERVER_DOWN as e: - # TODO add server info in exception - log.debug("Cannot connect to %r" % uri) - raise e - break - except ldap.CONFIDENTIALITY_REQUIRED: - needtls = True - self.__initPart2() - def rebind(self): """Reconnect to the DS @raise ldap.CONFIDENTIALITY_REQUIRED - missing TLS: """ - SimpleLDAPObject.__init__(self, self.toLDAPURL()) + if hasattr(ldap, 'PYLDAP_VERSION') and MAJOR >= 3: + super(DirSrv, self).__init__(uri, bytes_mode=False, trace_level=TRACE_LEVEL) + else: + super(DirSrv, self).__init__(uri, trace_level=TRACE_LEVEL) # self.start_tls_s() self.simple_bind_s(ensure_str(self.binddn), self.bindpw) @@ -1037,7 +998,7 @@ class DirSrv(SimpleLDAPObject, object): self.state = DIRSRV_STATE_ALLOCATED - def open(self, saslmethod=None, certdir=None, starttls=False, connOnly=False, reqcert=ldap.OPT_X_TLS_HARD): + def open(self, saslmethod=None, sasltoken=None, certdir=None, starttls=False, connOnly=False, reqcert=ldap.OPT_X_TLS_HARD): ''' It opens a ldap bound connection to dirsrv so that online administrative tasks are possible. It binds with the binddn @@ -1048,6 +1009,7 @@ class DirSrv(SimpleLDAPObject, object): @param self @param saslmethod - None, or GSSAPI + @param sasltoken - The ldap.sasl token type to bind with. @param certdir - Certificate directory for TLS @return None @@ -1058,22 +1020,24 @@ class DirSrv(SimpleLDAPObject, object): if self.verbose: self.log.info('open(): Connecting to uri %s' % uri) if hasattr(ldap, 'PYLDAP_VERSION') and MAJOR >= 3: - super(DirSrv, self).__init__(uri, bytes_mode=False) + super(DirSrv, self).__init__(uri, bytes_mode=False, trace_level=TRACE_LEVEL) else: - super(DirSrv, self).__init__(uri) + super(DirSrv, self).__init__(uri, trace_level=TRACE_LEVEL) - if certdir: + if certdir is not None: """ We have a certificate directory, so lets start up TLS negotiations """ - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, certdir) - log.debug("Using ca certificate %s" % certdir) + self.set_option(ldap.OPT_X_TLS_CACERTDIR, certdir) + log.debug("Using external ca certificate %s" % certdir) + else: + self.set_option(ldap.OPT_X_TLS_CACERTDIR, self.get_cert_dir()) + log.debug("Using dirsrv ca certificate %s" % certdir) if certdir or starttls: try: - # MUST be set on ldap. not the object, because pyldap is broken - # and only works if you set this globally. - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) + self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) + log.debug("Using certificate policy %s" % reqcert) log.debug("ldap.OPT_X_TLS_REQUIRE_CERT = %s" % reqcert) self.start_tls_s() except ldap.LDAPError as e: @@ -1095,6 +1059,9 @@ class DirSrv(SimpleLDAPObject, object): log.debug("SASL/GSSAPI Bind Failed: %s" % str(e)) raise e + elif saslmethod and sasltoken is not None: + # Just pass the sasltoken in! + self.sasl_interactive_bind_s("", sasltoken) elif saslmethod: # Unknown or unsupported method log.debug('Unsupported SASL method: %s' % saslmethod) @@ -1146,7 +1113,7 @@ class DirSrv(SimpleLDAPObject, object): # check that DirSrv was in DIRSRV_STATE_ONLINE state if self.state == DIRSRV_STATE_ONLINE: # Don't raise an error. Just move the state and return - SimpleLDAPObject.unbind(self) + self.unbind_s() self.state = DIRSRV_STATE_OFFLINE diff --git a/lib389/_mapped_object.py b/lib389/_mapped_object.py index 7d3afa1..93aad50 100644 --- a/lib389/_mapped_object.py +++ b/lib389/_mapped_object.py @@ -267,8 +267,8 @@ class DSLdapObject(DSLogging): # If the account can be bound to, this will attempt to do so. We don't check # for exceptions, just pass them back! - def bind(self, password=None): - conn = self._instance.openConnection() + def bind(self, password=None, *args, **kwargs): + conn = self._instance.openConnection(*args, **kwargs) conn.simple_bind_s(self.dn, password) return conn diff --git a/lib389/nss_ssl.py b/lib389/nss_ssl.py index bf9876b..36bc26a 100644 --- a/lib389/nss_ssl.py +++ b/lib389/nss_ssl.py @@ -23,8 +23,8 @@ CA_NAME = 'Self-Signed-CA' CERT_NAME = 'Server-Cert' PIN_TXT = 'pin.txt' PWD_TXT = 'pwdfile.txt' -ISSUER = 'CN=ca.unknown.example.com,O=testing,L=unknown,ST=Queensland,C=AU' -SELF_ISSUER = 'CN={HOSTNAME},O=testing,L=unknown,ST=Queensland,C=AU' +ISSUER = 'CN=ca.lib389.example.com,O=testing,L=lib389,ST=Queensland,C=AU' +SELF_ISSUER = 'CN={HOSTNAME},O=testing,L=lib389,ST=Queensland,C=AU' VALID = 2 @@ -125,6 +125,7 @@ class NssSsl(object): result = check_output(cmd) self.dirsrv.log.debug("nss output: %s" % result) # Now extract the CAcert to a well know place. + # This allows us to point the cacert dir here and it "just works" cmd = [ '/usr/bin/certutil', '-L', @@ -233,6 +234,9 @@ class NssSsl(object): CERT_NAME, '-s', SELF_ISSUER.format(HOSTNAME=self.dirsrv.host), + # We MUST issue with SANs else ldap wont verify the name. + '-8', + self.dirsrv.host, '-c', CA_NAME, '-g', diff --git a/lib389/rootdse.py b/lib389/rootdse.py index df98cbf..8d3a6d1 100644 --- a/lib389/rootdse.py +++ b/lib389/rootdse.py @@ -27,6 +27,9 @@ class RootDSE(DSLdapObject): def supports_sasl_ldapssotoken(self): return self.present("supportedSASLMechanisms", "LDAPSSOTOKEN") + def supports_sasl_plain(self): + return self.present("supportedSASLMechanisms", "PLAIN") + def supports_exop_whoami(self): return self.present("supportedExtension", "1.3.6.1.4.1.4203.1.11.3") diff --git a/lib389/sasl.py b/lib389/sasl.py index ee19c40..e64e3c3 100644 --- a/lib389/sasl.py +++ b/lib389/sasl.py @@ -12,7 +12,7 @@ Lib389 python ldap sasl operations. These should be upstreamed if possible. """ -from ldap.sasl import sasl, CB_PASS +from ldap.sasl import sasl, CB_AUTHNAME, CB_PASS class LdapSSOTokenSASL(sasl): """ @@ -29,6 +29,6 @@ class PlainSASL(sasl): """ def __init__(self, authz_id, passwd): - auth_dict = { CB_USER:authz_id, CB_PASS:passwd } + auth_dict = { CB_AUTHNAME:authz_id, CB_PASS:passwd } sasl.__init__(self, auth_dict, "PLAIN") diff --git a/lib389/tools.py b/lib389/tools.py index 10cc7aa..51de689 100644 --- a/lib389/tools.py +++ b/lib389/tools.py @@ -857,18 +857,18 @@ class DirSrvTools(object): for line in hostfp.readlines(): if ipPattern is None: words = line.split() - assert(words[1] == expectedHost) - return True + if words[1] == expectedHost: + return True else: if line.find(ipPattern) >= 0: words = line.split() # We just want to make sure it's in there somewhere - assert(expectedHost in words) - return True + if expectedHost in words: + return True except AssertionError: raise AssertionError( "Error: %s should contain '%s' host for %s" % - ('/etc/hosts/', expectedHost, ipPattern)) + ('/etc/hosts', expectedHost, ipPattern)) raise AssertionError( "Error: /etc/hosts does not contain '%s' as a host for %s" % (expectedHost, ipPattern))