From 8520361a11cc66048c2703c4562ef594521f3230 Mon Sep 17 00:00:00 2001 From: William Brown Date: Mon, 27 Nov 2017 14:47:44 +0100 Subject: [PATCH] Ticket 49218 - Certmap - support TLS tests Bug Description: This adds support for pluggable certificate mapping libraries. To achieve this, this replaces the existing baked in certificate mapping code. Fix Description: Improve our tls tests to cover more cases, support external signing cas, user certs, and addition of TLS by default to tests. This fixes some tests to use the new interfaces, as well as extending topologies to allow tls enabling. https://pagure.io/389-ds-base/issue/49218 https://pagure.io/lib389/issue/95 https://pagure.io/lib389/issue/84 Author: wibrown Review by: ??? --- .../tests/stress/reliabilty/reliab_7_5_test.py | 26 ++- .../tests/stress/reliabilty/reliab_conn_test.py | 15 +- dirsrvtests/tests/suites/sasl/plain_test.py | 33 +-- dirsrvtests/tests/tickets/ticket48784_test.py | 68 +----- dirsrvtests/tests/tickets/ticket48798_test.py | 73 +------ ldap/ldif/template-dse.ldif.in | 15 ++ ldap/schema/30ns-common.ldif | 3 +- rpm/389-ds-base.spec.in | 8 +- src/lib389/lib389/__init__.py | 195 +++++++++++------ src/lib389/lib389/_mapped_object.py | 43 ++-- src/lib389/lib389/idm/account.py | 26 +++ src/lib389/lib389/idm/directorymanager.py | 42 ++++ src/lib389/lib389/idm/services.py | 5 +- src/lib389/lib389/idm/user.py | 17 ++ src/lib389/lib389/instance/options.py | 5 + src/lib389/lib389/instance/setup.py | 47 +++- src/lib389/lib389/nss_ssl.py | 242 ++++++++++++++++----- src/lib389/lib389/tests/nss_ssl_test.py | 89 +++++--- src/lib389/lib389/topologies.py | 15 +- 19 files changed, 591 insertions(+), 376 deletions(-) create mode 100644 src/lib389/lib389/idm/directorymanager.py diff --git a/dirsrvtests/tests/stress/reliabilty/reliab_7_5_test.py b/dirsrvtests/tests/stress/reliabilty/reliab_7_5_test.py index 2be6167..3fdbd67 100644 --- a/dirsrvtests/tests/stress/reliabilty/reliab_7_5_test.py +++ b/dirsrvtests/tests/stress/reliabilty/reliab_7_5_test.py @@ -19,6 +19,8 @@ from lib389.properties import * from lib389.tasks import * from lib389.utils import * +from lib389.idm.directorymanager import DirectoryManager + logging.getLogger(__name__).setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s' + ' - %(message)s') @@ -34,6 +36,7 @@ CHECK_CONVERGENCE = True ENABLE_VALGRIND = False RUNNING = True +DEBUGGING = os.getenv('DEBUGGING', default=False) class TopologyReplication(object): def __init__(self, master1, master2): @@ -50,9 +53,10 @@ def topology(request): args_instance[SER_DEPLOYED_DIR] = installation1_prefix # Creating master 1... - master1 = DirSrv(verbose=False) + master1 = DirSrv(verbose=DEBUGGING) args_instance[SER_HOST] = HOST_MASTER_1 args_instance[SER_PORT] = PORT_MASTER_1 + args_instance[SER_SECURE_PORT] = SECUREPORT_MASTER_1 args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1 args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_master = args_instance.copy() @@ -66,9 +70,10 @@ def topology(request): replicaId=REPLICAID_MASTER_1) # Creating master 2... - master2 = DirSrv(verbose=False) + master2 = DirSrv(verbose=DEBUGGING) args_instance[SER_HOST] = HOST_MASTER_2 args_instance[SER_PORT] = PORT_MASTER_2 + args_instance[SER_SECURE_PORT] = SECUREPORT_MASTER_2 args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2 args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_master = args_instance.copy() @@ -220,7 +225,8 @@ class AddDelUsers(threading.Thread): idx = 0 RDN = 'uid=add_del_master_' + self.id + '-' - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() + while idx < NUM_USERS: USER_DN = RDN + str(idx) + ',' + DEFAULT_SUFFIX try: @@ -236,7 +242,7 @@ class AddDelUsers(threading.Thread): conn.close() # Delete 5000 entries - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: USER_DN = RDN + str(idx) + ',' + DEFAULT_SUFFIX @@ -259,7 +265,7 @@ class ModUsers(threading.Thread): def run(self): # Mod existing entries - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: USER_DN = ('uid=master' + self.id + '_entry' + str(idx) + ',' + @@ -275,7 +281,7 @@ class ModUsers(threading.Thread): conn.close() # Modrdn existing entries - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: USER_DN = ('uid=master' + self.id + '_entry' + str(idx) + ',' + @@ -290,7 +296,7 @@ class ModUsers(threading.Thread): conn.close() # Undo modrdn to we can rerun this test - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: USER_DN = ('cn=master' + self.id + '_entry' + str(idx) + ',' + @@ -315,7 +321,7 @@ class DoSearches(threading.Thread): def run(self): # Equality - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: search_filter = ('(|(uid=master' + self.id + '_entry' + str(idx) + @@ -333,7 +339,7 @@ class DoSearches(threading.Thread): conn.close() # Substring - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() idx = 0 while idx < NUM_USERS: search_filter = ('(|(uid=master' + self.id + '_entry' + str(idx) + @@ -360,7 +366,7 @@ class DoFullSearches(threading.Thread): def run(self): global RUNNING - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind() while RUNNING: time.sleep(2) try: diff --git a/dirsrvtests/tests/stress/reliabilty/reliab_conn_test.py b/dirsrvtests/tests/stress/reliabilty/reliab_conn_test.py index d3ee773..c14f88d 100644 --- a/dirsrvtests/tests/stress/reliabilty/reliab_conn_test.py +++ b/dirsrvtests/tests/stress/reliabilty/reliab_conn_test.py @@ -12,8 +12,9 @@ from lib389._constants import * from lib389.properties import * from lib389.tasks import * from lib389.utils import * +from lib389.idm.directorymanager import DirectoryManager -DEBUGGING = False +DEBUGGING = os.getenv('DEBUGGING', default=False) if DEBUGGING: logging.getLogger(__name__).setLevel(logging.DEBUG) @@ -41,12 +42,10 @@ def topology(request): """Create DS Deployment""" # Creating standalone instance ... - if DEBUGGING: - standalone = DirSrv(verbose=True) - else: - standalone = DirSrv(verbose=False) + standalone = DirSrv(verbose=DEBUGGING) args_instance[SER_HOST] = HOST_STANDALONE args_instance[SER_PORT] = PORT_STANDALONE + args_instance[SER_SECURE_PORT] = SECUREPORT_STANDALONE args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_standalone = args_instance.copy() @@ -129,7 +128,7 @@ class BindOnlyConn(threading.Thread): global STOP while idx < MAX_CONNS and not STOP: try: - conn = self.inst.openConnection() + conn = DirectoryManager(self.inst).bind(connOnly=True) conn.unbind_s() time.sleep(.2) err_count = 0 @@ -160,7 +159,7 @@ class IdleConn(threading.Thread): global STOP while idx < (MAX_CONNS / 10) and not STOP: try: - conn = self.inst.openConnection() + conn = self.inst.clone() conn.simple_bind_s('uid=entry0,dc=example,dc=com', 'password') conn.search_s('dc=example,dc=com', ldap.SCOPE_SUBTREE, 'uid=*') @@ -197,7 +196,7 @@ class LongConn(threading.Thread): global STOP while idx < MAX_CONNS and not STOP: try: - conn = self.inst.openConnection() + conn = self.inst.clone() conn.search_s('dc=example,dc=com', ldap.SCOPE_SUBTREE, 'objectclass=*') conn.search_s('dc=example,dc=com', ldap.SCOPE_SUBTREE, diff --git a/dirsrvtests/tests/suites/sasl/plain_test.py b/dirsrvtests/tests/suites/sasl/plain_test.py index cff2507..10f0704 100644 --- a/dirsrvtests/tests/suites/sasl/plain_test.py +++ b/dirsrvtests/tests/suites/sasl/plain_test.py @@ -15,7 +15,7 @@ from lib389.topologies import topology_st from lib389.utils import * from lib389._constants import DEFAULT_SUFFIX, DEFAULT_SECURE_PORT from lib389.sasl import PlainSASL -from lib389.idm.services import ServiceAccounts +from lib389.idm.services import ServiceAccounts, ServiceAccount log = logging.getLogger(__name__) @@ -57,28 +57,9 @@ def test_basic_feature(topology_st): 14. INVALID_CREDENTIALS exception should be raised """ - standalone = topology_st.standalone + [i.enable_tls() for i in topology_st] - # SETUP TLS - standalone.stop() - # Prepare SSL but don't enable it. - for f in ('key3.db', 'cert8.db', 'key4.db', 'cert9.db', 'secmod.db', 'pkcs11.txt'): - try: - os.remove("%s/%s" % (standalone.confdir, f)) - except: - pass - assert(standalone.nss_ssl.reinit() is True) - assert(standalone.nss_ssl.create_rsa_ca() is True) - assert(standalone.nss_ssl.create_rsa_key_and_cert() is True) - # Start again - standalone.start() - standalone.rsa.create() - # Set the secure port and nsslapd-security - # Could this fail with selinux? - standalone.config.set('nsslapd-secureport', str(DEFAULT_SECURE_PORT)) - standalone.config.set('nsslapd-security', 'on') - # Do we need to restart to allow starttls? - standalone.restart() + standalone = topology_st.standalone # Create a user sas = ServiceAccounts(standalone, DEFAULT_SUFFIX) @@ -96,20 +77,18 @@ def test_basic_feature(topology_st): # Check that it fails without TLS with pytest.raises(ldap.AUTH_UNKNOWN): - standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=False, connOnly=True) + conn = sa.sasl_bind(uri=standalone.get_ldap_uri(), saslmethod='PLAIN', sasltoken=auth_tokens, connOnly=True) # We *have* to use REQCERT NEVER here because python ldap fails cert verification for .... some reason that even # I can not solve. I think it's leaking state across connections in start_tls_s? # Check that it works with TLS - conn = standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=True, connOnly=True, - certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER) + conn = sa.sasl_bind(uri=standalone.get_ldaps_uri(), saslmethod='PLAIN', sasltoken=auth_tokens, connOnly=True) conn.close() # Check that it correct fails our bind if we don't have the password. auth_tokens = PlainSASL("dn:%s" % sa.dn, 'password-wrong') with pytest.raises(ldap.INVALID_CREDENTIALS): - standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=True, connOnly=True, - certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER) + conn = sa.sasl_bind(uri=standalone.get_ldaps_uri(), saslmethod='PLAIN', sasltoken=auth_tokens, connOnly=True) # Done! diff --git a/dirsrvtests/tests/tickets/ticket48784_test.py b/dirsrvtests/tests/tickets/ticket48784_test.py index 0f63302..8924fe2 100644 --- a/dirsrvtests/tests/tickets/ticket48784_test.py +++ b/dirsrvtests/tests/tickets/ticket48784_test.py @@ -44,70 +44,6 @@ def add_entry(server, name, rdntmpl, start, num): log.error('Failed to add %s ' % dn + e.message['desc']) assert False - -def enable_ssl(server, ldapsport, copy_serv=False): - server.stop() - server.nss_ssl.reinit() - if copy_serv: - ca_cert = copy_serv.get_cert_dir() + "/ca.crt" - os.system('cp %s/*.db %s' % (copy_serv.get_cert_dir(), server.get_cert_dir())) - os.system('cp %s %s' % (ca_cert, server.get_cert_dir())) - os.system('cp %s/noise* %s' % (copy_serv.get_cert_dir(), server.get_cert_dir())) - os.system('cp %s/p* %s' % (copy_serv.get_cert_dir(), server.get_cert_dir())) - else: - server.nss_ssl.create_rsa_ca() - server.nss_ssl.create_rsa_key_and_cert() - server.start() - - server.modify_s(ENCRYPTION_DN, [(ldap.MOD_REPLACE, 'nsSSL3', 'off'), - (ldap.MOD_REPLACE, 'nsTLS1', 'on'), - (ldap.MOD_REPLACE, 'nsSSLClientAuth', 'allowed'), - (ldap.MOD_REPLACE, 'allowWeakCipher', 'on'), - (ldap.MOD_REPLACE, 'nsSSL3Ciphers', '+all')]) - - time.sleep(1) - server.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-security', 'on'), - (ldap.MOD_REPLACE, 'nsslapd-ssl-check-hostname', 'off'), - (ldap.MOD_REPLACE, 'nsslapd-secureport', ldapsport)]) - - time.sleep(1) - server.add_s(Entry((RSA_DN, {'objectclass': "top nsEncryptionModule".split(), - 'cn': RSA, - 'nsSSLPersonalitySSL': SERVERCERT, - 'nsSSLToken': 'internal (software)', - 'nsSSLActivation': 'on'}))) - time.sleep(1) - server.restart() - - -def doAndPrintIt(cmdline, filename): - proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if filename is None: - log.info(" OUT:") - else: - log.info(" OUT: %s" % filename) - fd = open(filename, "w") - while True: - l = proc.stdout.readline() - if l == "": - break - if filename is None: - log.info(" %s" % l) - else: - fd.write(l) - log.info(" ERR:") - while True: - l = proc.stderr.readline() - if l == "" or l == "\n": - break - log.info(" <%s>" % l) - assert False - - if filename is not None: - fd.close() - time.sleep(1) - - def config_tls_agreements(topology_m2): log.info("######################### Configure SSL/TLS agreements ######################") log.info("######################## master1 <-- startTLS -> master2 #####################") @@ -152,8 +88,8 @@ def test_ticket48784(topology_m2): log.info("Ticket 48784 - Allow usage of OpenLDAP libraries that don't use NSS for crypto") #create_keys_certs(topology_m2) - enable_ssl(topology_m2.ms["master1"], '636') - enable_ssl(topology_m2.ms["master2"], '637', topology_m2.ms["master1"]) + [i.enable_tls() for i in topology_m2] + config_tls_agreements(topology_m2) add_entry(topology_m2.ms["master1"], 'master1', 'uid=m1user', 0, 5) diff --git a/dirsrvtests/tests/tickets/ticket48798_test.py b/dirsrvtests/tests/tickets/ticket48798_test.py index 80f307c..e0c3917 100644 --- a/dirsrvtests/tests/tickets/ticket48798_test.py +++ b/dirsrvtests/tests/tickets/ticket48798_test.py @@ -4,6 +4,7 @@ import pytest from lib389.tasks import * from lib389.utils import * from lib389.topologies import topology_st +from lib389.config import Encryption from lib389._constants import DEFAULT_SUFFIX, DEFAULT_SECURE_PORT @@ -18,9 +19,9 @@ def check_socket_dh_param_size(hostname, port): HOSTNAME=hostname, PORT=port) output = check_output(cmd, shell=True) - dhheader = output.split('\n')[1] + dhheader = output.split(b'\n')[1] # Get rid of all the other whitespace. - dhheader = dhheader.replace(' ', '') + dhheader = dhheader.replace(b' ', b'') # Example is 0c00040b0100ffffffffffffffffadf8 # We need the bits 0100 here. Which means 256 bytes aka 256 * 8, for 2048 bit. dhheader = dhheader[8:12] @@ -34,78 +35,26 @@ def test_ticket48798(topology_st): Test DH param sizes offered by DS. """ - - # Create a CA - # This is a trick. The nss db that ships with DS is broken fundamentally. - ## THIS ASSUMES old nss format. SQLite will bite us! - for f in ('key3.db', 'cert8.db', 'key4.db', 'cert9.db', 'secmod.db', 'pkcs11.txt'): - try: - os.remove("%s/%s" % (topology_st.standalone.confdir, f)) - except: - pass - - # Check if the db exists. Should be false. - assert (topology_st.standalone.nss_ssl._db_exists() is False) - time.sleep(0.5) - - # Create it. Should work. - assert (topology_st.standalone.nss_ssl.reinit() is True) - time.sleep(0.5) - - # Check if the db exists. Should be true - assert (topology_st.standalone.nss_ssl._db_exists() is True) - time.sleep(0.5) - - # Check if ca exists. Should be false. - assert (topology_st.standalone.nss_ssl._rsa_ca_exists() is False) - time.sleep(0.5) - - # Create it. Should work. - assert (topology_st.standalone.nss_ssl.create_rsa_ca() is True) - time.sleep(0.5) - - # Check if ca exists. Should be true - assert (topology_st.standalone.nss_ssl._rsa_ca_exists() is True) - time.sleep(0.5) - - # Check if we have a server cert / key. Should be false. - assert (topology_st.standalone.nss_ssl._rsa_key_and_cert_exists() is False) - time.sleep(0.5) - - # Create it. Should work. - assert (topology_st.standalone.nss_ssl.create_rsa_key_and_cert() is True) - time.sleep(0.5) - - # Check if server cert and key exist. Should be true. - assert (topology_st.standalone.nss_ssl._rsa_key_and_cert_exists() is True) - time.sleep(0.5) - - topology_st.standalone.config.enable_ssl(secport=DEFAULT_SECURE_PORT, secargs={'nsSSL3Ciphers': '+all'}) - - topology_st.standalone.restart(30) + [i.enable_tls() for i in topology_st] # Confirm that we have a connection, and that it has DH # Open a socket to the port. # Check the security settings. - size = check_socket_dh_param_size(topology_st.standalone.host, DEFAULT_SECURE_PORT) + size = check_socket_dh_param_size(topology_st.standalone.host, topology_st.standalone.sslport) - assert (size == 2048) + assert size == 2048 # Now toggle the settings. - mod = [(ldap.MOD_REPLACE, 'allowWeakDHParam', 'on')] - dn_enc = 'cn=encryption,cn=config' - topology_st.standalone.modify_s(dn_enc, mod) + enc = Encryption(topology_st.standalone) + enc.set('allowWeakDHParam', 'on') - topology_st.standalone.restart(30) + topology_st.standalone.restart() # Check the DH params are less than 1024. - size = check_socket_dh_param_size(topology_st.standalone.host, DEFAULT_SECURE_PORT) - - assert (size == 1024) - - log.info('Test complete') + size = check_socket_dh_param_size(topology_st.standalone.host, topology_st.standalone.sslport) + assert size == 1024 if __name__ == '__main__': # Run isolated diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index 3107903..a034325 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -21,6 +21,21 @@ nsslapd-auditfaillog: %log_dir%/audit nsslapd-rootdn: %rootdn% nsslapd-rootpw: %ds_passwd% +dn: cn=encryption,cn=config +objectClass: top +objectClass: nsEncryptionConfig +cn: encryption +nsSSLSessionTimeout: 0 +nsSSLClientAuth: allowed + +dn: cn=RSA,cn=encryption,cn=config +objectClass: top +objectClass: nsEncryptionModule +cn: RSA +nsSSLPersonalitySSL: Server-Cert +nsSSLActivation: on +nsSSLToken: internal (software) + dn: cn=features,cn=config objectclass: top objectclass: nsContainer diff --git a/ldap/schema/30ns-common.ldif b/ldap/schema/30ns-common.ldif index b095909..2b6edc1 100644 --- a/ldap/schema/30ns-common.ldif +++ b/ldap/schema/30ns-common.ldif @@ -53,6 +53,7 @@ attributeTypes: ( nsExecRef-oid NAME 'nsExecRef' DESC 'Netscape defined attribut attributeTypes: ( nsLogSuppress-oid NAME 'nsLogSuppress' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( nsJarfilename-oid NAME 'nsJarfilename' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( nsClassname-oid NAME 'nsClassname' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2337 NAME 'nsCertSubjectDN' DESC 'An x509 DN from a certificate used to map during a TLS bind process' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN '389 Directory Server Project' ) objectClasses: ( nsAdminDomain-oid NAME 'nsAdminDomain' DESC 'Netscape defined objectclass' SUP organizationalUnit MAY ( nsAdminDomainName ) X-ORIGIN 'Netscape' ) objectClasses: ( nsHost-oid NAME 'nsHost' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( serverHostName $ description $ l $ nsHostLocation $ nsHardwarePlatform $ nsOsVersion ) X-ORIGIN 'Netscape' ) objectClasses: ( nsAdminGroup-oid NAME 'nsAdminGroup' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsAdminGroupName $ description $ nsConfigRoot $ nsAdminSIEDN ) X-ORIGIN 'Netscape' ) @@ -64,4 +65,4 @@ objectClasses: ( nsAdminObject-oid NAME 'nsAdminObject' DESC 'Netscape defined o objectClasses: ( nsConfig-oid NAME 'nsConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( description $ nsServerPort $ nsServerAddress $ nsSuiteSpotUser $ nsErrorLog $ nsPidLog $ nsAccessLog $ nsDefaultAcceptLanguage $ nsServerSecurity ) X-ORIGIN 'Netscape' ) objectClasses: ( nsDirectoryInfo-oid NAME 'nsDirectoryInfo' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsBindDN $ nsBindPassword $ nsDirectoryURL $ nsDirectoryFailoverList $ nsDirectoryInfoRef ) X-ORIGIN 'Netscape' ) objectClasses: ( 2.16.840.1.113730.3.2.329 NAME 'nsMemberOf' DESC 'Allow memberOf assignment on groups for nesting and users' SUP top AUXILIARY MAY ( memberOf ) X-ORIGIN '389 Directory Server Project' ) - +objectClasses: ( 2.16.840.1.113730.3.2.331 NAME 'nsAccount' DESC 'A representation of a user in a directory server' SUP top AUXILIARY MAY ( userCertificate $ nsCertSubjectDN ) X-ORIGIN '389 Directory Server Project' ) diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in index 7938d16..76fb96e 100644 --- a/rpm/389-ds-base.spec.in +++ b/rpm/389-ds-base.spec.in @@ -235,9 +235,10 @@ Summary: A library for accessing, testing, and configuring the 389 Directory Se BuildArch: noarch Group: Development/Libraries Requires: krb5-workstation -Requires: krb5-server Requires: openssl Requires: iproute +# This is for /usr/sbin/cacertdir_rehash +Requires: authconfig Requires: python%{python3_pkgversion} Requires: python%{python3_pkgversion}-pytest Requires: python%{python3_pkgversion}-pyldap @@ -254,8 +255,9 @@ This module contains tools and libraries for accessing, testing, Summary: The lib389 Continuous Integration Tests Group: Development/Libraries BuildArch: noarch -Requires: python%{python3_pkgversion} -Requires: python%{python3_pkgversion}-lib389 +Requires: krb5-server +Requires: python%{python3_pkgversion} +Requires: python%{python3_pkgversion}-lib389 %description -n python%{python3_pkgversion}-%{srcname}-tests The lib389 CI tests that can be run against the Directory Server. diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py index 1f15a6d..e23ae91 100644 --- a/src/lib389/lib389/__init__.py +++ b/src/lib389/lib389/__init__.py @@ -73,6 +73,7 @@ from lib389._ldifconn import LDIFConn from lib389.tools import DirSrvTools from lib389.mit_krb5 import MitKrb5 from lib389.utils import ( + ds_is_older, isLocalHost, is_a_dn, normalizeDN, @@ -83,6 +84,7 @@ from lib389.utils import ( ensure_bytes, ensure_str) from lib389.paths import Paths +from lib389.nss_ssl import NssSsl # mixin # from lib389.tools import DirSrvTools @@ -292,7 +294,6 @@ class DirSrv(SimpleLDAPObject, object): def __add_brookers__(self): from lib389.config import Config from lib389.aci import Aci - from lib389.nss_ssl import NssSsl from lib389.config import RSA from lib389.config import Encryption from lib389.dirsrv_log import DirsrvAccessLog, DirsrvErrorLog @@ -334,7 +335,6 @@ class DirSrv(SimpleLDAPObject, object): self.mappingtrees = MappingTrees(self) self.replicas = Replicas(self) self.aci = Aci(self) - self.nss_ssl = NssSsl(self) self.rsa = RSA(self) self.encryption = Encryption(self) self.ds_access_log = DirsrvAccessLog(self) @@ -503,51 +503,48 @@ class DirSrv(SimpleLDAPObject, object): raise ValueError("invalid state for calling allocate: %s" % self.state) + self.isLocal = False if SER_SERVERID_PROP not in args: self.log.debug('SER_SERVERID_PROP not provided, assuming non-local instance') # The lack of this value basically rules it out in most cases - self.isLocal = False self.ds_paths = Paths(instance=self) else: self.ds_paths = Paths(args[SER_SERVERID_PROP], instance=self) - + # Settings from args of server attributes + self.serverid = args.get(SER_SERVERID_PROP, None) + # Probably local? + self.isLocal = True # Do we have ldapi settings? # Do we really need .strip() on this? self.ldapi_enabled = args.get(SER_LDAPI_ENABLED, 'off') self.ldapi_socket = args.get(SER_LDAPI_SOCKET, None) - self.host = None - self.ldapuri = None - self.sslport = None - self.port = None + 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.port = args.get(SER_PORT, DEFAULT_PORT) + self.sslport = args.get(SER_SECURE_PORT) + self.inst_scripts = args.get(SER_INST_SCRIPTS_ENABLED, None) + # Or do we have tcp / ip settings? if self.ldapi_enabled == 'on' and self.ldapi_socket is not None: self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off') self.isLocal = True if self.verbose: self.log.info("Allocate %s with %s" % (self.__class__, self.ldapi_socket)) - elif args.get(SER_LDAP_URL, None) is not None: - self.ldapuri = args.get(SER_LDAP_URL) - if self.verbose: - self.log.info("Allocate %s with %s" % (self.__class__, self.ldapuri)) - else: - # Settings from args of server attributes - self.strict_hostname = args.get(SER_STRICT_HOSTNAME_CHECKING, False) - if self.strict_hostname is True: - self.host = args.get(SER_HOST, LOCALHOST) - if self.host == LOCALHOST: - DirSrvTools.testLocalhost() - else: - # Make sure our name is in hosts - DirSrvTools.searchHostsFile(self.host, None) + # Settings from args of server attributes + self.strict_hostname = args.get(SER_STRICT_HOSTNAME_CHECKING, False) + if self.strict_hostname is True: + if self.host == LOCALHOST: + DirSrvTools.testLocalhost() else: - self.host = args.get(SER_HOST, LOCALHOST_SHORT) - self.port = args.get(SER_PORT, DEFAULT_PORT) - self.sslport = args.get(SER_SECURE_PORT) + # Make sure our name is in hosts + DirSrvTools.searchHostsFile(self.host, None) self.isLocal = isLocalHost(self.host) - if self.verbose: - self.log.info("Allocate %s with %s:%s" % (self.__class__, self.host, (self.sslport or self.port))) + + self.log.debug("Allocate %s with %s:%s" % (self.__class__, self.host, (self.sslport or self.port))) self.binddn = args.get(SER_ROOT_DN, DN_DM) self.bindpw = args.get(SER_ROOT_PW, PW_DM) @@ -562,8 +559,6 @@ class DirSrv(SimpleLDAPObject, object): else: self.userid = pwd.getpwuid(os.getuid())[0] - # Settings from args of server attributes - self.serverid = args.get(SER_SERVERID_PROP, None) self.groupid = args.get(SER_GROUP_ID, self.userid) self.backupdir = args.get(SER_BACKUP_INST_DIR, DEFAULT_BACKUPDIR) # Allocate from the args, or use our env, or use / @@ -589,23 +584,22 @@ class DirSrv(SimpleLDAPObject, object): (self.sslport or self.port))) - def openConnection(self, *args, **kwargs): + def clone(self, args_instance={}): """ Open a new connection to our LDAP server *IMPORTANT* - This is different to re-opening on the same dirsrv, as bugs in pyldap + This is different to re-opening on the same dirsrv, as quirks in pyldap mean that ldap.set_option doesn't take effect! You need to use this - to allow some of the start TLS options to work! + to allow all of the start TLS options to work! """ server = DirSrv(verbose=self.verbose) + args_instance[SER_LDAP_URL] = self.ldapuri args_instance[SER_HOST] = self.host args_instance[SER_PORT] = self.port - if self.sslport is not None: - args_instance[SER_SECURE_PORT] = self.sslport + args_instance[SER_SECURE_PORT] = self.sslport args_instance[SER_SERVERID_PROP] = self.serverid args_standalone = args_instance.copy() server.allocate(args_standalone) - server.open(*args, **kwargs) return server @@ -919,8 +913,6 @@ class DirSrv(SimpleLDAPObject, object): (self.prefix, self.serverid)) self.restart() - # Restart the instance - def _createPythonDirsrv(self, version): """ Create a new dirsrv instance based on the new python installer, rather @@ -944,6 +936,8 @@ class DirSrv(SimpleLDAPObject, object): slapd_options.set('secure_port', self.sslport) slapd_options.set('root_password', self.bindpw) slapd_options.set('root_dn', self.binddn) + #We disable TLS during setup, we use a function in tests to enable instead. + slapd_options.set('self_sign_cert', False) slapd_options.set('defaults', version) slapd_options.verify() @@ -1003,6 +997,10 @@ class DirSrv(SimpleLDAPObject, object): else: self._createDirsrv() + # Because of how this works, we force ldap:// only for now. + # A real install will have ldaps, and won't go via this path. + self.use_ldap_uri() + # Retrieve sroot from the sys/priv config file assert(self.exists()) self.sroot = self.list()[0][CONF_SERVER_DIR] @@ -1071,7 +1069,7 @@ class DirSrv(SimpleLDAPObject, object): # Now, we are still an allocated ds object so we can be re-installed self.state = DIRSRV_STATE_ALLOCATED - def open(self, saslmethod=None, sasltoken=None, certdir=None, starttls=False, connOnly=False, reqcert=ldap.OPT_X_TLS_HARD, + def open(self, uri=None, saslmethod=None, sasltoken=None, certdir=None, starttls=False, connOnly=False, reqcert=ldap.OPT_X_TLS_HARD, usercert=None, userkey=None): ''' It opens a ldap bound connection to dirsrv so that online @@ -1090,60 +1088,55 @@ class DirSrv(SimpleLDAPObject, object): @raise LDAPError ''' - ################## - # WARNING: While you have a python ldap connection open some settings like - # ldap.set_option MAY NOT WORK AS YOU EXPECT. - # There are cases (especially CACERT/USERCERTS) where when one connection - # is open set_option SILENTLY fails!!!! - # - # You MAY need to set post_open=False in your DirSrv start/restart instance! - ################## + # Force our state offline to prevent paths from trying to search + # cn=config while we startup. + self.state = DIRSRV_STATE_OFFLINE - uri = self.toLDAPURL() + if not uri: + uri = self.toLDAPURL() + self.log.debug('open(): Connecting to uri %s' % uri) + super(DirSrv, self).__init__(uri, bytes_mode=False, trace_level=TRACE_LEVEL) if certdir is None and self.isLocal: certdir = self.get_cert_dir() self.log.debug("Using dirsrv ca certificate %s" % certdir) + if certdir is not None: + """ + We have a certificate directory, so lets start up TLS negotiations + """ + # Note this sets LDAP.OPT not SELF. Because once self has opened + # it can NOT change opts AT ALL. + self.set_option(ldap.OPT_X_TLS_CACERTDIR, ensure_str(certdir)) + self.log.debug("Using external ca certificate %s" % certdir) + if userkey is not None: # Note this sets LDAP.OPT not SELF. Because once self has opened # it can NOT change opts AT ALL. - ldap.set_option(ldap.OPT_X_TLS_KEYFILE, ensure_str(userkey)) self.log.debug("Using user private key %s" % userkey) + self.set_option(ldap.OPT_X_TLS_KEYFILE, ensure_str(userkey)) + if usercert is not None: - # Note this sets LDAP.OPT not SELF. Because once self has opened - # it can NOT change opts AT ALL. - ldap.set_option(ldap.OPT_X_TLS_CERTFILE, ensure_str(usercert)) self.log.debug("Using user certificate %s" % usercert) + self.set_option(ldap.OPT_X_TLS_CERTFILE, ensure_str(usercert)) if certdir is not None: - """ - We have a certificate directory, so lets start up TLS negotiations - """ - # Note this sets LDAP.OPT not SELF. Because once self has opened - # it can NOT change opts AT ALL. - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, ensure_str(certdir)) self.log.debug("Using external ca certificate %s" % certdir) + self.set_option(ldap.OPT_X_TLS_CACERTDIR, ensure_str(certdir)) if certdir or starttls: try: # Note this sets LDAP.OPT not SELF. Because once self has opened # it can NOT change opts on reused (ie restart) - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) + self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) self.log.debug("Using certificate policy %s" % reqcert) self.log.debug("ldap.OPT_X_TLS_REQUIRE_CERT = %s" % reqcert) except ldap.LDAPError as e: self.log.fatal('TLS negotiation failed: %s' % str(e)) raise e - ## NOW INIT THIS. This MUST be after all the ldap.OPT set above, - # so that we inherit the settings correctly!!!! - 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, trace_level=TRACE_LEVEL) - else: - super(DirSrv, self).__init__(uri, trace_level=TRACE_LEVEL) + # Tell python ldap to make a new TLS context with this information. + self.set_option(ldap.OPT_X_TLS_NEWCTX, 0) if starttls and not uri.startswith('ldaps'): self.start_tls_s() @@ -1664,6 +1657,69 @@ class DirSrv(SimpleLDAPObject, object): """Check if autobind/LDAPI is enabled.""" return self.ldapi_enabled == 'on' and self.ldapi_socket is not None and self.ldapi_autobind == 'on' + def enable_tls(self, post_open=True): + """ If it doesn't exist, create a self-signed system CA. Using that, + we create certificates for our instance, as well as configuring the + servers security settings. This is mainly used for test cases, if + you want to enable_Tls on a real instance, there are better ways to + achieve this. + + :param post_open: Open the server connection after restart. + :type post_open: bool + """ + # If it doesn't exist, create a cadb. + ssca_path = os.path.join(self.get_sysconf_dir(), 'dirsrv/ssca/') + ssca = NssSsl(dbpath=ssca_path) + if not ssca._db_exists(): + ssca.reinit() + ssca.create_rsa_ca() + + # Create certificate database. + tlsdb = NssSsl(dbpath=self.get_cert_dir()) + # Remember, DS breaks the db, so force reinit it. + tlsdb.reinit() + csr = tlsdb.create_rsa_key_and_csr() + (ca, crt) = ssca.rsa_ca_sign_csr(csr) + tlsdb.import_rsa_crt(ca, crt) + + self.config.set('nsslapd-security', 'on') + self.use_ldaps_uri() + + if self.ds_paths.perl_enabled: + # We don't setup sslport correctly in perl installer .... + self.config.set('nsslapd-secureport', '%s' % self.sslport) + # If we are old, we don't have template dse, so enable manually. + if ds_is_older('1.4.0'): + if not self.encryption.exists(): + self.encryption.create() + if not self.rsa.exists(): + self.rsa.create() + + # Restart the instance + self.restart(post_open=post_open) + + def use_ldaps_uri(self): + """Change this connection to use ldaps (TLS) on the next .open() call""" + self.ldapuri = 'ldaps://%s:%s' % (self.host, self.sslport) + + def get_ldaps_uri(self): + """Return what our ldaps (TLS) uri would be for this instance + + :returns: The string of the servers ldaps (TLS) uri. + """ + return 'ldaps://%s:%s' % (self.host, self.sslport) + + def use_ldap_uri(self): + """Change this connection to use ldap on the next .open() call""" + self.ldapuri = 'ldap://%s:%s' % (self.host, self.port) + + def get_ldap_uri(self): + """Return what our ldap uri would be for this instance + + :returns: The string of the servers ldap uri. + """ + return 'ldap://%s:%s' % (self.host, self.port) + def getServerId(self): """Return the server identifier.""" return self.serverid @@ -1688,6 +1744,13 @@ class DirSrv(SimpleLDAPObject, object): def get_sysconf_dir(self): return self.ds_paths.sysconf_dir + def get_ssca_dir(self): + """Get the system self signed CA path. + + :returns: The path to the CA nss db + """ + return os.path.join(self.ds_paths.sysconf_dir, 'dirsrv/ssca') + def get_initconfig_dir(self): return self.ds_paths.initconfig_dir diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 9f1d3d0..3644193 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -13,7 +13,7 @@ import logging from functools import partial from lib389._entry import Entry -from lib389._constants import DIRSRV_STATE_ONLINE +from lib389._constants import DIRSRV_STATE_ONLINE, SER_ROOT_DN, SER_ROOT_PW from lib389.utils import ( ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str, ensure_list_int @@ -535,21 +535,6 @@ class DSLdapObject(DSLogging): def set_values(self, values, action=ldap.MOD_REPLACE): pass - # 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, *args, **kwargs): - """Open a new connection and bind with the entry. - You can pass arguments that will be passed to openConnection. - - :param password: An entry password - :type password: str - :returns: Connection with a binding as the entry - """ - - conn = self._instance.openConnection(*args, **kwargs) - conn.simple_bind_s(self.dn, password) - return conn - # Modifies the DN of an entry to the new fqdn provided def rename(self, new_rdn, newsuperior=None): """Renames the object within the tree. @@ -640,7 +625,9 @@ class DSLdapObject(DSLogging): if basedn is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. basedn cannot be None') - if properties.get(self._rdn_attribute, None) is not None: + if rdn is not None: + tdn = ensure_str('%s,%s' % (rdn, basedn)) + elif properties.get(self._rdn_attribute, None) is not None: # Favour the value in the properties dictionary v = properties.get(self._rdn_attribute) rdn = ensure_str(v[0]) @@ -852,17 +839,17 @@ class DSLdapObjects(DSLogging): raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") # Get the rdn out of the properties if it's unset??? - if rdn is None and self._rdn_attribute in properties: - # First see if we can get it from the properties. - trdn = properties.get(self._rdn_attribute) - if type(trdn) == str: - rdn = trdn - elif type(trdn) == list and len(trdn) != 1: - raise ldap.UNWILLING_TO_PERFORM("Cannot determine rdn %s from properties. Too many choices" % (self._rdn_attribute)) - elif type(trdn) == list: - rdn = trdn[0] - else: - raise ldap.UNWILLING_TO_PERFORM("Cannot determine rdn %s from properties, Invalid type" % type(trdn)) + # if rdn is None and self._rdn_attribute in properties: + # # First see if we can get it from the properties. + # trdn = properties.get(self._rdn_attribute) + # if type(trdn) == str: + # rdn = "%s=%s" % (self._rdn_attribute, trdn) + # elif type(trdn) == list and len(trdn) != 1: + # raise ldap.UNWILLING_TO_PERFORM("Cannot determine rdn %s from properties. Too many choices" % (self._rdn_attribute)) + # elif type(trdn) == list: + # rdn = "%s=%s" % (self._rdn_attribute, trdn[0]) + # else: + # raise ldap.UNWILLING_TO_PERFORM("Cannot determine rdn %s from properties, Invalid type" % type(trdn)) return (rdn, properties) diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py index 9953f28..02c2954 100644 --- a/src/lib389/lib389/idm/account.py +++ b/src/lib389/lib389/idm/account.py @@ -7,6 +7,7 @@ # --- END COPYRIGHT BLOCK --- from lib389._mapped_object import DSLdapObject, DSLdapObjects, _gen_or, _gen_filter, _term_gen +from lib389._constants import SER_ROOT_DN, SER_ROOT_PW class Account(DSLdapObject): @@ -36,6 +37,31 @@ class Account(DSLdapObject): self.remove('nsAccountLock', None) + # 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, *args, **kwargs): + """Open a new connection and bind with the entry. + You can pass arguments that will be passed to openConnection. + + :param password: An entry password + :type password: str + :returns: Connection with a binding as the entry + """ + + inst_clone = self._instance.clone({SER_ROOT_DN: self.dn, SER_ROOT_PW: password}) + inst_clone.open(*args, **kwargs) + return inst_clone + + def sasl_bind(self, *args, **kwargs): + """Open a new connection and bind with the entry via SASL. + You can pass arguments that will be pass to clone. + + :return: Connection with a sasl binding to the entry. + """ + inst_clone = self._instance.clone({SER_ROOT_DN: self.dn}) + inst_clone.open(*args, **kwargs) + return inst_clone + class Accounts(DSLdapObjects): """DSLdapObjects that represents Account entry diff --git a/src/lib389/lib389/idm/directorymanager.py b/src/lib389/lib389/idm/directorymanager.py new file mode 100644 index 0000000..4028fc6 --- /dev/null +++ b/src/lib389/lib389/idm/directorymanager.py @@ -0,0 +1,42 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2017, William Brown +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.idm.account import Account +from lib389._constants import DN_DM, PW_DM + + +class DirectoryManager(Account): + """ + The directory manager. This is a convinence class to help with rebinds + to the same server, as well as some other DM related specific tasks. + """ + + def __init__(self, instance, dn=DN_DM): + """The Directory Manager instance. Useful for binding in tests. + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + """ + super(DirectoryManager, self).__init__(instance, dn) + self._rdn_attribute = 'cn' + self._must_attributes = [] + self._create_objectclasses = None + self._protected = True + + def bind(self, password=PW_DM, *args, **kwargs): + """Bind as the Directory Manager. We have a default test password + that can be overriden. + + :param password: The password to bind as for Directory Manager + :type password: str + :returns: A new connection bound as directory manager. + """ + return super(DirectoryManager, self).bind(password, *args, **kwargs) + diff --git a/src/lib389/lib389/idm/services.py b/src/lib389/lib389/idm/services.py index 50d8ed4..90b0ba9 100644 --- a/src/lib389/lib389/idm/services.py +++ b/src/lib389/lib389/idm/services.py @@ -6,14 +6,15 @@ # See LICENSE for details. # --- END COPYRIGHT BLOCK --- -from lib389._mapped_object import DSLdapObject, DSLdapObjects +from lib389._mapped_object import DSLdapObjects +from lib389.idm.account import Account RDN = 'cn' MUST_ATTRIBUTES = [ 'cn', ] -class ServiceAccount(DSLdapObject): +class ServiceAccount(Account): """A single instance of Service entry :param instance: An instance diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py index 176345c..c4d7928 100644 --- a/src/lib389/lib389/idm/user.py +++ b/src/lib389/lib389/idm/user.py @@ -60,6 +60,8 @@ class UserAccount(Account): self._create_objectclasses.append('inetUser') else: self._create_objectclasses.append('nsMemberOf') + if not ds_is_older('1.4.0'): + self._create_objectclasses.append('nsAccount') user_compare_exclude = [ 'nsUniqueId', 'modifyTimestamp', @@ -75,6 +77,21 @@ class UserAccount(Account): return super(UserAccount, self)._validate(rdn, properties, basedn) + def enroll_certificate(self, der_path): + """Enroll a certificate for certmap verification. Because of the userCertificate + attribute, we have to do this on userAccount which has support for it. + + :param der_path: the certificate file in DER format to include. + :type der_path: str + """ + if ds_is_older('1.4.0'): + raise Exception("This version of DS does not support nsAccount") + # Given a cert path, add this to the object as a userCertificate + crt = None + with open(der_path, 'rb') as f: + crt = f.read() + self.add('usercertificate;binary', crt) + # Add a set password function.... # Can't I actually just set, and it will hash? diff --git a/src/lib389/lib389/instance/options.py b/src/lib389/lib389/instance/options.py index f057fe0..91fcec6 100644 --- a/src/lib389/lib389/instance/options.py +++ b/src/lib389/lib389/instance/options.py @@ -187,6 +187,11 @@ class Slapd2Base(Options2): self._helptext['secure_port'] = "The TCP port that Directory Server will listen on for TLS secured LDAP connections." self._example_comment['secure_port'] = True + self._options['self_sign_cert'] = True + self._type['self_sign_cert'] = bool + self._helptext['self_sign_cert'] = "Issue a self signed certificate during the setup process. This is not suitable for production TLS, but aids simplifying setup of TLS (you only need to replace a certificate instead)" + self._example_comment['self_sign_cert'] = True + # In the future, make bin and sbin /usr/[s]bin, but we may need autotools assistance from Ds self._options['bin_dir'] = ds_paths.bin_dir self._type['bin_dir'] = str diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py index 90929dd..fa5d99b 100644 --- a/src/lib389/lib389/instance/setup.py +++ b/src/lib389/lib389/instance/setup.py @@ -22,6 +22,8 @@ from lib389._constants import * from lib389.properties import * from lib389.passwd import password_hash, password_generate +from lib389.nss_ssl import NssSsl + from lib389.configurations import get_config from lib389.instance.options import General2Base, Slapd2Base @@ -268,10 +270,9 @@ class SetupDs(object): assert(slapd['port'] is not None) assert(socket_check_open('::1', slapd['port']) is False) - ## This causes some problems in tests :( - # assert(slapd['secure_port'] is not None) - if slapd['secure_port'] is not None: - assert(socket_check_open('::1', slapd['secure_port']) is False) + # We enable secure port by default. + assert(slapd['secure_port'] is not None) + assert(socket_check_open('::1', slapd['secure_port']) is False) if self.verbose: self.log.info("PASSED: network avaliability checking") @@ -358,7 +359,13 @@ class SetupDs(object): srcfile = os.path.join(slapd['sysconf_dir'], 'dirsrv/config/slapd-collations.conf') dstfile = os.path.join(slapd['config_dir'], 'slapd-collations.conf') shutil.copy2(srcfile, dstfile) - os.chown(slapd['schema_dir'], slapd['user_uid'], slapd['group_gid']) + os.chown(dstfile, slapd['user_uid'], slapd['group_gid']) + + # Copy in the certmap configuration + srcfile = os.path.join(slapd['sysconf_dir'], 'dirsrv/config/certmap.conf') + dstfile = os.path.join(slapd['config_dir'], 'certmap.conf') + shutil.copy2(srcfile, dstfile) + os.chown(dstfile, slapd['user_uid'], slapd['group_gid']) # If we are on the correct platform settings, systemd if general['systemd'] and not self.containerised: @@ -424,9 +431,23 @@ class SetupDs(object): ds_instance.allocate(args) # Does this work? assert(ds_instance.exists()) - # Create the nssdb - assert(ds_instance.nss_ssl.reinit()) - # Do we want to selfsign a CA and cert? + + # Create a certificate database. + tlsdb = NssSsl(dbpath=slapd['cert_dir']) + if not tlsdb._db_exists(): + tlsdb.reinit() + + if slapd['self_sign_cert']: + # If it doesn't exist, create a cadb. + ssca_path = os.path.join(slapd['sysconf_dir'], 'dirsrv/ssca/') + ssca = NssSsl(dbpath=ssca_path) + if not ssca._db_exists(): + ssca.reinit() + ssca.create_rsa_ca() + + csr = tlsdb.create_rsa_key_and_csr() + (ca, crt) = ssca.rsa_ca_sign_csr(csr) + tlsdb.import_rsa_crt(ca, crt) ## LAST CHANCE, FIX PERMISSIONS. # Selinux fixups? @@ -444,6 +465,11 @@ class SetupDs(object): base_config_inst = base_config(ds_instance) base_config_inst.apply_config(install=True) + # Setup TLS with the instance. + ds_instance.config.set('nsslapd-secureport', '%s' % slapd['secure_port']) + if slapd['self_sign_cert']: + ds_instance.config.set('nsslapd-security', 'on') + # Create the backends as listed # Load example data if needed. for backend in backends: @@ -460,7 +486,10 @@ class SetupDs(object): ds_instance.config.set('nsslapd-rootpw', ensure_str(slapd['root_password'])) - # In a container build we need to stop DirSrv at the end if self.containerised: + # In a container build we need to stop DirSrv at the end ds_instance.stop() + else: + # Restart for changes to take effect - this could be removed later + ds_instance.restart(post_open=False) diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py index 90e78f9..a37c11f 100644 --- a/src/lib389/lib389/nss_ssl.py +++ b/src/lib389/lib389/nss_ssl.py @@ -15,6 +15,9 @@ import random import string import re import socket +import time +import shutil +import logging # from nss import nss from subprocess import check_call, check_output from lib389.passwd import password_generate @@ -27,25 +30,28 @@ CERT_NAME = 'Server-Cert' USER_PREFIX = 'user-' PIN_TXT = 'pin.txt' PWD_TXT = 'pwdfile.txt' -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' +CERT_SUFFIX = 'O=testing,L=389ds,ST=Queensland,C=AU' +ISSUER = 'CN=ssca.389ds.example.com,%s' % CERT_SUFFIX +SELF_ISSUER = 'CN={HOSTNAME},%s' % CERT_SUFFIX VALID = 2 +# My logger +log = logging.getLogger(__name__) class NssSsl(object): - def __init__(self, dirsrv, dbpassword=None): + def __init__(self, dirsrv=None, dbpassword=None, dbpath=None): self.dirsrv = dirsrv - self.log = self.dirsrv.log + self._certdb = dbpath + if self._certdb is None: + self._certdb = self.dirsrv.get_cert_dir() + self.log = log + if self.dirsrv is not None: + self.log = self.dirsrv.log if dbpassword is None: self.dbpassword = password_generate() else: self.dbpassword = dbpassword - @property - def _certdb(self): - # return "sql:%s" % self.dirsrv.get_cert_dir() - return self.dirsrv.get_cert_dir() - def _generate_noise(self, fpath): noise = password_generate(256) with open(fpath, 'w') as f: @@ -60,38 +66,44 @@ class NssSsl(object): for f in ('key3.db', 'cert8.db', 'key4.db', 'cert9.db', 'secmod.db', 'pkcs11.txt'): try: # Perhaps we should be backing these up instead ... - os.remove("%s/%s" % (self.dirsrv.get_cert_dir(), f )) + os.remove("%s/%s" % (self._certdb, f )) except: pass + try: + os.makedirs(self._certdb) + except FileExistsError: + pass + # In the future we may add the needed option to avoid writing the pin # files. # Write the pin.txt, and the pwdfile.txt - if not os.path.exists('%s/%s' % (self.dirsrv.get_cert_dir(), PIN_TXT)): - with open('%s/%s' % (self.dirsrv.get_cert_dir(), PIN_TXT), 'w') as f: + if not os.path.exists('%s/%s' % (self._certdb, PIN_TXT)): + with open('%s/%s' % (self._certdb, PIN_TXT), 'w') as f: f.write('Internal (Software) Token:%s' % self.dbpassword) - if not os.path.exists('%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT)): - with open('%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), 'w') as f: + if not os.path.exists('%s/%s' % (self._certdb, PWD_TXT)): + with open('%s/%s' % (self._certdb, PWD_TXT), 'w') as f: f.write('%s' % self.dbpassword) # Init the db. # 48886; This needs to be sql format ... - cmd = ['/usr/bin/certutil', '-N', '-d', self._certdb, '-f', '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT)] - self.dirsrv.log.debug("nss cmd: %s" % cmd) - result = check_output(cmd) - self.dirsrv.log.debug("nss output: %s" % result) + cmd = ['/usr/bin/certutil', '-N', '-d', self._certdb, '-f', '%s/%s' % (self._certdb, PWD_TXT)] + self._generate_noise('%s/noise.txt' % self._certdb) + self.log.debug("nss cmd: %s" % cmd) + result = ensure_str(check_output(cmd)) + self.log.debug("nss output: %s" % result) return True def _db_exists(self): """ Check that a nss db exists at the certpath """ - key3 = os.path.exists("%s/key3.db" % (self.dirsrv.get_cert_dir())) - cert8 = os.path.exists("%s/cert8.db" % (self.dirsrv.get_cert_dir())) - key4 = os.path.exists("%s/key4.db" % (self.dirsrv.get_cert_dir())) - cert9 = os.path.exists("%s/cert9.db" % (self.dirsrv.get_cert_dir())) - secmod = os.path.exists("%s/secmod.db" % (self.dirsrv.get_cert_dir())) - pkcs11 = os.path.exists("%s/pkcs11.txt" % (self.dirsrv.get_cert_dir())) + key3 = os.path.exists("%s/key3.db" % (self._certdb)) + cert8 = os.path.exists("%s/cert8.db" % (self._certdb)) + key4 = os.path.exists("%s/key4.db" % (self._certdb)) + cert9 = os.path.exists("%s/cert9.db" % (self._certdb)) + secmod = os.path.exists("%s/secmod.db" % (self._certdb)) + pkcs11 = os.path.exists("%s/pkcs11.txt" % (self._certdb)) if ((key3 and cert8 and secmod) or (key4 and cert9 and pkcs11)): return True @@ -102,8 +114,10 @@ class NssSsl(object): Create a self signed CA. """ + # Wait a second to avoid an NSS bug with serial ids based on time. + time.sleep(1) # Create noise. - self._generate_noise('%s/noise.txt' % self.dirsrv.get_cert_dir()) + self._generate_noise('%s/noise.txt' % self._certdb) # Now run the command. Can we do this with NSS native? cmd = [ '/usr/bin/certutil', @@ -122,12 +136,12 @@ class NssSsl(object): '-d', self._certdb, '-z', - '%s/noise.txt' % self.dirsrv.get_cert_dir(), + '%s/noise.txt' % self._certdb, '-f', - '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '%s/%s' % (self._certdb, PWD_TXT), ] - result = check_output(cmd) - self.dirsrv.log.debug("nss output: %s" % result) + result = ensure_str(check_output(cmd)) + self.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 = [ @@ -140,10 +154,9 @@ class NssSsl(object): '-a', ] certdetails = check_output(cmd) - with open('%s/ca.crt' % self.dirsrv.get_cert_dir(), 'w') as f: + with open('%s/ca.crt' % self._certdb, 'w') as f: f.write(ensure_str(certdetails)) - if os.path.isfile('/usr/sbin/cacertdir_rehash'): - check_output(['/usr/sbin/cacertdir_rehash', self.dirsrv.get_cert_dir()]) + check_output(['/usr/sbin/cacertdir_rehash', self._certdb]) return True def _rsa_cert_list(self): @@ -153,9 +166,9 @@ class NssSsl(object): '-d', self._certdb, '-f', - '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '%s/%s' % (self._certdb, PWD_TXT), ] - result = check_output(cmd) + result = ensure_str(check_output(cmd)) # We can skip the first few lines. They are junk # IE ['', @@ -180,9 +193,9 @@ class NssSsl(object): '-d', self._certdb, '-f', - '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '%s/%s' % (self._certdb, PWD_TXT), ] - result = check_output(cmd) + result = ensure_str(check_output(cmd)) lines = result.split('\n')[1:-1] key_list = [] @@ -255,18 +268,20 @@ class NssSsl(object): if len(alt_names) == 0: alt_names.append(socket.gethostname()) - if self.dirsrv.host not in alt_names: + if self.dirsrv and self.dirsrv.host not in alt_names: alt_names.append(self.dirsrv.host) + # Wait a second to avoid an NSS bug with serial ids based on time. + time.sleep(1) # Create noise. - self._generate_noise('%s/noise.txt' % self.dirsrv.get_cert_dir()) + self._generate_noise('%s/noise.txt' % self._certdb) cmd = [ '/usr/bin/certutil', '-S', '-n', CERT_NAME, '-s', - SELF_ISSUER.format(HOSTNAME=self.dirsrv.host), + SELF_ISSUER.format(HOSTNAME=alt_names[0]), # We MUST issue with SANs else ldap wont verify the name. '-8', ','.join(alt_names), '-c', @@ -280,21 +295,123 @@ class NssSsl(object): '-d', self._certdb, '-z', - '%s/noise.txt' % self.dirsrv.get_cert_dir(), + '%s/noise.txt' % self._certdb, '-f', - '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '%s/%s' % (self._certdb, PWD_TXT), ] - result = check_output(cmd) - self.dirsrv.log.debug("nss output: %s" % result) + result = ensure_str(check_output(cmd)) + self.log.debug("nss output: %s" % result) return True + def create_rsa_key_and_csr(self, alt_names=[]): + """Create a new RSA key and the certificate signing request. This + request can be submitted to a CA for signing. The returned certifcate + can be added with import_rsa_crt. + """ + csr_path = os.path.join(self._certdb, '%s.csr' % CERT_NAME) + + if len(alt_names) == 0: + alt_names.append(socket.gethostname()) + if self.dirsrv and self.dirsrv.host not in alt_names: + alt_names.append(self.dirsrv.host) + + # Wait a second to avoid an NSS bug with serial ids based on time. + time.sleep(1) + # Create noise. + self._generate_noise('%s/noise.txt' % self._certdb) + + check_call([ + '/usr/bin/certutil', + '-R', + '-s', + SELF_ISSUER.format(HOSTNAME=alt_names[0]), + # We MUST issue with SANs else ldap wont verify the name. + '-8', ','.join(alt_names), + '-g', + '%s' % KEYBITS, + '-v', + '%s' % VALID, + '-d', + self._certdb, + '-z', + '%s/noise.txt' % self._certdb, + '-f', + '%s/%s' % (self._certdb, PWD_TXT), + '-a', + '-o', csr_path, + ]) + return csr_path + + def rsa_ca_sign_csr(self, csr_path): + """ Given a CSR, sign it with our CA certificate (if present). This + emits a signed certificate which can be imported with import_rsa_crt. + """ + crt_path = 'crt'.join(csr_path.rsplit('csr', 1)) + ca_path = '%s/ca.crt' % self._certdb + + check_call([ + '/usr/bin/certutil', + '-C', + '-d', + self._certdb, + '-f', + '%s/%s' % (self._certdb, PWD_TXT), + '-a', + '-i', csr_path, + '-o', crt_path, + '-c', CA_NAME, + ]) + + return (ca_path, crt_path) + + def import_rsa_crt(self, ca, crt): + """Given a signed certificate from a ca, import the CA and certificate + to our database. + """ + shutil.copyfile(ca, '%s/ca.crt' % self._certdb) + check_output(['/usr/sbin/cacertdir_rehash', self._certdb]) + check_call([ + '/usr/bin/certutil', + '-A', + '-n', CA_NAME, + '-t', "CT,,", + '-a', + '-i', '%s/ca.crt' % self._certdb, + '-d', self._certdb, + '-f', + '%s/%s' % (self._certdb, PWD_TXT), + ]) + check_call([ + '/usr/bin/certutil', + '-A', + '-n', CERT_NAME, + '-t', ",,", + '-a', + '-i', crt, + '-d', self._certdb, + '-f', + '%s/%s' % (self._certdb, PWD_TXT), + ]) + check_call([ + '/usr/bin/certutil', + '-V', + '-d', self._certdb, + '-n', CERT_NAME, + '-u', 'V' + ]) + def create_rsa_user(self, name): """ Create a key and cert for a user to authenticate to the directory. Name is the uid of the account, and will become the CN of the cert. """ + if self._rsa_user_exists(name): + return True + + # Wait a second to avoid an NSS bug with serial ids based on time. + time.sleep(1) cmd = [ '/usr/bin/certutil', '-S', @@ -319,20 +436,20 @@ class NssSsl(object): '-d', self._certdb, '-z', - '%s/noise.txt' % self.dirsrv.get_cert_dir(), + '%s/noise.txt' % self._certdb, '-f', - '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '%s/%s' % (self._certdb, PWD_TXT), ] - result = check_output(cmd) - self.dirsrv.log.debug("nss output: %s" % result) + result = ensure_str(check_output(cmd)) + self.log.debug("nss output: %s" % result) # Now extract this into PEM files that we can use. # pk12util -o user-william.p12 -d . -k pwdfile.txt -n user-william -W '' check_call([ 'pk12util', '-d', self._certdb, - '-o', '%s/%s%s.p12' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name), - '-k', '%s/%s' % (self.dirsrv.get_cert_dir(), PWD_TXT), + '-o', '%s/%s%s.p12' % (self._certdb, USER_PREFIX, name), + '-k', '%s/%s' % (self._certdb, PWD_TXT), '-n', '%s%s' % (USER_PREFIX, name), '-W', '""' ]) @@ -341,9 +458,9 @@ class NssSsl(object): check_call([ 'openssl', 'pkcs12', - '-in', '%s/%s%s.p12' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name), + '-in', '%s/%s%s.p12' % (self._certdb, USER_PREFIX, name), '-passin', 'pass:""', - '-out', '%s/%s%s.key' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name), + '-out', '%s/%s%s.key' % (self._certdb, USER_PREFIX, name), '-nocerts', '-nodes' ]) @@ -351,21 +468,32 @@ class NssSsl(object): check_call([ 'openssl', 'pkcs12', - '-in', '%s/%s%s.p12' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name), + '-in', '%s/%s%s.p12' % (self._certdb, USER_PREFIX, name), '-passin', 'pass:""', - '-out', '%s/%s%s.crt' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name), + '-out', '%s/%s%s.crt' % (self._certdb, USER_PREFIX, name), '-nokeys', '-clcerts', '-nodes' ]) + # Convert the cert for userCertificate attr + check_call([ + 'openssl', + 'x509', + '-inform', 'PEM', + '-outform', 'DER', + '-in', '%s/%s%s.crt' % (self._certdb, USER_PREFIX, name), + '-out', '%s/%s%s.der' % (self._certdb, USER_PREFIX, name), + ]) + return True def get_rsa_user(self, name): """ Return a dict of information for ca, key and cert paths for the user id """ - ca_path = '%s/ca.crt' % self.dirsrv.get_cert_dir() - key_path = '%s/%s%s.key' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name) - crt_path = '%s/%s%s.crt' % (self.dirsrv.get_cert_dir(), USER_PREFIX, name) - return {'ca': ca_path, 'key': key_path, 'crt': crt_path} + ca_path = '%s/ca.crt' % self._certdb + key_path = '%s/%s%s.key' % (self._certdb, USER_PREFIX, name) + crt_path = '%s/%s%s.crt' % (self._certdb, USER_PREFIX, name) + crt_der_path = '%s/%s%s.der' % (self._certdb, USER_PREFIX, name) + return {'ca': ca_path, 'key': key_path, 'crt': crt_path, 'crt_der_path': crt_der_path} diff --git a/src/lib389/lib389/tests/nss_ssl_test.py b/src/lib389/lib389/tests/nss_ssl_test.py index e1867b6..4741da5 100644 --- a/src/lib389/lib389/tests/nss_ssl_test.py +++ b/src/lib389/lib389/tests/nss_ssl_test.py @@ -14,6 +14,8 @@ import logging from lib389.topologies import topology_st as topo +from lib389.nss_ssl import NssSsl + DEBUGGING = os.getenv('DEBUGGING', False) if DEBUGGING: @@ -23,52 +25,67 @@ else: log = logging.getLogger(__name__) -def test_nss(topo): +def test_external_ca(): + """ Test the behaviour of our system ca database. + + :id: 321c85c1-9cf3-413f-9e26-99a8b327509c + + :steps: + 1. Create the system CA. + 2. Create an nss db + 3. Submit a CSR to the system ca + 4. Import the crt to the db + :expectedresults: + 1. It works. + 2. It works. + 3. It works. + 4. It works. + """ + # If it doesn't exist, create a cadb. + ssca = NssSsl(dbpath='/tmp/lib389-ssca') + ssca.reinit() + ssca.create_rsa_ca() + + # Create certificate database. + tlsdb = NssSsl(dbpath='/tmp/lib389-tlsdb') + tlsdb.reinit() + + csr = tlsdb.create_rsa_key_and_csr() + (ca, crt) = ssca.rsa_ca_sign_csr(csr) + tlsdb.import_rsa_crt(ca, crt) + +def test_nss_ssca_users(topo): """ - Build a nss db, create a ca, and check that it is correct. + Validate that we can submit user certs to the ds ca for signing. + + :id: a47e47ed-2056-440b-8797-d13fa89098f6 + :steps: + 1. Find the ssca path. + 2. Assert it exists + 3. Create user certificates from the ssca + :expectedresults: + 1. It works. + 2. It works. + 3. It works. """ + ssca = NssSsl(dbpath=topo.standalone.get_ssca_dir()) - standalone = topo.standalone - - # This is a trick. The nss db that ships with DS is broken fundamentally. - # THIS ASSUMES old nss format. SQLite will bite us! - for f in ('key3.db', 'cert8.db', 'key4.db', 'cert9.db', 'secmod.db', 'pkcs11.txt'): - try: - os.remove("%s/%s" % (standalone.confdir, f)) - except: - pass - - - # Check if the db exists. Should be false. - assert(standalone.nss_ssl._db_exists() is False) - # Create it. Should work. - assert(standalone.nss_ssl.reinit() is True) - # Check if the db exists. Should be true - assert(standalone.nss_ssl._db_exists() is True) - - # Check if ca exists. Should be false. - assert(standalone.nss_ssl._rsa_ca_exists() is False) - # Create it. Should work. - assert(standalone.nss_ssl.create_rsa_ca() is True) - # Check if ca exists. Should be true - assert(standalone.nss_ssl._rsa_ca_exists() is True) - - # Check if we have a server cert / key. Should be false. - assert(standalone.nss_ssl._rsa_key_and_cert_exists() is False) - # Create it. Should work. - assert(standalone.nss_ssl.create_rsa_key_and_cert() is True) - # Check if server cert and key exist. Should be true. - assert(standalone.nss_ssl._rsa_key_and_cert_exists() is True) + if not ssca._rsa_ca_exists(): + ssca.reinit() + ssca.create_rsa_ca() + + # It better exist now! + assert(ssca._rsa_ca_exists() is True) # Check making users certs. They should never conflict for user in ('william', 'noriko', 'mark'): - assert(standalone.nss_ssl._rsa_user_exists(user) is False) # Create the user cert - assert(standalone.nss_ssl.create_rsa_user(user) is True) + assert(ssca.create_rsa_user(user) is True) # Assert it exists now - assert(standalone.nss_ssl._rsa_user_exists(user) is True) + assert(ssca._rsa_user_exists(user) is True) if __name__ == "__main__": CURRENT_FILE = os.path.realpath(__file__) pytest.main("-s -vv %s" % CURRENT_FILE) + diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py index 69c71ee..068a24a 100644 --- a/src/lib389/lib389/topologies.py +++ b/src/lib389/lib389/topologies.py @@ -13,10 +13,12 @@ import time 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, - ReplicaRole, DEFAULT_SUFFIX, REPLICA_ID) + SER_SECURE_PORT, ReplicaRole, DEFAULT_SUFFIX, REPLICA_ID, + SER_LDAP_URL) DEBUGGING = os.getenv('DEBUGGING', default=False) if DEBUGGING: @@ -61,14 +63,19 @@ def create_topology(topo_dict): # the instance creation here. 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] args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX + args_copied = args_instance.copy() instance.allocate(args_copied) instance_exists = instance.exists() if instance_exists: instance.delete() instance.create() + # We set a URL here to force ldap:// only. Once we turn on TLS + # we'll flick this to ldaps. + instance.use_ldap_uri() instance.open() if role == ReplicaRole.STANDALONE: ins[instance.serverid] = instance @@ -137,8 +144,10 @@ class TopologyMain(object): if standalones: if isinstance(standalones, dict): self.ins = standalones + self.all_insts.update(standalones) else: self.standalone = standalones + self.all_insts['standalone1'] = standalones if masters: self.ms = masters self.all_insts.update(self.ms) @@ -149,6 +158,9 @@ class TopologyMain(object): self.hs = hubs self.all_insts.update(self.hs) + def __iter__(self): + return self.all_insts.values().__iter__() + def pause_all_replicas(self): """Pause all agreements in the class instance""" @@ -328,6 +340,7 @@ def topology_m1h1c1(request): instance = DirSrv(verbose=False) 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] args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_copied = args_instance.copy() -- 1.8.3.1