From 5215d152d51834cd3d9875f15a7661ae2425111d Mon Sep 17 00:00:00 2001 From: William Brown Date: Wed, 29 Nov 2017 15:55:12 +0100 Subject: [PATCH] Ticket 49474 - Improve GSSAPI testing capability Bug Description: GSSAPI is difficult to test correctly. Having support for it as a topology in lib389 will mak ethis easier. Note that these tests require specific dns and hosts settings. Fix Description: Improve the ability to integrate accounts with principles, bind them with GSSAPI, oru ability to return valid uris with krb realms, our saslmap capabilities and create a single master gssapi topology. https://pagure.io/389-ds-base/issue/49474 Author: wibrown Review by: ??? --- dirsrvtests/tests/suites/gssapi/__init__.py | 0 .../tests/suites/gssapi/simple_gssapi_test.py | 147 +++++++++++++++++++++ src/lib389/lib389/__init__.py | 64 +++------ src/lib389/lib389/idm/account.py | 31 +++++ src/lib389/lib389/mit_krb5.py | 19 +-- src/lib389/lib389/properties.py | 1 - src/lib389/lib389/saslmap.py | 54 ++++++++ src/lib389/lib389/tests/krb5_create_test.py | 147 --------------------- src/lib389/lib389/tools.py | 2 +- src/lib389/lib389/topologies.py | 76 ++++++++++- 10 files changed, 332 insertions(+), 209 deletions(-) create mode 100644 dirsrvtests/tests/suites/gssapi/__init__.py create mode 100644 dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py create mode 100644 src/lib389/lib389/saslmap.py delete mode 100644 src/lib389/lib389/tests/krb5_create_test.py diff --git a/dirsrvtests/tests/suites/gssapi/__init__.py b/dirsrvtests/tests/suites/gssapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py new file mode 100644 index 0000000..9a881cb --- /dev/null +++ b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py @@ -0,0 +1,147 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2017 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.topologies import topology_st_gssapi, gssapi_ack +from lib389.idm.user import UserAccounts + +from lib389.saslmap import SaslMappings + +from lib389._constants import DEFAULT_SUFFIX + +import ldap +import subprocess +import os +import pytest + + + +@pytest.fixture(scope='module') +def testuser(topology_st_gssapi): + # Create a user + users = UserAccounts(topology_st_gssapi.standalone, DEFAULT_SUFFIX) + user = users.create(properties={ + 'uid': 'testuser', + 'cn' : 'testuser', + 'sn' : 'user', + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/testuser' + }) + # Give them a krb princ + user.create_keytab() + return user + +@gssapi_ack +def test_gssapi_bind(topology_st_gssapi, testuser): + """Test that we can bind with GSSAPI + + :id: 894a4c27-3d4c-4ba3-aa33-2910032e3783 + + :setup: standalone gssapi instance + + :steps: + 1. Bind with sasl/gssapi + :expectedresults: + 1. Bind succeeds + + """ + conn = testuser.bind_gssapi() + assert(conn.whoami_s() == "dn: %s" % testuser.dn.lower()) + +@gssapi_ack +def test_invalid_sasl_map(topology_st_gssapi, testuser): + """Test that auth fails when we can not map a user. + + :id: dd4218eb-9237-4611-ba2f-1781391cadd1 + + :setup: standalone gssapi instance + + :steps: + 1. Invalidate a sasl map + 2. Attempt to bind + :expectedresults: + 1. The sasl map is invalid. + 2. The bind fails. + """ + saslmaps = SaslMappings(topology_st_gssapi.standalone) + saslmap = saslmaps.get('suffix map') + saslmap.set('nsSaslMapFilterTemplate', '(invalidattr=\\1)') + + with pytest.raises(ldap.INVALID_CREDENTIALS): + conn = testuser.bind_gssapi() + + saslmap.set('nsSaslMapFilterTemplate', '(uid=\\1)') + +@gssapi_ack +def test_missing_user(topology_st_gssapi): + """Test that binding with no user does not work. + + :id: 109b5ab8-6556-4222-92d6-398476a50d30 + + :setup: standalone gssapi instance + + :steps: + 1. Create a principal with a name that is not mappable + 2. Attempt to bind + :expectedresults: + 1. The principal is created + 2. The bind fails. + """ + # Make a principal and bind with no user. + st = topology_st_gssapi.standalone + st.realm.create_principal("doesnotexist") + st.realm.create_keytab("doesnotexist", "/tmp/doesnotexist.keytab") + # Now try to bind. + subprocess.call(['/usr/bin/kdestroy', '-A']) + os.environ["KRB5_CLIENT_KTNAME"] = "/tmp/doesnotexist.keytab" + + conn = ldap.initialize(st.toLDAPURL()) + sasltok = ldap.sasl.gssapi() + + with pytest.raises(ldap.INVALID_CREDENTIALS): + conn.sasl_interactive_bind_s('', sasltok) + +@gssapi_ack +def test_support_mech(topology_st_gssapi, testuser): + """Test allowed sasl mechs works when GSSAPI is allowed + + :id: 6ec80aca-00c4-4141-b96b-3ae8837fc751 + + :setup: standalone gssapi instance + + :steps: + 1. Add GSSAPI to allowed sasl mechanisms. + 2. Attempt to bind + :expectedresults: + 1. The allowed mechs are changed. + 2. The bind succeeds. + """ + topology_st_gssapi.standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'GSSAPI EXTERNAL ANONYMOUS') + conn = testuser.bind_gssapi() + assert(conn.whoami_s() == "dn: %s" % testuser.dn.lower()) + +@gssapi_ack +def test_rejected_mech(topology_st_gssapi, testuser): + """Test allowed sasl mechs fail when GSSAPI is not allowed. + + :id: 7896c756-6f65-4390-a844-12e2eec19675 + + :setup: standalone gssapi instance + + :steps: + 1. Add GSSAPI to allowed sasl mechanisms. + 2. Attempt to bind + :expectedresults: + 1. The allowed mechs are changed. + 2. The bind fails. + """ + topology_st_gssapi.standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'EXTERNAL ANONYMOUS') + with pytest.raises(ldap.STRONG_AUTH_NOT_SUPPORTED): + conn = testuser.bind_gssapi() + topology_st_gssapi.standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'GSSAPI EXTERNAL ANONYMOUS') + diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py index 6961d1f..fab1ed1 100644 --- a/src/lib389/lib389/__init__.py +++ b/src/lib389/lib389/__init__.py @@ -50,6 +50,8 @@ import subprocess import collections import signal import errno +import pwd +import grp from shutil import copy2 try: # There are too many issues with this on EL7 @@ -390,7 +392,6 @@ class DirSrv(SimpleLDAPObject, object): args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_instance[SER_USER_ID] = None args_instance[SER_GROUP_ID] = None - args_instance[SER_REALM] = None args_instance[SER_INST_SCRIPTS_ENABLED] = None # We allocate a "default" prefix here which allows an un-allocate or @@ -522,7 +523,7 @@ class DirSrv(SimpleLDAPObject, object): self.ldapuri = args.get(SER_LDAP_URL, None) self.log.debug("Allocate %s with %s" % (self.__class__, self.ldapuri)) # Still needed in setup, even if ldapuri over writes. - self.host = args.get(SER_HOST, LOCALHOST) + self.host = args.get(SER_HOST, socket.gethostname()) self.port = args.get(SER_PORT, DEFAULT_PORT) self.sslport = args.get(SER_SECURE_PORT) @@ -564,9 +565,8 @@ class DirSrv(SimpleLDAPObject, object): # Allocate from the args, or use our env, or use / if args.get(SER_DEPLOYED_DIR, self.prefix) is not None: self.prefix = args.get(SER_DEPLOYED_DIR, self.prefix) - self.realm = args.get(SER_REALM, None) - if self.realm is not None: - self.krb5_realm = MitKrb5(realm=self.realm, debug=self.verbose) + # This will be externally populated in topologies. + self.realm = None # Those variables needs to be revisited (sroot for 64 bits) # self.sroot = os.path.join(self.prefix, "lib/dirsrv") @@ -596,6 +596,7 @@ class DirSrv(SimpleLDAPObject, object): args_instance[SER_LDAP_URL] = self.ldapuri args_instance[SER_HOST] = self.host args_instance[SER_PORT] = self.port + args_instance[SER_LDAP_URL] = self.ldapuri args_instance[SER_SECURE_PORT] = self.sslport args_instance[SER_SERVERID_PROP] = self.serverid args_standalone = args_instance.copy() @@ -863,7 +864,6 @@ class DirSrv(SimpleLDAPObject, object): SER_GROUP_ID (groupid) SER_DEPLOYED_DIR (prefix) SER_BACKUP_INST_DIR (backupdir) - SER_REALM (krb5_realm) @return None @@ -901,17 +901,6 @@ class DirSrv(SimpleLDAPObject, object): prefix=self.prefix) if result != 0: raise Exception('Failed to run setup-ds.pl') - if self.realm is not None: - # This may conflict in some tests, we may need to use /etc/host - # aliases or we may need to use server id - self.krb5_realm.create_principal(principal='ldap/%s' % self.host) - ktab = '%s/ldap.keytab' % (self.ds_paths.config_dir) - self.krb5_realm.create_keytab(principal='ldap/%s' % self.host, keytab=ktab) - with open('%s/dirsrv-%s' % (self.ds_paths.initconfig_dir, self.serverid), 'a') as sfile: - sfile.write("\nKRB5_KTNAME=%s/etc/dirsrv/slapd-%s/" - "ldap.keytab\nexport KRB5_KTNAME\n" % - (self.prefix, self.serverid)) - self.restart() def _createPythonDirsrv(self, version): """ @@ -953,17 +942,7 @@ class DirSrv(SimpleLDAPObject, object): # Go! sds.create_from_args(general, slapd, backends, None) - if self.realm is not None: - # This may conflict in some tests, we may need to use /etc/host - # aliases or we may need to use server id - self.krb5_realm.create_principal(principal='ldap/%s' % self.host) - ktab = '%s/ldap.keytab' % (self.ds_paths.config_dir) - self.krb5_realm.create_keytab(principal='ldap/%s' % self.host, keytab=ktab) - with open('%s/dirsrv-%s' % (self.ds_paths.initconfig_dir, self.serverid), 'a') as sfile: - sfile.write("\nKRB5_KTNAME=%s/etc/dirsrv/slapd-%s/" - "ldap.keytab\nexport KRB5_KTNAME\n" % - (self.prefix, self.serverid)) - self.restart() + def create(self, pyinstall=False, version=INSTALL_LATEST_CONFIG): """ @@ -1144,28 +1123,20 @@ class DirSrv(SimpleLDAPObject, object): if starttls and not uri.startswith('ldaps'): self.start_tls_s() - if saslmethod and saslmethod.lower() == 'gssapi': + if saslmethod and sasltoken is not None: + # Just pass the sasltoken in! + self.sasl_interactive_bind_s("", sasltoken) + elif saslmethod and saslmethod.lower() == 'gssapi': """ Perform kerberos/gssapi authentication """ - try: - sasl_auth = ldap.sasl.gssapi("") - self.sasl_interactive_bind_s("", sasl_auth) - except ldap.LOCAL_ERROR as e: - # No Ticket - ultimately invalid credentials - self.log.debug("Error: No Ticket (%s)" % str(e)) - raise ldap.INVALID_CREDENTIALS - except ldap.LDAPError as e: - self.log.debug("SASL/GSSAPI Bind Failed: %s" % str(e)) - raise e + sasl_auth = ldap.sasl.gssapi("") + self.sasl_interactive_bind_s("", sasl_auth) elif saslmethod == 'EXTERNAL': # Do nothing. sasl_auth = ldap.sasl.external() self.sasl_interactive_bind_s("", sasl_auth) - elif saslmethod and sasltoken is not None: - # Just pass the sasltoken in! - self.sasl_interactive_bind_s("", sasltoken) elif saslmethod: # Unknown or unsupported method self.log.debug('Unsupported SASL method: %s' % saslmethod) @@ -1651,7 +1622,8 @@ class DirSrv(SimpleLDAPObject, object): return self.ldapuri elif self.ldapi_enabled == 'on' and self.ldapi_socket is not None: return "ldapi://%s" % (ldapurl.ldapUrlEscape(ensure_str(ldapi_socket))) - elif self.sslport: + elif self.sslport and not self.realm: + # Gssapi can't use SSL so we have to nuke it here. return "ldaps://%s:%d/" % (ensure_str(self.host), self.sslport) else: return "ldap://%s:%d/" % (ensure_str(self.host), self.port) @@ -1772,6 +1744,12 @@ class DirSrv(SimpleLDAPObject, object): def get_ldapi_path(self): return self.ds_paths.ldapi + def get_user_uid(self): + return pwd.getpwnam(self.ds_paths.user).pw_uid + + def get_group_gid(self): + return grp.getgrnam(self.ds_paths.group).gr_gid + def has_asan(self): return self.ds_paths.asan_enabled diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py index 02c2954..92038bf 100644 --- a/src/lib389/lib389/idm/account.py +++ b/src/lib389/lib389/idm/account.py @@ -9,6 +9,8 @@ from lib389._mapped_object import DSLdapObject, DSLdapObjects, _gen_or, _gen_filter, _term_gen from lib389._constants import SER_ROOT_DN, SER_ROOT_PW +import os +import subprocess class Account(DSLdapObject): """A single instance of Account entry @@ -62,6 +64,35 @@ class Account(DSLdapObject): inst_clone.open(*args, **kwargs) return inst_clone + def create_keytab(self): + """ + Create a keytab for this account valid to bind with. + """ + assert self._instance.realm is not None + + myuid = self.get_attr_val_utf8('uid') + self._instance.realm.create_principal(myuid) + self._instance.realm.create_keytab(myuid, "/tmp/%s.keytab" % myuid) + + self._keytab = "/tmp/%s.keytab" % myuid + + def bind_gssapi(self): + """ + Bind this account with gssapi credntials (if available) + """ + assert self._instance.realm is not None + # Kill any local ccache. + subprocess.call(['/usr/bin/kdestroy', '-A']) + + # This uses an in memory once off ccache. + os.environ["KRB5_CLIENT_KTNAME"] = self._keytab + + # Because of the way that GSSAPI works, we can't + # use the normal dirsrv open method. + inst_clone = self._instance.clone() + inst_clone.open(saslmethod='gssapi') + return inst_clone + class Accounts(DSLdapObjects): """DSLdapObjects that represents Account entry diff --git a/src/lib389/lib389/mit_krb5.py b/src/lib389/lib389/mit_krb5.py index 64ca24d..fe37387 100644 --- a/src/lib389/lib389/mit_krb5.py +++ b/src/lib389/lib389/mit_krb5.py @@ -108,6 +108,9 @@ class MitKrb5(object): realm = self.realm lrealm = self.realm.lower() cfile.write(""" +[libdefaults] + default_realm = {REALM} + [realms] {REALM} = {{ kdc = {HOST} @@ -130,12 +133,11 @@ class MitKrb5(object): [realms] {REALM} = {{ - #master_key_type = aes256-cts acl_file = {PREFIX}/var/kerberos/krb5kdc/kadm5.acl dict_file = /usr/share/dict/words admin_keytab = {PREFIX}/var/kerberos/krb5kdc/kadm5.keytab # Just use strong enctypes - supported_enctypes = aes256-cts:normal aes128-cts:normal + # supported_enctypes = aes256-cts:normal aes128-cts:normal }} """.format(REALM=self.realm, PREFIX=self.krb_prefix)) @@ -170,7 +172,7 @@ class MitKrb5(object): p = Popen([self.kdb5_util, 'destroy', '-r', self.realm], env=self.krb_env, stdin=PIPE) - p.communicate("yes\n") + p.communicate(b"yes\n") p.wait() assert(p.returncode == 0) # Should we clean up the configurations we made too? @@ -213,14 +215,3 @@ class MitKrb5(object): (keytab, principal, self.realm)]) assert(p.wait() == 0) - -class KrbClient(object): - def __init__(self, principal, keytab, ccache=None): - self.krb_prefix = "" - self.kdestroy = "/usr/bin/kdestroy" - if ccache is not None: - os.environ["KRB5CCNAME"] = ccache - # Destroy the previous cache if any. - subprocess.call(self.kdestroy) - # Gssapi has magic that automatically creates things by env vars - os.environ["KRB5_CLIENT_KTNAME"] = keytab diff --git a/src/lib389/lib389/properties.py b/src/lib389/lib389/properties.py index 2a9e174..e986aea 100644 --- a/src/lib389/lib389/properties.py +++ b/src/lib389/lib389/properties.py @@ -44,7 +44,6 @@ SER_PROPNAME_TO_ATTRNAME = {SER_HOST: 'nsslapd-localhost', # # Those WITHOUT related attribute name # -SER_REALM = 'krb5_realm' SER_SERVERID_PROP = 'server-id' SER_GROUP_ID = 'group-id' SER_DEPLOYED_DIR = 'deployed-dir' diff --git a/src/lib389/lib389/saslmap.py b/src/lib389/lib389/saslmap.py new file mode 100644 index 0000000..3105f2e --- /dev/null +++ b/src/lib389/lib389/saslmap.py @@ -0,0 +1,54 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2016 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389._mapped_object import DSLdapObject, DSLdapObjects + +class SaslMapping(DSLdapObject): + """A sasl map providing a link from a sasl user and realm + to a valid directory server entry. + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + """ + + def __init__(self, instance, dn=None): + super(SaslMapping, self).__init__(instance, dn) + self._rdn_attribute = 'cn' + self._must_attributes = [ + 'cn', + 'nsSaslMapRegexString', + 'nsSaslMapBaseDNTemplate', + 'nsSaslMapFilterTemplate', + ] + self._create_objectclasses = [ + 'top', + 'nsSaslMapping', + ] + self._protected = False + +class SaslMappings(DSLdapObjects): + """DSLdapObjects that represents SaslMappings in the server. + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + """ + + def __init__(self, instance): + super(SaslMappings, self).__init__(instance) + self._objectclasses = [ + 'nsSaslMapping', + ] + self._filterattrs = ['cn'] + self._childobject = SaslMapping + self._basedn = 'cn=mapping,cn=sasl,cn=config' + + diff --git a/src/lib389/lib389/tests/krb5_create_test.py b/src/lib389/lib389/tests/krb5_create_test.py deleted file mode 100644 index 182d51f..0000000 --- a/src/lib389/lib389/tests/krb5_create_test.py +++ /dev/null @@ -1,147 +0,0 @@ - -# --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2015 Red Hat, Inc. -# All rights reserved. -# -# License: GPL (version 3 or any later version). -# See LICENSE for details. -# --- END COPYRIGHT BLOCK --- -# -from lib389._constants import * -from lib389.mit_krb5 import MitKrb5, KrbClient -from lib389 import DirSrv, Entry -import pytest -import logging -import socket -import subprocess - -import ldap -import ldap.sasl - -logging.getLogger(__name__).setLevel(logging.DEBUG) -log = logging.getLogger(__name__) - -INSTANCE_PORT = 54321 -INSTANCE_SERVERID = 'standalone' -REALM = "EXAMPLE.COM" -TEST_USER = 'uid=test,%s' % DEFAULT_SUFFIX - -KEYTAB = "/tmp/test.keytab" -CCACHE = "FILE:/tmp/test.ccache" - - -class TopologyInstance(object): - def __init__(self, instance): - instance.open() - self.instance = instance - - -@pytest.fixture(scope="module") -def topology(request): - # Create the realm - krb = MitKrb5(realm=REALM) - instance = DirSrv(verbose=False) - instance.log.debug("Instance allocated") - # WARNING: If this test fails it's like a hostname issue!!! - args = {SER_HOST: socket.gethostname(), - SER_PORT: INSTANCE_PORT, - SER_REALM: REALM, - SER_SERVERID_PROP: INSTANCE_SERVERID} - instance.allocate(args) - if instance.exists(): - instance.delete() - # Its likely our realm exists too - # Remove the old keytab - if os.path.exists(KEYTAB): - os.remove(KEYTAB) - if krb.check_realm(): - krb.destroy_realm() - # This will automatically create the krb entries - krb.create_realm() - instance.create() - instance.open() - - def fin(): - if instance.exists(): - instance.delete() - if krb.check_realm(): - krb.destroy_realm() - if os.path.exists(KEYTAB): - os.remove(KEYTAB) - if os.path.exists(CCACHE): - os.remove(CCACHE) - request.addfinalizer(fin) - - return TopologyInstance(instance) - - -@pytest.fixture(scope="module") -def add_user(topology): - """ - Create a user entry - """ - - log.info('Create a user entry: %s' % TEST_USER) - uentry = Entry(TEST_USER) - uentry.setValues('objectclass', 'top', 'extensibleobject') - uentry.setValues('uid', 'test') - topology.instance.add_s(uentry) - # This doesn't matter that we re-open the realm - krb = MitKrb5(realm=REALM) - krb.create_principal("test") - # We extract the kt so we can kinit from it - krb.create_keytab("test", "/tmp/test.keytab") - - -def test_gssapi(topology, add_user): - """ - Check that our bind completese with ldapwhoami correctly mapped from - the principal to our test user object. - """ - # Init our local ccache - kclient = KrbClient("test@%s" % REALM, KEYTAB, CCACHE) - # Probably need to change this to NOT be raw python ldap - # conn = ldap.initialize("ldap://%s:%s" % (LOCALHOST, INSTANCE_PORT)) - conn = ldap.initialize("ldap://%s:%s" % (socket.gethostname(), INSTANCE_PORT)) - sasl = ldap.sasl.gssapi("test@%s" % REALM) - try: - conn.sasl_interactive_bind_s('', sasl) - except Exception as e: - try: - print("%s" % subprocess.check_output(['klist'])) - except Exception as ex: - print("%s" % ex) - print("%s" % os.environ) - print("IF THIS TEST FAILS ITS LIKELY A HOSTNAME ISSUE") - raise e - assert(conn.whoami_s() == "dn: uid=test,dc=example,dc=com") - - print("Error case 1. Broken Kerberos uid mapping") - uidmapping = 'cn=Kerberos uid mapping,cn=mapping,cn=sasl,cn=config' - topology.instance.modify_s(uidmapping, [(ldap.MOD_REPLACE, 'nsSaslMapFilterTemplate', '(cn=\1)')]) - conn0 = ldap.initialize("ldap://%s:%s" % (socket.gethostname(), INSTANCE_PORT)) - try: - conn0.sasl_interactive_bind_s('', sasl) - except Exception as e: - print("Exception (expected): %s" % type(e).__name__) - print('Desc ' + str(e['desc'])) - assert isinstance(e, ldap.INVALID_CREDENTIALS) - - # undo - topology.instance.modify_s(uidmapping, [(ldap.MOD_REPLACE, 'nsSaslMapFilterTemplate', '(uid=\1)')]) - - print("Error case 2. Delete %s from DS" % TEST_USER) - topology.instance.delete_s(TEST_USER) - try: - conn0.sasl_interactive_bind_s('', sasl) - except Exception as e: - print("Exception (expected): %s" % type(e).__name__) - print('Desc ' + str(e['desc'])) - assert isinstance(e, ldap.INVALID_CREDENTIALS) - - print("SUCCESS") - - -if __name__ == "__main__": - CURRENT_FILE = os.path.realpath(__file__) - pytest.main("-s -v %s" % CURRENT_FILE) diff --git a/src/lib389/lib389/tools.py b/src/lib389/lib389/tools.py index edb95b1..f0f4b1c 100644 --- a/src/lib389/lib389/tools.py +++ b/src/lib389/lib389/tools.py @@ -857,7 +857,7 @@ class DirSrvTools(object): for line in hostfp.readlines(): if ipPattern is None: words = line.split() - if words[1] == expectedHost: + if len(words) >= 2 and words[1] == expectedHost: return True else: if line.find(ipPattern) >= 0: diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py index 068a24a..ae64828 100644 --- a/src/lib389/lib389/topologies.py +++ b/src/lib389/lib389/topologies.py @@ -10,13 +10,19 @@ import os import logging import time +# For hostname detection for GSSAPI tests +import socket + import pytest from lib389 import DirSrv from lib389.nss_ssl import NssSsl from lib389.utils import generate_ds_params from lib389.replica import Replicas -from lib389._constants import (args_instance, SER_HOST, SER_PORT, SER_SERVERID_PROP, SER_CREATION_SUFFIX, +from lib389.mit_krb5 import MitKrb5 +from lib389.saslmap import SaslMappings + +from lib389._constants import (SER_HOST, SER_PORT, SER_SERVERID_PROP, SER_CREATION_SUFFIX, SER_SECURE_PORT, ReplicaRole, DEFAULT_SUFFIX, REPLICA_ID, SER_LDAP_URL) @@ -61,7 +67,8 @@ def create_topology(topo_dict): # Also, we need to keep in mind that the function returns # SER_SECURE_PORT and REPLICA_ID that are not used in # the instance creation here. - args_instance[SER_HOST] = instance_data[SER_HOST] + # args_instance[SER_HOST] = instance_data[SER_HOST] + args_instance = {} args_instance[SER_PORT] = instance_data[SER_PORT] args_instance[SER_SECURE_PORT] = instance_data[SER_SECURE_PORT] args_instance[SER_SERVERID_PROP] = instance_data[SER_SERVERID_PROP] @@ -198,6 +205,68 @@ def topology_st(request): return topology +gssapi_ack = pytest.mark.skipif(not os.environ.get('GSSAPI_ACK', False), reason="GSSAPI tests may damage system configuration.") + +@pytest.fixture(scope="module") +def topology_st_gssapi(request): + """Create a DS standalone instance with GSSAPI enabled. + + This will alter the instance to remove the secure port, to allow + GSSAPI to function. + """ + hostname = socket.gethostname().split('.', 1) + + # Assert we have a domain setup in some kind. + assert len(hostname) == 2 + + REALM = hostname[1].upper() + + topology = create_topology({ReplicaRole.STANDALONE: 1}) + + # Fix the hostname. + topology.standalone.host = socket.gethostname() + + krb = MitKrb5(realm=REALM, debug=DEBUGGING) + + # Destroy existing realm. + if krb.check_realm(): + krb.destroy_realm() + krb.create_realm() + + # Now add krb to our instance. + krb.create_principal(principal='ldap/%s' % topology.standalone.host) + krb.create_keytab(principal='ldap/%s' % topology.standalone.host, keytab='/etc/krb5.keytab') + os.chown('/etc/krb5.keytab', topology.standalone.get_user_uid(), topology.standalone.get_group_gid()) + + # Add sasl mappings + saslmappings = SaslMappings(topology.standalone) + saslmappings.create(properties={ + 'cn': 'suffix map', + # Don't add the realm due to a SASL bug + # 'nsSaslMapRegexString': '\\(.*\\)@%s' % self.realm, + 'nsSaslMapRegexString': '\\(.*\\)', + 'nsSaslMapBaseDNTemplate': topology.standalone.creation_suffix, + 'nsSaslMapFilterTemplate': '(uid=\\1)' + }) + topology.standalone.realm = krb + + topology.standalone.config.set('nsslapd-localhost', topology.standalone.host) + + topology.standalone.sslport = None + + topology.standalone.restart() + + def fin(): + if DEBUGGING: + topology.standalone.stop() + else: + topology.standalone.delete() + krb.destroy_realm() + + request.addfinalizer(fin) + + return topology + @pytest.fixture(scope="module") def topology_i2(request): @@ -338,7 +407,8 @@ def topology_m1h1c1(request): instance = DirSrv(verbose=True) else: instance = DirSrv(verbose=False) - args_instance[SER_HOST] = instance_data[SER_HOST] + args_instance = {} + # args_instance[SER_HOST] = instance_data[SER_HOST] args_instance[SER_PORT] = instance_data[SER_PORT] args_instance[SER_SECURE_PORT] = instance_data[SER_SECURE_PORT] args_instance[SER_SERVERID_PROP] = instance_data[SER_SERVERID_PROP] -- 1.8.3.1