diff --git a/SOURCES/0050-Fix-elements-not-being-removed-in-otpd_queue_pop_msg.patch b/SOURCES/0050-Fix-elements-not-being-removed-in-otpd_queue_pop_msg.patch new file mode 100644 index 0000000..d0e2f25 --- /dev/null +++ b/SOURCES/0050-Fix-elements-not-being-removed-in-otpd_queue_pop_msg.patch @@ -0,0 +1,31 @@ +From 006986b0f004b4cdbc3620b417d5a7f62931ecb6 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Wed, 30 May 2018 14:54:54 -0400 +Subject: [PATCH] Fix elements not being removed in otpd_queue_pop_msgid() + +If the element being removed were not the queue head, +otpd_queue_pop_msgid() would not actually remove the element, leading +to potential double frees and request replays. + +Reviewed-By: Alexander Bokovoy +Reviewed-By: Stanislav Laznicka +--- + daemons/ipa-otpd/queue.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/daemons/ipa-otpd/queue.c b/daemons/ipa-otpd/queue.c +index 730bbc40b864b9dc65a69049c0a0e516e3daac0e..9e29fb238d5c7a7395bcf3860ce7445c27ca98ac 100644 +--- a/daemons/ipa-otpd/queue.c ++++ b/daemons/ipa-otpd/queue.c +@@ -155,7 +155,7 @@ struct otpd_queue_item *otpd_queue_pop_msgid(struct otpd_queue *q, int msgid) + + for (item = q->head, prev = &q->head; + item != NULL; +- item = item->next, prev = &item->next) { ++ prev = &item->next, item = item->next) { + if (item->msgid == msgid) { + *prev = item->next; + if (q->head == NULL) +-- +2.17.1 + diff --git a/SOURCES/0051-Tune-DS-replication-settings.patch b/SOURCES/0051-Tune-DS-replication-settings.patch new file mode 100644 index 0000000..6776b5b --- /dev/null +++ b/SOURCES/0051-Tune-DS-replication-settings.patch @@ -0,0 +1,434 @@ +From f86e77a8e6fd9e57472e6d1b63246254143f706d Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 3 Jul 2018 19:40:05 +0200 +Subject: [PATCH] Tune DS replication settings + +Tune 389-DS replication settings to improve performance and avoid +timeouts. During installation of a replica, the value of +nsDS5ReplicaBindDnGroupCheckInterval is reduced to 2 seconds. At the end +of the installation, the value is increased sensible production +settings. This avoids long delays during replication. + +See: https://pagure.io/freeipa/issue/7617 +Signed-off-by: Christian Heimes +Reviewed-By: Tibor Dudlak +--- + ipaserver/install/cainstance.py | 14 ++- + ipaserver/install/dsinstance.py | 48 +++++--- + ipaserver/install/replication.py | 92 +++++++++++---- + ipaserver/install/server/replicainstall.py | 2 + + ipaserver/install/server/upgrade.py | 12 +- + ipatests/test_integration/test_external_ca.py | 109 ++++++------------ + 6 files changed, 157 insertions(+), 120 deletions(-) + +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index eefc30b6e01dcf744703b8607cbe169fbec5d859..e72e706ffd2d9163a283f6bb09815e5aa60c889c 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -388,8 +388,13 @@ class CAInstance(DogtagInstance): + self.step("set up CRL publishing", self.__enable_crl_publish) + self.step("enable PKIX certificate path discovery and validation", self.enable_pkix) + if promote: +- self.step("destroying installation admin user", self.teardown_admin) +- self.step("starting certificate server instance", self.start_instance) ++ self.step("destroying installation admin user", ++ self.teardown_admin) ++ self.step("starting certificate server instance", ++ self.start_instance) ++ if promote: ++ self.step("Finalize replication settings", ++ self.finalize_replica_config) + # Step 1 of external is getting a CSR so we don't need to do these + # steps until we get a cert back from the external CA. + if self.external != 1: +@@ -1196,13 +1201,16 @@ class CAInstance(DogtagInstance): + api.Backend.ldap2.add_entry(entry) + + def __setup_replication(self): +- + repl = replication.CAReplicationManager(self.realm, self.fqdn) + repl.setup_cs_replication(self.master_host) + + # Activate Topology for o=ipaca segments + self.__update_topology() + ++ def finalize_replica_config(self): ++ repl = replication.CAReplicationManager(self.realm, self.fqdn) ++ repl.finalize_replica_config(self.master_host) ++ + def __enable_instance(self): + basedn = ipautil.realm_to_suffix(self.realm) + if not self.clone: +diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py +index 923f483340a26a614001701ce6c235dd73501501..7adaabd3c1280709150329003130f70233de37f4 100644 +--- a/ipaserver/install/dsinstance.py ++++ b/ipaserver/install/dsinstance.py +@@ -417,6 +417,20 @@ class DsInstance(service.Service): + + self.start_creation(runtime=30) + ++ def _get_replication_manager(self): ++ # Always connect to self over ldapi ++ ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) ++ conn = ipaldap.LDAPClient(ldap_uri) ++ conn.external_bind() ++ repl = replication.ReplicationManager( ++ self.realm, self.fqdn, self.dm_password, conn=conn ++ ) ++ if self.dm_password is not None and not self.promote: ++ bind_dn = DN(('cn', 'Directory Manager')) ++ bind_pw = self.dm_password ++ else: ++ bind_dn = bind_pw = None ++ return repl, bind_dn, bind_pw + + def __setup_replica(self): + """ +@@ -433,26 +447,24 @@ class DsInstance(service.Service): + self.realm, + self.dm_password) + +- # Always connect to self over ldapi +- ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) +- conn = ipaldap.LDAPClient(ldap_uri) +- conn.external_bind() +- repl = replication.ReplicationManager(self.realm, +- self.fqdn, +- self.dm_password, conn=conn) +- +- if self.dm_password is not None and not self.promote: +- bind_dn = DN(('cn', 'Directory Manager')) +- bind_pw = self.dm_password +- else: +- bind_dn = bind_pw = None +- +- repl.setup_promote_replication(self.master_fqdn, +- r_binddn=bind_dn, +- r_bindpw=bind_pw, +- cacert=self.ca_file) ++ repl, bind_dn, bind_pw = self._get_replication_manager() ++ repl.setup_promote_replication( ++ self.master_fqdn, ++ r_binddn=bind_dn, ++ r_bindpw=bind_pw, ++ cacert=self.ca_file ++ ) + self.run_init_memberof = repl.needs_memberof_fixup() + ++ def finalize_replica_config(self): ++ repl, bind_dn, bind_pw = self._get_replication_manager() ++ repl.finalize_replica_config( ++ self.master_fqdn, ++ r_binddn=bind_dn, ++ r_bindpw=bind_pw, ++ cacert=self.ca_file ++ ) ++ + def __configure_sasl_mappings(self): + # we need to remove any existing SASL mappings in the directory as otherwise they + # they may conflict. +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index e0055b792ce5b51fd411a7ea1f4316bb017984ba..3a76b70038bf6f739fe63aeac0233ccbfda2f016 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -69,6 +69,20 @@ STRIP_ATTRS = ('modifiersName', + 'internalModifiersName', + 'internalModifyTimestamp') + ++# settings for cn=replica,cn=$DB,cn=mapping tree,cn=config ++# during replica installation ++REPLICA_CREATION_SETTINGS = { ++ "nsds5ReplicaReleaseTimeout": ["20"], ++ "nsds5ReplicaBackoffMax": ["3"], ++ "nsDS5ReplicaBindDnGroupCheckInterval": ["2"] ++} ++# after replica installation ++REPLICA_FINAL_SETTINGS = { ++ "nsds5ReplicaReleaseTimeout": ["60"], ++ "nsds5ReplicaBackoffMax": ["300"], # default ++ "nsDS5ReplicaBindDnGroupCheckInterval": ["60"] ++} ++ + + def replica_conn_check(master_host, host_name, realm, check_ca, + dogtag_master_ds_port, admin_password=None, +@@ -192,9 +206,13 @@ def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True): + + + class ReplicationManager(object): +- """Manage replication agreements between DS servers, and sync +- agreements with Windows servers""" +- def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, conn=None): ++ """Manage replication agreements ++ ++ between DS servers, and sync agreements with Windows servers ++ """ ++ ++ def __init__(self, realm, hostname, dirman_passwd=None, port=PORT, ++ starttls=False, conn=None): + self.hostname = hostname + self.port = port + self.dirman_passwd = dirman_passwd +@@ -471,22 +489,16 @@ class ReplicationManager(object): + except errors.NotFound: + pass + else: +- managers = {DN(m) for m in entry.get('nsDS5ReplicaBindDN', [])} +- +- mods = [] +- if replica_binddn not in managers: ++ binddns = entry.setdefault('nsDS5ReplicaBindDN', []) ++ if replica_binddn not in {DN(m) for m in binddns}: + # Add the new replication manager +- mods.append( +- (ldap.MOD_ADD, 'nsDS5ReplicaBindDN', replica_binddn) +- ) +- if 'nsds5replicareleasetimeout' not in entry: +- # See https://pagure.io/freeipa/issue/7488 +- mods.append( +- (ldap.MOD_ADD, 'nsds5replicareleasetimeout', ['60']) +- ) +- +- if mods: +- conn.modify_s(dn, mods) ++ binddns.append(replica_binddn) ++ for key, value in REPLICA_CREATION_SETTINGS.items(): ++ entry[key] = value ++ try: ++ conn.update_entry(entry) ++ except errors.EmptyModlist: ++ pass + + self.set_replica_binddngroup(conn, entry) + +@@ -505,9 +517,8 @@ class ReplicationManager(object): + nsds5flags=["1"], + nsds5replicabinddn=[replica_binddn], + nsds5replicabinddngroup=[self.repl_man_group_dn], +- nsds5replicabinddngroupcheckinterval=["60"], +- nsds5replicareleasetimeout=["60"], + nsds5replicalegacyconsumer=["off"], ++ **REPLICA_CREATION_SETTINGS + ) + conn.add_entry(entry) + +@@ -533,6 +544,47 @@ class ReplicationManager(object): + except errors.DuplicateEntry: + return + ++ def _finalize_replica_settings(self, conn): ++ """Change replica settings to final values ++ ++ During replica installation, some settings are configured for faster ++ replication. ++ """ ++ dn = self.replica_dn() ++ entry = conn.get_entry(dn) ++ for key, value in REPLICA_FINAL_SETTINGS.items(): ++ entry[key] = value ++ try: ++ conn.update_entry(entry) ++ except errors.EmptyModlist: ++ pass ++ ++ def finalize_replica_config(self, r_hostname, r_binddn=None, ++ r_bindpw=None, cacert=paths.IPA_CA_CRT): ++ """Apply final cn=replica settings ++ ++ replica_config() sets several attribute to fast cache invalidation ++ and fast reconnects to optimize replicat installation. For ++ production, longer timeouts and less aggressive cache invalidation ++ is sufficient. finalize_replica_config() sets the values on new ++ replica and the master. ++ ++ When installing multiple replicas in parallel, one replica may ++ finalize the values while another is still installing. ++ ++ See https://pagure.io/freeipa/issue/7617 ++ """ ++ self._finalize_replica_settings(self.conn) ++ ++ ldap_uri = ipaldap.get_ldap_uri(r_hostname) ++ r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert) ++ if r_bindpw: ++ r_conn.simple_bind(r_binddn, r_bindpw) ++ else: ++ r_conn.gssapi_bind() ++ self._finalize_replica_settings(r_conn) ++ r_conn.close() ++ + def setup_chaining_backend(self, conn): + chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config')) + benamebase = "chaindb" +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index 2ecc0abe0c5918cf5aefccc1bd6f09e70503baab..27e5e9cde7788b8c9b78b27ed47f5748f34c90ff 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -1504,6 +1504,8 @@ def install(installer): + # Apply any LDAP updates. Needs to be done after the replica is synced-up + service.print_msg("Applying LDAP updates") + ds.apply_updates() ++ service.print_msg("Finalize replication settings") ++ ds.finalize_replica_config() + + if kra_enabled: + kra.install(api, config, options, custodia=custodia) +diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py +index bf603acb5f931a4194320795874859f5bdc94647..793092be86da687fd21cf2c0ef3608f32fcf9f16 100644 +--- a/ipaserver/install/server/upgrade.py ++++ b/ipaserver/install/server/upgrade.py +@@ -50,6 +50,7 @@ from ipaserver.install import dnskeysyncinstance + from ipaserver.install import dogtaginstance + from ipaserver.install import krbinstance + from ipaserver.install import adtrustinstance ++from ipaserver.install import replication + from ipaserver.install.upgradeinstance import IPAUpgrade + from ipaserver.install.ldapupdate import BadSyntax + +@@ -1575,11 +1576,14 @@ def update_replica_config(db_suffix): + except ipalib.errors.NotFound: + return # entry does not exist until a replica is installed + +- if 'nsds5replicareleasetimeout' not in entry: +- # See https://pagure.io/freeipa/issue/7488 +- root_logger.info("Adding nsds5replicaReleaseTimeout=60 to %s", dn) +- entry['nsds5replicareleasetimeout'] = '60' ++ for key, value in replication.REPLICA_FINAL_SETTINGS.items(): ++ entry[key] = value ++ try: + api.Backend.ldap2.update_entry(entry) ++ except ipalib.errors.EmptyModlist: ++ pass ++ else: ++ root_logger.info("Updated entry %s", dn) + + + def upgrade_configuration(): +diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py +index 7fc89cc949ca6a7ccddde17aca434cf55f2918ed..b22fa05206204e3dc382dc2b02c23a73e5c1b80b 100644 +--- a/ipatests/test_integration/test_external_ca.py ++++ b/ipatests/test_integration/test_external_ca.py +@@ -31,86 +31,45 @@ class TestExternalCA(IntegrationTest): + """ + Test of FreeIPA server installation with exernal CA + """ +- def test_external_ca(self): +- # Step 1 of ipa-server-install +- self.master.run_command([ +- 'ipa-server-install', '-U', +- '-a', self.master.config.admin_password, +- '-p', self.master.config.dirman_password, +- '--setup-dns', '--no-forwarders', +- '-n', self.master.domain.name, +- '-r', self.master.domain.realm, +- '--domain-level=%i' % self.master.config.domain_level, +- '--external-ca' +- ]) +- +- nss_db = os.path.join(self.master.config.test_dir, 'testdb') +- external_cert_file = os.path.join(nss_db, 'ipa.crt') +- external_ca_file = os.path.join(nss_db, 'ca.crt') +- noisefile = os.path.join(self.master.config.test_dir, 'noise.txt') +- pwdfile = os.path.join(self.master.config.test_dir, 'pwdfile.txt') +- +- # Create noise and password files for NSS database +- self.master.run_command('date | sha256sum > %s' % noisefile) +- self.master.run_command('echo %s > %s' % +- (self.master.config.admin_password, pwdfile)) +- +- # Create NSS database +- self.master.run_command(['mkdir', nss_db]) +- self.master.run_command([ +- 'certutil', '-N', +- '-d', nss_db, +- '-f', pwdfile +- ]) +- +- # Create external CA +- self.master.run_command([ +- 'certutil', '-S', +- '-d', nss_db, +- '-f', pwdfile, +- '-n', 'external', +- '-s', 'CN=External CA, O=%s' % self.master.domain.name, +- '-x', +- '-t', 'CTu,CTu,CTu', +- '-g', '2048', +- '-m', '0', +- '-v', '60', +- '-z', noisefile, +- '-2', '-1', '-5', '--extSKID' +- ], stdin_text='5\n9\nn\ny\n10\ny\n{}\nn\n5\n6\n7\n9\nn\n' +- ''.format(EXTERNAL_CA_KEY_ID)) ++ num_replicas = 1 + +- # Sign IPA cert request using the external CA +- self.master.run_command([ +- 'certutil', '-C', +- '-d', nss_db, +- '-f', pwdfile, +- '-c', 'external', +- '-m', '1', +- '-v', '60', +- '-2', '-1', '-3', '--extSKID', +- '-i', '/root/ipa.csr', +- '-o', external_cert_file, +- '-a' +- ], stdin_text='0\n1\n5\n9\ny\ny\n\ny\ny\n{}\n-1\n\nn\n{}\nn\n' +- ''.format(EXTERNAL_CA_KEY_ID, IPA_CA_KEY_ID)) ++ @tasks.collect_logs ++ def test_external_ca(self): ++ # Step 1 of ipa-server-install. ++ result = install_server_external_ca_step1(self.master) ++ assert result.returncode == 0 + +- # Export external CA file +- self.master.run_command( +- 'certutil -L -d %s -n "external" -a > %s' % +- (nss_db, external_ca_file) +- ) ++ # Sign CA, transport it to the host and get ipa a root ca paths. ++ root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport( ++ self.master, paths.ROOT_IPA_CSR, ROOT_CA, IPA_CA) + +- # Step 2 of ipa-server-install +- self.master.run_command([ +- 'ipa-server-install', +- '-a', self.master.config.admin_password, +- '-p', self.master.config.dirman_password, +- '--external-cert-file', external_cert_file, +- '--external-cert-file', external_ca_file +- ]) ++ # Step 2 of ipa-server-install. ++ result = install_server_external_ca_step2( ++ self.master, ipa_ca_fname, root_ca_fname) ++ assert result.returncode == 0 + + # Make sure IPA server is working properly + tasks.kinit_admin(self.master) + result = self.master.run_command(['ipa', 'user-show', 'admin']) + assert 'User login: admin' in result.stdout_text ++ ++ # check that we can also install replica ++ tasks.install_replica(self.master, self.replicas[0]) ++ ++ # check that nsds5ReplicaReleaseTimeout option was set ++ result = self.master.run_command([ ++ 'ldapsearch', ++ '-x', ++ '-D', ++ 'cn=directory manager', ++ '-w', self.master.config.dirman_password, ++ '-b', 'cn=mapping tree,cn=config', ++ '(cn=replica)', ++ '-LLL', ++ '-o', ++ 'ldif-wrap=no']) ++ # case insensitive match ++ text = result.stdout_text.lower() ++ # see ipaserver.install.replication.REPLICA_FINAL_SETTINGS ++ assert 'nsds5ReplicaReleaseTimeout: 60'.lower() in text ++ assert 'nsDS5ReplicaBindDnGroupCheckInterval: 60'.lower() in text +-- +2.17.1 + diff --git a/SOURCES/0052-In-IPA-4.4-when-updating-userpassword-with-ldapmodif.patch b/SOURCES/0052-In-IPA-4.4-when-updating-userpassword-with-ldapmodif.patch new file mode 100644 index 0000000..69de466 --- /dev/null +++ b/SOURCES/0052-In-IPA-4.4-when-updating-userpassword-with-ldapmodif.patch @@ -0,0 +1,48 @@ +From 0335e799ce5f73a1a1e9901fb5e9c85aaa718ecd Mon Sep 17 00:00:00 2001 +From: Thierry Bordaz +Date: Tue, 24 Jul 2018 12:16:35 +0200 +Subject: [PATCH] In IPA 4.4 when updating userpassword with ldapmodify does + not update krbPasswordExpiration nor krbLastPwdChange + +When making ipa-pwd-extop TXN aware, some callbacks are call twice. +Particularily + ipapwd_pre_add is called during PRE_ADD and TXN_PRE_ADD + ipapwd_pre_mod is called during PRE_MOD and TXN_PRE_MOD + ipapwd_post_modadd is called during POST_ADD and TXN_POST_ADD + ipapwd_post_modadd is called during POST_MOD and TXN_POST_MOD +It is not the expected behavior and it results on some skipped updates krbPasswordExpiration +and krbLastPwdChange + +https://pagure.io/freeipa/issue/7601 + +Reviewed-By: Florence Blanc-Renaud +--- + daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +index c62eae33418f8368c831a91f611915addf7d423b..209d596255d05bfd03de432b7930e6406cc025dc 100644 +--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c ++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +@@ -1488,8 +1488,6 @@ int ipapwd_pre_init(Slapi_PBlock *pb) + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind); +- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add); +- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); + + return ret; + } +@@ -1513,9 +1511,7 @@ int ipapwd_post_init(Slapi_PBlock *pb) + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); +- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_modadd); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *)ipapwd_post_updatecfg); +- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_modadd); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *)ipapwd_post_updatecfg); + + return ret; +-- +2.17.1 + diff --git a/SOURCES/0053-Tests-add-integration-test-for-password-changes-by-d.patch b/SOURCES/0053-Tests-add-integration-test-for-password-changes-by-d.patch new file mode 100644 index 0000000..4580cb4 --- /dev/null +++ b/SOURCES/0053-Tests-add-integration-test-for-password-changes-by-d.patch @@ -0,0 +1,157 @@ +From 97e0d55745a125a933a8d4f9dddd31a752977948 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Mon, 6 Aug 2018 18:25:16 +0200 +Subject: [PATCH] Tests: add integration test for password changes by dir mgr + +Add a test for issue 7601: +- add a user, perform kinit user to modify the password, read krblastpwdchange +and krbpasswordexpiration. +- perform a ldapmodify on the password as dir mgr +- make sure that krblastpwdchange and krbpasswordexpiration have been modified +- perform the same check with ldappasswd + +Related to: +https://pagure.io/freeipa/issue/7601 + +Reviewed-By: Thierry Bordaz +--- + ipatests/test_integration/test_commands.py | 127 +++++++++++++++++++++ + 1 file changed, 127 insertions(+) + create mode 100644 ipatests/test_integration/test_commands.py + +diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py +new file mode 100644 +index 0000000000000000000000000000000000000000..e277e4a2fe4089392b08719d46b011e6444e8094 +--- /dev/null ++++ b/ipatests/test_integration/test_commands.py +@@ -0,0 +1,127 @@ ++# ++# Copyright (C) 2018 FreeIPA Contributors see COPYING for license ++# ++"""Misc test for 'ipa' CLI regressions ++""" ++from __future__ import print_function ++ ++import re ++from tempfile import NamedTemporaryFile ++import textwrap ++import time ++ ++from ipaplatform.paths import paths ++ ++from ipatests.test_integration.base import IntegrationTest ++from ipatests.pytest_plugins.integration import tasks ++ ++ ++class TestIPACommand(IntegrationTest): ++ """ ++ A lot of commands can be executed against a single IPA installation ++ so provide a generic class to execute one-off commands that need to be ++ tested without having to fire up a full server to run one command. ++ """ ++ topology = 'line' ++ ++ def test_ldapmodify_password_issue7601(self): ++ user = 'ipauser' ++ original_passwd = 'Secret123' ++ new_passwd = 'userPasswd123' ++ new_passwd2 = 'mynewPwd123' ++ master = self.master ++ base_dn = str(master.domain.basedn) # pylint: disable=no-member ++ ++ # Create a user with a password ++ tasks.kinit_admin(master) ++ add_password_stdin_text = "{pwd}\n{pwd}".format(pwd=original_passwd) ++ master.run_command(['ipa', 'user-add', user, ++ '--first', user, ++ '--last', user, ++ '--password'], ++ stdin_text=add_password_stdin_text) ++ # kinit as that user in order to modify the pwd ++ user_kinit_stdin_text = "{old}\n%{new}\n%{new}\n".format( ++ old=original_passwd, ++ new=original_passwd) ++ master.run_command(['kinit', user], stdin_text=user_kinit_stdin_text) ++ # Retrieve krblastpwdchange and krbpasswordexpiration ++ search_cmd = [ ++ 'ldapsearch', '-x', ++ '-D', 'cn=directory manager', ++ '-w', master.config.dirman_password, ++ '-s', 'base', ++ '-b', 'uid={user},cn=users,cn=accounts,{base_dn}'.format( ++ user=user, base_dn=base_dn), ++ '-o', 'ldif-wrap=no', ++ '-LLL', ++ 'krblastpwdchange', ++ 'krbpasswordexpiration'] ++ output = master.run_command(search_cmd).stdout_text.lower() ++ ++ # extract krblastpwdchange and krbpasswordexpiration ++ krbchg_pattern = 'krblastpwdchange: (.+)\n' ++ krbexp_pattern = 'krbpasswordexpiration: (.+)\n' ++ krblastpwdchange = re.findall(krbchg_pattern, output)[0] ++ krbexp = re.findall(krbexp_pattern, output)[0] ++ ++ # sleep 1 sec (krblastpwdchange and krbpasswordexpiration have at most ++ # a 1s precision) ++ time.sleep(1) ++ # perform ldapmodify on userpassword as dir mgr ++ mod = NamedTemporaryFile() ++ ldif_file = mod.name ++ entry_ldif = textwrap.dedent(""" ++ dn: uid={user},cn=users,cn=accounts,{base_dn} ++ changetype: modify ++ replace: userpassword ++ userpassword: {new_passwd} ++ """).format( ++ user=user, ++ base_dn=base_dn, ++ new_passwd=new_passwd) ++ master.put_file_contents(ldif_file, entry_ldif) ++ arg = ['ldapmodify', ++ '-h', master.hostname, ++ '-p', '389', '-D', ++ str(master.config.dirman_dn), # pylint: disable=no-member ++ '-w', master.config.dirman_password, ++ '-f', ldif_file] ++ master.run_command(arg) ++ ++ # Test new password with kinit ++ master.run_command(['kinit', user], stdin_text=new_passwd) ++ # Retrieve krblastpwdchange and krbpasswordexpiration ++ output = master.run_command(search_cmd).stdout_text.lower() ++ # extract krblastpwdchange and krbpasswordexpiration ++ newkrblastpwdchange = re.findall(krbchg_pattern, output)[0] ++ newkrbexp = re.findall(krbexp_pattern, output)[0] ++ ++ # both should have changed ++ assert newkrblastpwdchange != krblastpwdchange ++ assert newkrbexp != krbexp ++ ++ # Now test passwd modif with ldappasswd ++ time.sleep(1) ++ master.run_command([ ++ paths.LDAPPASSWD, ++ '-D', str(master.config.dirman_dn), # pylint: disable=no-member ++ '-w', master.config.dirman_password, ++ '-a', new_passwd, ++ '-s', new_passwd2, ++ '-x', '-ZZ', ++ '-H', 'ldap://{hostname}'.format(hostname=master.hostname), ++ 'uid={user},cn=users,cn=accounts,{base_dn}'.format( ++ user=user, base_dn=base_dn)] ++ ) ++ # Test new password with kinit ++ master.run_command(['kinit', user], stdin_text=new_passwd2) ++ # Retrieve krblastpwdchange and krbpasswordexpiration ++ output = master.run_command(search_cmd).stdout_text.lower() ++ # extract krblastpwdchange and krbpasswordexpiration ++ newkrblastpwdchange2 = re.findall(krbchg_pattern, output)[0] ++ newkrbexp2 = re.findall(krbexp_pattern, output)[0] ++ ++ # both should have changed ++ assert newkrblastpwdchange != newkrblastpwdchange2 ++ assert newkrbexp != newkrbexp2 +-- +2.17.1 + diff --git a/SOURCES/0054-Check-if-replication-agreement-exist-before-enable-d.patch b/SOURCES/0054-Check-if-replication-agreement-exist-before-enable-d.patch new file mode 100644 index 0000000..82ede2d --- /dev/null +++ b/SOURCES/0054-Check-if-replication-agreement-exist-before-enable-d.patch @@ -0,0 +1,74 @@ +From 6db737931822c188833dee8cd086f91c640710fd Mon Sep 17 00:00:00 2001 +From: Felipe Barreto +Date: Mon, 15 Jan 2018 17:12:15 -0200 +Subject: [PATCH] Check if replication agreement exist before enable/disable it + +If the replication agreement does not exist, a custom exception is +raised explaining the problem. + +https://pagure.io/freeipa/issue/7201 + +Reviewed-By: Rob Crittenden +Reviewed-By: Christian Heimes +--- + install/tools/ipa-replica-manage | 7 +++++-- + ipaserver/install/replication.py | 11 +++++++++++ + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage +index c00d8ca3a0fa8228c5aa782a270991f14ee16974..019826f822b23e4e55dedb794ff6c1459a3d93c8 100755 +--- a/install/tools/ipa-replica-manage ++++ b/install/tools/ipa-replica-manage +@@ -1200,8 +1200,11 @@ def re_initialize(realm, thishost, fromhost, dirman_passwd, nolookup=False): + repl = replication.ReplicationManager(realm, fromhost, dirman_passwd) + agreement = repl.get_replication_agreement(thishost) + +- thisrepl.enable_agreement(fromhost) +- repl.enable_agreement(thishost) ++ try: ++ thisrepl.enable_agreement(fromhost) ++ repl.enable_agreement(thishost) ++ except errors.NotFound as e: ++ sys.exit(e) + + repl.force_sync(repl.conn, thishost) + +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index 3a76b70038bf6f739fe63aeac0233ccbfda2f016..d4b41caa45409fa1537ae10f182599307f3e0439 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -31,6 +31,7 @@ import ldap + from ipalib import api, errors + from ipalib.cli import textui + from ipapython.ipa_log_manager import root_logger ++from ipalib.text import _ + from ipapython import ipautil, ipaldap, kerberos + from ipapython.admintool import ScriptError + from ipapython.dn import DN +@@ -1599,6 +1600,11 @@ class ReplicationManager(object): + Disable the replication agreement to hostname. + """ + entry = self.get_replication_agreement(hostname) ++ if not entry: ++ raise errors.NotFound(reason=_( ++ "Replication agreement for %(hostname)s not found") % { ++ 'hostname': hostname ++ }) + entry['nsds5ReplicaEnabled'] = 'off' + + try: +@@ -1613,6 +1619,11 @@ class ReplicationManager(object): + Note: for replication to work it needs to be enabled both ways. + """ + entry = self.get_replication_agreement(hostname) ++ if not entry: ++ raise errors.NotFound(reason=_( ++ "Replication agreement for %(hostname)s not found") % { ++ 'hostname': hostname ++ }) + entry['nsds5ReplicaEnabled'] = 'on' + + try: +-- +2.17.1 + diff --git a/SOURCES/0055-Fix-ipa-restore-create-var-run-ipa-files.patch b/SOURCES/0055-Fix-ipa-restore-create-var-run-ipa-files.patch new file mode 100644 index 0000000..c47f4b9 --- /dev/null +++ b/SOURCES/0055-Fix-ipa-restore-create-var-run-ipa-files.patch @@ -0,0 +1,47 @@ +From be17d3c929027a5ef9f5024aa1bb4112006bb6f6 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Mon, 6 Aug 2018 16:57:48 +0200 +Subject: [PATCH] Fix ipa-restore: create /var/run/ipa files + +In ipa 4.5.4, ipa-restore fails because the file /etc/tmpfiles.d/ipa.conf +is not restored => /var/run/ipa and /var/run/ipa/ccaches directories +are not created. + +The fix creates these directories in ipa-restore and creates ipa.conf. +With this approach, the fix allows to restore a backup done with 4.5.4 +prior to the fix. + +Note: the fix is specific to ipa-4-5, in ipa-4-6 and above version the file +/etc/tmpfiles.d/ipa.conf is created at package install time and not at +ipa server install time. + +Fixes: https://pagure.io/freeipa/issue/7571 +Reviewed-By: Christian Heimes +--- + ipaserver/install/ipa_restore.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py +index a3824df230857b02b47c12645fadee1200afdf66..e069b8ba9b7a01e7602e66d4404063bbc2a1e795 100644 +--- a/ipaserver/install/ipa_restore.py ++++ b/ipaserver/install/ipa_restore.py +@@ -32,6 +32,7 @@ from six.moves.configparser import SafeConfigParser + from ipaclient.install.client import update_ipa_nssdb + from ipalib import api, errors + from ipalib.constants import FQDN ++from ipalib.constants import IPAAPI_USER + from ipapython import version, ipautil + from ipapython.ipautil import run, user_input + from ipapython import admintool +@@ -377,6 +378,8 @@ class Restore(admintool.AdminTool): + if restore_type == 'FULL': + self.remove_old_files() + self.cert_restore_prepare() ++ tasks.create_tmpfiles_dirs(IPAAPI_USER) ++ tasks.configure_tmpfiles() + self.file_restore(options.no_logs) + self.cert_restore() + if 'CA' in self.backup_services: +-- +2.17.1 + diff --git a/SOURCES/0056-Sort-and-shuffle-SRV-record-by-priority-and-weight.patch b/SOURCES/0056-Sort-and-shuffle-SRV-record-by-priority-and-weight.patch new file mode 100644 index 0000000..fcb6006 --- /dev/null +++ b/SOURCES/0056-Sort-and-shuffle-SRV-record-by-priority-and-weight.patch @@ -0,0 +1,427 @@ +From 9e2b0c4b026f281507d1879da8398841270bc20f Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 15 Jun 2018 17:03:29 +0200 +Subject: [PATCH] Sort and shuffle SRV record by priority and weight + +On multiple occasions, SRV query answers were not properly sorted by +priority. Records with same priority weren't randomized and shuffled. +This caused FreeIPA to contact the same remote peer instead of +distributing the load across all available servers. + +Two new helper functions now take care of SRV queries. sort_prio_weight() +sorts SRV and URI records. query_srv() combines SRV lookup with +sort_prio_weight(). + +Fixes: https://pagure.io/freeipa/issue/7475 +Signed-off-by: Christian Heimes +Reviewed-By: Rob Crittenden +--- + ipaclient/install/ipadiscovery.py | 3 +- + ipalib/rpc.py | 21 ++--- + ipalib/util.py | 11 ++- + ipapython/config.py | 8 +- + ipapython/dnsutil.py | 92 +++++++++++++++++++- + ipaserver/dcerpc.py | 4 +- + ipatests/test_ipapython/test_dnsutil.py | 106 ++++++++++++++++++++++++ + 7 files changed, 217 insertions(+), 28 deletions(-) + create mode 100644 ipatests/test_ipapython/test_dnsutil.py + +diff --git a/ipaclient/install/ipadiscovery.py b/ipaclient/install/ipadiscovery.py +index 46e05c971647b4f0fb4e6044ef74aff3e7919632..34142179a9f4957e842769d9d4036d2024130793 100644 +--- a/ipaclient/install/ipadiscovery.py ++++ b/ipaclient/install/ipadiscovery.py +@@ -25,6 +25,7 @@ from ipapython.ipa_log_manager import root_logger + from dns import resolver, rdatatype + from dns.exception import DNSException + from ipalib import errors ++from ipapython.dnsutil import query_srv + from ipapython import ipaldap + from ipaplatform.paths import paths + from ipapython.ipautil import valid_ip, realm_to_suffix +@@ -492,7 +493,7 @@ class IPADiscovery(object): + root_logger.debug("Search DNS for SRV record of %s", qname) + + try: +- answers = resolver.query(qname, rdatatype.SRV) ++ answers = query_srv(qname) + except DNSException as e: + root_logger.debug("DNS record not found: %s", e.__class__.__name__) + answers = [] +diff --git a/ipalib/rpc.py b/ipalib/rpc.py +index e3b8d67d69c084ad1a43390b5f93061826a27e1d..e74807d57955cd36aa8622b4441e08ee89cd313e 100644 +--- a/ipalib/rpc.py ++++ b/ipalib/rpc.py +@@ -43,7 +43,6 @@ import socket + import gzip + + import gssapi +-from dns import resolver, rdatatype + from dns.exception import DNSException + from ssl import SSLError + import six +@@ -59,7 +58,7 @@ from ipapython.ipa_log_manager import root_logger + from ipapython import ipautil + from ipapython import session_storage + from ipapython.cookie import Cookie +-from ipapython.dnsutil import DNSName ++from ipapython.dnsutil import DNSName, query_srv + from ipalib.text import _ + from ipalib.util import create_https_connection + from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT_EXPIRED, \ +@@ -853,7 +852,7 @@ class RPCClient(Connectible): + name = '_ldap._tcp.%s.' % self.env.domain + + try: +- answers = resolver.query(name, rdatatype.SRV) ++ answers = query_srv(name) + except DNSException: + answers = [] + +@@ -861,17 +860,11 @@ class RPCClient(Connectible): + server = str(answer.target).rstrip(".") + servers.append('https://%s%s' % (ipautil.format_netloc(server), path)) + +- servers = list(set(servers)) +- # the list/set conversion won't preserve order so stick in the +- # local config file version here. +- cfg_server = rpc_uri +- if cfg_server in servers: +- # make sure the configured master server is there just once and +- # it is the first one +- servers.remove(cfg_server) +- servers.insert(0, cfg_server) +- else: +- servers.insert(0, cfg_server) ++ # make sure the configured master server is there just once and ++ # it is the first one. ++ if rpc_uri in servers: ++ servers.remove(rpc_uri) ++ servers.insert(0, rpc_uri) + + return servers + +diff --git a/ipalib/util.py b/ipalib/util.py +index 6ee65498b4de674fe4b2ee361541d3bfe648bba0..56db48638e8319859850fba449ed7c23b6e909ab 100644 +--- a/ipalib/util.py ++++ b/ipalib/util.py +@@ -934,14 +934,13 @@ def detect_dns_zone_realm_type(api, domain): + + try: + # The presence of this record is enough, return foreign in such case +- result = resolver.query(ad_specific_record_name, rdatatype.SRV) +- return 'foreign' +- ++ resolver.query(ad_specific_record_name, rdatatype.SRV) + except DNSException: +- pass ++ # If we could not detect type with certainty, return unknown ++ return 'unknown' ++ else: ++ return 'foreign' + +- # If we could not detect type with certainity, return unknown +- return 'unknown' + + def has_managed_topology(api): + domainlevel = api.Command['domainlevel_get']().get('result', DOMAIN_LEVEL_0) +diff --git a/ipapython/config.py b/ipapython/config.py +index 19abfc51ee354d2971be836fa6bad70eea3a6720..44c823b6b946c28a510e5f156061eba0b05aa059 100644 +--- a/ipapython/config.py ++++ b/ipapython/config.py +@@ -24,7 +24,6 @@ from optparse import ( + from copy import copy + import socket + +-from dns import resolver, rdatatype + from dns.exception import DNSException + import dns.name + # pylint: disable=import-error +@@ -33,6 +32,7 @@ from six.moves.urllib.parse import urlsplit + # pylint: enable=import-error + + from ipapython.dn import DN ++from ipapython.dnsutil import query_srv + + try: + # pylint: disable=ipa-forbidden-import +@@ -195,7 +195,7 @@ def __discover_config(discover_server = True): + name = "_ldap._tcp." + domain + + try: +- servers = resolver.query(name, rdatatype.SRV) ++ servers = query_srv(name) + except DNSException: + # try cycling on domain components of FQDN + try: +@@ -210,7 +210,7 @@ def __discover_config(discover_server = True): + return False + name = "_ldap._tcp.%s" % domain + try: +- servers = resolver.query(name, rdatatype.SRV) ++ servers = query_srv(name) + break + except DNSException: + pass +@@ -221,7 +221,7 @@ def __discover_config(discover_server = True): + if not servers: + name = "_ldap._tcp.%s." % config.default_domain + try: +- servers = resolver.query(name, rdatatype.SRV) ++ servers = query_srv(name) + except DNSException: + pass + +diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py +index 011b722dac3e181ac52f7d92d9f44d31c5e2e6bb..25435ba51e6e7c2c6581b60eb077dd133dd29724 100644 +--- a/ipapython/dnsutil.py ++++ b/ipapython/dnsutil.py +@@ -17,10 +17,15 @@ + # along with this program. If not, see . + # + ++import copy ++import operator ++import random ++ + import dns.name + import dns.exception + import dns.resolver +-import copy ++import dns.rdataclass ++import dns.rdatatype + + import six + +@@ -369,3 +374,88 @@ def check_zone_overlap(zone, raise_on_error=True): + if ns: + msg += u" and is handled by server(s): {0}".format(', '.join(ns)) + raise ValueError(msg) ++ ++ ++def _mix_weight(records): ++ """Weighted population sorting for records with same priority ++ """ ++ # trivial case ++ if len(records) <= 1: ++ return records ++ ++ # Optimization for common case: If all weights are the same (e.g. 0), ++ # just shuffle the records, which is about four times faster. ++ if all(rr.weight == records[0].weight for rr in records): ++ random.shuffle(records) ++ return records ++ ++ noweight = 0.01 # give records with 0 weight a small chance ++ result = [] ++ records = set(records) ++ while len(records) > 1: ++ # Compute the sum of the weights of those RRs. Then choose a ++ # uniform random number between 0 and the sum computed (inclusive). ++ urn = random.uniform(0, sum(rr.weight or noweight for rr in records)) ++ # Select the RR whose running sum value is the first in the selected ++ # order which is greater than or equal to the random number selected. ++ acc = 0. ++ for rr in records.copy(): ++ acc += rr.weight or noweight ++ if acc >= urn: ++ records.remove(rr) ++ result.append(rr) ++ if records: ++ result.append(records.pop()) ++ return result ++ ++ ++def sort_prio_weight(records): ++ """RFC 2782 sorting algorithm for SRV and URI records ++ ++ RFC 2782 defines a sorting algorithms for SRV records, that is also used ++ for sorting URI records. Records are sorted by priority and than randomly ++ shuffled according to weight. ++ ++ This implementation also removes duplicate entries. ++ """ ++ # order records by priority ++ records = sorted(records, key=operator.attrgetter("priority")) ++ ++ # remove duplicate entries ++ uniquerecords = [] ++ seen = set() ++ for rr in records: ++ # A SRV record has target and port, URI just has target. ++ target = (rr.target, getattr(rr, "port", None)) ++ if target not in seen: ++ uniquerecords.append(rr) ++ seen.add(target) ++ ++ # weighted randomization of entries with same priority ++ result = [] ++ sameprio = [] ++ for rr in uniquerecords: ++ # add all items with same priority in a bucket ++ if not sameprio or sameprio[0].priority == rr.priority: ++ sameprio.append(rr) ++ else: ++ # got different priority, shuffle bucket ++ result.extend(_mix_weight(sameprio)) ++ # start a new priority list ++ sameprio = [rr] ++ # add last batch of records with same priority ++ if sameprio: ++ result.extend(_mix_weight(sameprio)) ++ return result ++ ++ ++def query_srv(qname, resolver=None, **kwargs): ++ """Query SRV records and sort reply according to RFC 2782 ++ ++ :param qname: query name, _service._proto.domain. ++ :return: list of dns.rdtypes.IN.SRV.SRV instances ++ """ ++ if resolver is None: ++ resolver = dns.resolver ++ answer = resolver.query(qname, rdtype=dns.rdatatype.SRV, **kwargs) ++ return sort_prio_weight(answer) +diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py +index ac1b2a34784df491a3851aa21bbadbec2297241c..4e957b19292f51a7f6e3540dc38590737c7ae5e4 100644 +--- a/ipaserver/dcerpc.py ++++ b/ipaserver/dcerpc.py +@@ -30,6 +30,7 @@ from ipalib import errors + from ipapython import ipautil + from ipapython.ipa_log_manager import root_logger + from ipapython.dn import DN ++from ipapython.dnsutil import query_srv + from ipaserver.install import installutils + from ipaserver.dcerpc_common import (TRUST_BIDIRECTIONAL, + TRUST_JOIN_EXTERNAL, +@@ -55,7 +56,6 @@ import samba + import ldap as _ldap + from ipapython import ipaldap + from ipapython.dnsutil import DNSName +-from dns import resolver, rdatatype + from dns.exception import DNSException + import pysss_nss_idmap + import pysss +@@ -795,7 +795,7 @@ class DomainValidator(object): + gc_name = '_gc._tcp.%s.' % info['dns_domain'] + + try: +- answers = resolver.query(gc_name, rdatatype.SRV) ++ answers = query_srv(gc_name) + except DNSException as e: + answers = [] + +diff --git a/ipatests/test_ipapython/test_dnsutil.py b/ipatests/test_ipapython/test_dnsutil.py +new file mode 100644 +index 0000000000000000000000000000000000000000..36adb077cf38f6d036aa1048b201dee7d08eb310 +--- /dev/null ++++ b/ipatests/test_ipapython/test_dnsutil.py +@@ -0,0 +1,106 @@ ++# ++# Copyright (C) 2018 FreeIPA Contributors. See COPYING for license ++# ++import dns.name ++import dns.rdataclass ++import dns.rdatatype ++from dns.rdtypes.IN.SRV import SRV ++from dns.rdtypes.ANY.URI import URI ++ ++from ipapython import dnsutil ++ ++import pytest ++ ++ ++def mksrv(priority, weight, port, target): ++ return SRV( ++ rdclass=dns.rdataclass.IN, ++ rdtype=dns.rdatatype.SRV, ++ priority=priority, ++ weight=weight, ++ port=port, ++ target=dns.name.from_text(target) ++ ) ++ ++ ++def mkuri(priority, weight, target): ++ return URI( ++ rdclass=dns.rdataclass.IN, ++ rdtype=dns.rdatatype.URI, ++ priority=priority, ++ weight=weight, ++ target=target ++ ) ++ ++ ++class TestSortSRV(object): ++ def test_empty(self): ++ assert dnsutil.sort_prio_weight([]) == [] ++ ++ def test_one(self): ++ h1 = mksrv(1, 0, 443, u"host1") ++ assert dnsutil.sort_prio_weight([h1]) == [h1] ++ ++ h2 = mksrv(10, 5, 443, u"host2") ++ assert dnsutil.sort_prio_weight([h2]) == [h2] ++ ++ def test_prio(self): ++ h1 = mksrv(1, 0, 443, u"host1") ++ h2 = mksrv(2, 0, 443, u"host2") ++ h3 = mksrv(3, 0, 443, u"host3") ++ assert dnsutil.sort_prio_weight([h3, h2, h1]) == [h1, h2, h3] ++ assert dnsutil.sort_prio_weight([h3, h3, h3]) == [h3] ++ assert dnsutil.sort_prio_weight([h2, h2, h1, h1]) == [h1, h2] ++ ++ h380 = mksrv(4, 0, 80, u"host3") ++ assert dnsutil.sort_prio_weight([h1, h3, h380]) == [h1, h3, h380] ++ ++ hs = mksrv(-1, 0, 443, u"special") ++ assert dnsutil.sort_prio_weight([h1, h2, hs]) == [hs, h1, h2] ++ ++ def assert_permutations(self, answers, permutations): ++ seen = set() ++ for _unused in range(1000): ++ result = tuple(dnsutil.sort_prio_weight(answers)) ++ assert result in permutations ++ seen.add(result) ++ if seen == permutations: ++ break ++ else: ++ pytest.fail("sorting didn't exhaust all permutations.") ++ ++ def test_sameprio(self): ++ h1 = mksrv(1, 0, 443, u"host1") ++ h2 = mksrv(1, 0, 443, u"host2") ++ permutations = { ++ (h1, h2), ++ (h2, h1), ++ } ++ self.assert_permutations([h1, h2], permutations) ++ ++ def test_weight(self): ++ h1 = mksrv(1, 0, 443, u"host1") ++ h2_w15 = mksrv(2, 15, 443, u"host2") ++ h3_w10 = mksrv(2, 10, 443, u"host3") ++ ++ permutations = { ++ (h1, h2_w15, h3_w10), ++ (h1, h3_w10, h2_w15), ++ } ++ self.assert_permutations([h1, h2_w15, h3_w10], permutations) ++ ++ def test_large(self): ++ records = tuple( ++ mksrv(1, i, 443, "host{}".format(i)) for i in range(1000) ++ ) ++ assert len(dnsutil.sort_prio_weight(records)) == len(records) ++ ++ ++class TestSortURI(object): ++ def test_prio(self): ++ h1 = mkuri(1, 0, u"https://host1/api") ++ h2 = mkuri(2, 0, u"https://host2/api") ++ h3 = mkuri(3, 0, u"https://host3/api") ++ assert dnsutil.sort_prio_weight([h3, h2, h1]) == [h1, h2, h3] ++ assert dnsutil.sort_prio_weight([h3, h3, h3]) == [h3] ++ assert dnsutil.sort_prio_weight([h2, h2, h1, h1]) == [h1, h2] +-- +2.17.1 + diff --git a/SOURCES/0057-Always-set-ca_host-when-installing-replica.patch b/SOURCES/0057-Always-set-ca_host-when-installing-replica.patch new file mode 100644 index 0000000..bb73ba1 --- /dev/null +++ b/SOURCES/0057-Always-set-ca_host-when-installing-replica.patch @@ -0,0 +1,43 @@ +From f084ab0a6828efd8d76b4495e800f7cc7158d909 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 19 Jun 2018 19:10:27 +0200 +Subject: [PATCH] Always set ca_host when installing replica + +ipa-replica-install only set ca_host in its temporary +/etc/ipa/default.conf, when it wasn't installing a replica with CA. As a +consequence, the replica installer was picking a random CA server from +LDAP. + +Always set the replication peer as ca_host. This will ensure that the +installer uses the same replication peer for CA. In case the replication +peer is not a CA master, the installer will automatically pick another +host later. + +See: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Fraser Tweedale +--- + ipaserver/install/server/replicainstall.py | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index 8668859ba5da427ea6e752ed52af057635061f23..b10f761e3f643f9fa868451192fa4550b24b6b16 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -236,11 +236,9 @@ def create_ipa_conf(fstore, config, ca_enabled, master=None): + gopts.extend([ + ipaconf.setOption('enable_ra', 'True'), + ipaconf.setOption('ra_plugin', 'dogtag'), +- ipaconf.setOption('dogtag_version', '10') ++ ipaconf.setOption('dogtag_version', '10'), ++ ipaconf.setOption('ca_host', config.ca_host_name) + ]) +- +- if not config.setup_ca: +- gopts.append(ipaconf.setOption('ca_host', config.ca_host_name)) + else: + gopts.extend([ + ipaconf.setOption('enable_ra', 'False'), +-- +2.17.1 + diff --git a/SOURCES/0058-Improve-and-fix-timeout-bug-in-wait_for_entry.patch b/SOURCES/0058-Improve-and-fix-timeout-bug-in-wait_for_entry.patch new file mode 100644 index 0000000..78bf47d --- /dev/null +++ b/SOURCES/0058-Improve-and-fix-timeout-bug-in-wait_for_entry.patch @@ -0,0 +1,110 @@ +From f52d626c166ad0f94f9d3752a4d8978dd3d9ccfc Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 22 Jun 2018 09:39:26 +0200 +Subject: [PATCH] Improve and fix timeout bug in wait_for_entry() + +replication.wait_for_entry() now can wait for an attribute value to +appear on a replica. + +Fixed timeout handling caused by bad rounding and comparison. For small +timeouts, the actual time was rounded down. For example for 60 seconds +timeout and fast replica, the query accumulated to about 0.45 seconds +plus 60 seconds sleep. 60.45 is large enough to terminate the loop +"while int(time.time()) < timeout", but not large enough to trigger the +exception in "if int(time.time()) > timeout", because int(60.65) == 60. + +See: https://pagure.io/freeipa/issue/7593 +Fixes: https://pagure.io/freeipa/issue/7595 +Signed-off-by: Christian Heimes +Reviewed-By: Fraser Tweedale +--- + ipaserver/install/replication.py | 57 +++++++++++++++++--------------- + 1 file changed, 31 insertions(+), 26 deletions(-) + +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index d4b41caa45409fa1537ae10f182599307f3e0439..5a491f248236c8d2166484d0db2acccb28ccf178 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -17,7 +17,9 @@ + # along with this program. If not, see . + # + +-from __future__ import print_function ++from __future__ import print_function, absolute_import ++ ++import itertools + + import six + import time +@@ -170,40 +172,43 @@ def wait_for_task(conn, dn): + return exit_code + + +-def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True): +- """Wait for entry and/or attr to show up""" +- +- filter = "(objectclass=*)" ++def wait_for_entry(connection, dn, timeout=7200, attr=None, attrvalue='*', ++ quiet=True): ++ """Wait for entry and/or attr to show up ++ """ ++ log = root_logger.debug if quiet else root_logger.info + attrlist = [] +- if attr: +- filter = "(%s=*)" % attr ++ if attr is not None: ++ filterstr = ipaldap.LDAPClient.make_filter_from_attr(attr, attrvalue) + attrlist.append(attr) +- timeout += int(time.time()) +- +- if not quiet: +- sys.stdout.write("Waiting for %s %s:%s " % (connection, dn, attr)) +- sys.stdout.flush() +- entry = None +- while not entry and int(time.time()) < timeout: ++ else: ++ filterstr = "(objectclass=*)" ++ log("Waiting for replication (%s) %s %s", connection, dn, filterstr) ++ entry = [] ++ deadline = time.time() + timeout ++ for i in itertools.count(start=1): + try: +- [entry] = connection.get_entries( +- dn, ldap.SCOPE_BASE, filter, attrlist) ++ entry = connection.get_entries( ++ dn, ldap.SCOPE_BASE, filterstr, attrlist) + except errors.NotFound: + pass # no entry yet + except Exception as e: # badness + root_logger.error("Error reading entry %s: %s", dn, e) + raise +- if not entry: +- if not quiet: +- sys.stdout.write(".") +- sys.stdout.flush() +- time.sleep(1) + +- if not entry and int(time.time()) > timeout: +- raise errors.NotFound( +- reason="wait_for_entry timeout for %s for %s" % (connection, dn)) +- elif entry and not quiet: +- root_logger.error("The waited for entry is: %s", entry) ++ if entry: ++ log("Entry found %r", entry) ++ return ++ elif time.time() > deadline: ++ raise errors.NotFound( ++ reason="wait_for_entry timeout on {} for {}".format( ++ connection, dn ++ ) ++ ) ++ else: ++ if i % 10 == 0: ++ root_logger.debug("Still waiting for replication of %s", dn) ++ time.sleep(1) + + + class ReplicationManager(object): +-- +2.17.1 + diff --git a/SOURCES/0059-Use-common-replication-wait-timeout-of-5min.patch b/SOURCES/0059-Use-common-replication-wait-timeout-of-5min.patch new file mode 100644 index 0000000..d752feb --- /dev/null +++ b/SOURCES/0059-Use-common-replication-wait-timeout-of-5min.patch @@ -0,0 +1,123 @@ +From 3bf0ee3a4128dc538183ee8e45bc22a3966cfd4b Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 22 Jun 2018 10:00:24 +0200 +Subject: [PATCH] Use common replication wait timeout of 5min + +Instead of multiple timeout values all over the code base, all +replication waits now use a common timeout value from api.env of 5 +minutes. Waiting for HTTP/replica principal takes 90 to 120 seconds, so +5 minutes seem like a sufficient value for slow setups. + +Fixes: https://pagure.io/freeipa/issue/7595 +Signed-off-by: Christian Heimes +Reviewed-By: Fraser Tweedale +--- + ipalib/constants.py | 2 ++ + ipaserver/install/custodiainstance.py | 4 +++- + ipaserver/install/httpinstance.py | 6 +++++- + ipaserver/install/krbinstance.py | 13 ++++++++----- + ipaserver/install/replication.py | 6 ++++-- + 5 files changed, 22 insertions(+), 9 deletions(-) + +diff --git a/ipalib/constants.py b/ipalib/constants.py +index ab466bab7fc17a563a849e2cd9bb89515caff77b..b6a79ce716b3ee0f832390f5bc895c3bb9d37e33 100644 +--- a/ipalib/constants.py ++++ b/ipalib/constants.py +@@ -142,6 +142,8 @@ DEFAULT_CONFIG = ( + ('startup_timeout', 300), + # How long http connection should wait for reply [seconds]. + ('http_timeout', 30), ++ # How long to wait for an entry to appear on a replica ++ ('replication_wait_timeout', 300), + + # Web Application mount points + ('mount_ipa', '/ipa/'), +diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py +index ada8d03a6914e19c186264f68178cce2442945ca..b37032974e4825a3f3043929171533e4d94730e9 100644 +--- a/ipaserver/install/custodiainstance.py ++++ b/ipaserver/install/custodiainstance.py +@@ -4,6 +4,7 @@ from __future__ import print_function, absolute_import + + import enum + ++from ipalib import api + from ipaserver.secrets.kem import IPAKEMKeys, KEMLdap + from ipaserver.secrets.client import CustodiaClient + from ipaplatform.paths import paths +@@ -190,7 +191,8 @@ class CustodiaInstance(SimpleServiceInstance): + cli = self._get_custodia_client() + cli.fetch_key('dm/DMHash') + +- def _wait_keys(self, timeout=300): ++ def _wait_keys(self): ++ timeout = api.env.replication_wait_timeout + deadline = int(time.time()) + timeout + root_logger.info("Waiting up to %s seconds to see our keys " + "appear on host %s", timeout, self.ldap_uri) +diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py +index 434c2549dd868554735f4bcedc4cdceb23eeccdd..e68bfc09b34e087dfb4872b6565b06c6c2188384 100644 +--- a/ipaserver/install/httpinstance.py ++++ b/ipaserver/install/httpinstance.py +@@ -617,4 +617,8 @@ class HTTPInstance(service.Service): + else: + remote_ldap.simple_bind(ipaldap.DIRMAN_DN, + self.dm_password) +- replication.wait_for_entry(remote_ldap, service_dn, timeout=60) ++ replication.wait_for_entry( ++ remote_ldap, ++ service_dn, ++ timeout=api.env.replication_wait_timeout ++ ) +diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py +index 34fe46aa8ef297bf69eb74953c956ad9c3d30def..5971a30fc566f6e96ce0b08632772d33da5602d2 100644 +--- a/ipaserver/install/krbinstance.py ++++ b/ipaserver/install/krbinstance.py +@@ -390,13 +390,16 @@ class KrbInstance(service.Service): + def _wait_for_replica_kdc_entry(self): + master_dn = self.api.Object.server.get_dn(self.fqdn) + kdc_dn = DN(('cn', 'KDC'), master_dn) +- +- ldap_uri = 'ldap://{}'.format(self.master_fqdn) +- ++ ldap_uri = ipaldap.get_ldap_uri(self.master_fqdn) + with ipaldap.LDAPClient( +- ldap_uri, cacert=paths.IPA_CA_CRT) as remote_ldap: ++ ldap_uri, cacert=paths.IPA_CA_CRT, start_tls=True ++ ) as remote_ldap: + remote_ldap.gssapi_bind() +- replication.wait_for_entry(remote_ldap, kdc_dn, timeout=60) ++ replication.wait_for_entry( ++ remote_ldap, ++ kdc_dn, ++ timeout=api.env.replication_wait_timeout ++ ) + + def _call_certmonger(self, certmonger_ca='IPA'): + subject = str(DN(('cn', self.fqdn), self.subject_base)) +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index 5a491f248236c8d2166484d0db2acccb28ccf178..c017764468674830670a817b3d815c5e2d78728e 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -172,7 +172,7 @@ def wait_for_task(conn, dn): + return exit_code + + +-def wait_for_entry(connection, dn, timeout=7200, attr=None, attrvalue='*', ++def wait_for_entry(connection, dn, timeout, attr=None, attrvalue='*', + quiet=True): + """Wait for entry and/or attr to show up + """ +@@ -799,7 +799,9 @@ class ReplicationManager(object): + # that we will have to set the memberof fixup task + self.need_memberof_fixup = True + +- wait_for_entry(a_conn, entry.dn) ++ wait_for_entry( ++ a_conn, entry.dn, timeout=api.env.replication_wait_timeout ++ ) + + def needs_memberof_fixup(self): + return self.need_memberof_fixup +-- +2.17.1 + diff --git a/SOURCES/0060-Fix-replication-races-in-Dogtag-admin-code.patch b/SOURCES/0060-Fix-replication-races-in-Dogtag-admin-code.patch new file mode 100644 index 0000000..b4eae37 --- /dev/null +++ b/SOURCES/0060-Fix-replication-races-in-Dogtag-admin-code.patch @@ -0,0 +1,209 @@ +From 9a576f7303fd1b7faae3a419f6d54720f234585b Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 22 Jun 2018 10:04:38 +0200 +Subject: [PATCH] Fix replication races in Dogtag admin code + +DogtagInstance.setup_admin and related methods have multiple LDAP +replication race conditions. The bugs can cause parallel +ipa-replica-install to fail. + +The code from __add_admin_to_group() has been changed to use MOD_ADD +ather than search + MOD_REPLACE. The MOD_REPLACE approach can lead to +data loss, when more than one writer changes a group. + +setup_admin() now waits until both admin user and group membership have +been replicated to the master peer. The method also adds a new ACI to +allow querying group member in the replication check. + +Fixes: https://pagure.io/freeipa/issue/7593 +Signed-off-by: Christian Heimes +Reviewed-By: Fraser Tweedale +--- + ipaserver/install/cainstance.py | 1 + + ipaserver/install/dogtaginstance.py | 101 ++++++++++++++++++++++------ + ipaserver/install/krainstance.py | 1 + + 3 files changed, 82 insertions(+), 21 deletions(-) + +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index 3a3558a5ded6de3e578574d08a5cdb59338e99c4..0c4d9bf9ad8ae11ac88523857845e16eb62651b9 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -377,6 +377,7 @@ class CAInstance(DogtagInstance): + # Setup Database + self.step("creating certificate server db", self.__create_ds_db) + self.step("setting up initial replication", self.__setup_replication) ++ self.step("creating ACIs for admin", self.add_ipaca_aci) + self.step("creating installation admin user", self.setup_admin) + self.step("configuring certificate server instance", + self.__spawn_instance) +diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py +index 9470e1a13608a8a84aab8a36c269a708e3f3e9f4..960b8cc7ce495bf5ca359f72b46aa0d43ccec5c3 100644 +--- a/ipaserver/install/dogtaginstance.py ++++ b/ipaserver/install/dogtaginstance.py +@@ -18,6 +18,8 @@ + # + + import base64 ++import time ++ + import ldap + import os + import shutil +@@ -84,6 +86,16 @@ class DogtagInstance(service.Service): + tracking_reqs = None + server_cert_name = None + ++ ipaca_groups = DN(('ou', 'groups'), ('o', 'ipaca')) ++ ipaca_people = DN(('ou', 'people'), ('o', 'ipaca')) ++ groups_aci = ( ++ b'(targetfilter="(objectClass=groupOfUniqueNames)")' ++ b'(targetattr="cn || description || objectclass || uniquemember")' ++ b'(version 3.0; acl "Allow users from o=ipaca to read groups"; ' ++ b'allow (read, search, compare) ' ++ b'userdn="ldap:///uid=*,ou=people,o=ipaca";)' ++ ) ++ + def __init__(self, realm, subsystem, service_desc, host_name=None, + nss_db=paths.PKI_TOMCAT_ALIAS_DIR, service_prefix=None): + """Initializer""" +@@ -101,10 +113,11 @@ class DogtagInstance(service.Service): + self.pkcs12_info = None + self.clone = False + +- self.basedn = DN(('o', 'ipa%s' % subsystem.lower())) ++ self.basedn = DN(('o', 'ipaca')) + self.admin_user = "admin" +- self.admin_dn = DN(('uid', self.admin_user), +- ('ou', 'people'), ('o', 'ipaca')) ++ self.admin_dn = DN( ++ ('uid', self.admin_user), self.ipaca_people ++ ) + self.admin_groups = None + self.tmp_agent_db = None + self.subsystem = subsystem +@@ -385,27 +398,33 @@ class DogtagInstance(service.Service): + + raise RuntimeError("%s configuration failed." % self.subsystem) + +- def __add_admin_to_group(self, group): +- dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca')) +- entry = api.Backend.ldap2.get_entry(dn) +- members = entry.get('uniqueMember', []) +- members.append(self.admin_dn) +- mod = [(ldap.MOD_REPLACE, 'uniqueMember', members)] ++ def add_ipaca_aci(self): ++ """Add ACI to allow ipaca users to read their own group information ++ ++ Dogtag users aren't allowed to enumerate their own groups. The ++ setup_admin() method needs the permission to wait, until all group ++ information has been replicated. ++ """ ++ dn = self.ipaca_groups ++ mod = [(ldap.MOD_ADD, 'aci', [self.groups_aci])] + try: + api.Backend.ldap2.modify_s(dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS: +- # already there +- pass ++ self.log.debug( ++ "%s already has ACI to read group information", dn ++ ) ++ else: ++ self.log.debug("Added ACI to read groups to %s", dn) + + def setup_admin(self): + self.admin_user = "admin-%s" % self.fqdn + self.admin_password = ipautil.ipa_generate_password() +- self.admin_dn = DN(('uid', self.admin_user), +- ('ou', 'people'), ('o', 'ipaca')) +- ++ self.admin_dn = DN( ++ ('uid', self.admin_user), self.ipaca_people ++ ) + # remove user if left-over exists + try: +- entry = api.Backend.ldap2.delete_entry(self.admin_dn) ++ api.Backend.ldap2.delete_entry(self.admin_dn) + except errors.NotFound: + pass + +@@ -424,18 +443,58 @@ class DogtagInstance(service.Service): + ) + api.Backend.ldap2.add_entry(entry) + ++ wait_groups = [] + for group in self.admin_groups: +- self.__add_admin_to_group(group) ++ group_dn = DN(('cn', group), self.ipaca_groups) ++ mod = [(ldap.MOD_ADD, 'uniqueMember', [self.admin_dn])] ++ try: ++ api.Backend.ldap2.modify_s(group_dn, mod) ++ except ldap.TYPE_OR_VALUE_EXISTS: ++ # already there ++ return None ++ else: ++ wait_groups.append(group_dn) + + # Now wait until the other server gets replicated this data + ldap_uri = ipaldap.get_ldap_uri(self.master_host) +- master_conn = ipaldap.LDAPClient(ldap_uri) +- master_conn.gssapi_bind() +- replication.wait_for_entry(master_conn, entry.dn) +- del master_conn ++ master_conn = ipaldap.LDAPClient( ++ ldap_uri, start_tls=True, cacert=paths.IPA_CA_CRT ++ ) ++ self.log.debug( ++ "Waiting for %s to appear on %s", self.admin_dn, master_conn ++ ) ++ deadline = time.time() + api.env.replication_wait_timeout ++ while time.time() < deadline: ++ time.sleep(1) ++ try: ++ master_conn.simple_bind(self.admin_dn, self.admin_password) ++ except ldap.INVALID_CREDENTIALS: ++ pass ++ else: ++ self.log.debug("Successfully logged in as %s", self.admin_dn) ++ break ++ else: ++ self.log.error( ++ "Unable to log in as %s on %s", self.admin_dn, master_conn ++ ) ++ raise errors.NotFound( ++ reason="{} did not replicate to {}".format( ++ self.admin_dn, master_conn ++ ) ++ ) ++ ++ # wait for group membership ++ for group_dn in wait_groups: ++ replication.wait_for_entry( ++ master_conn, ++ group_dn, ++ timeout=api.env.replication_wait_timeout, ++ attr='uniqueMember', ++ attrvalue=self.admin_dn ++ ) + + def __remove_admin_from_group(self, group): +- dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca')) ++ dn = DN(('cn', group), self.ipaca_groups) + mod = [(ldap.MOD_DELETE, 'uniqueMember', self.admin_dn)] + try: + api.Backend.ldap2.modify_s(dn, mod) +diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py +index 990bb87ca2f0029d2450cbef47958399f534f2a6..915d3c3c6e038eeb6a8f94f1ed7f7008c0ef4ead 100644 +--- a/ipaserver/install/krainstance.py ++++ b/ipaserver/install/krainstance.py +@@ -111,6 +111,7 @@ class KRAInstance(DogtagInstance): + "A Dogtag CA must be installed first") + + if promote: ++ self.step("creating ACIs for admin", self.add_ipaca_aci) + self.step("creating installation admin user", self.setup_admin) + self.step("configuring KRA instance", self.__spawn_instance) + if not self.clone: +-- +2.17.1 + diff --git a/SOURCES/0061-Increase-WSGI-process-count-to-5-on-64bit.patch b/SOURCES/0061-Increase-WSGI-process-count-to-5-on-64bit.patch new file mode 100644 index 0000000..ff15210 --- /dev/null +++ b/SOURCES/0061-Increase-WSGI-process-count-to-5-on-64bit.patch @@ -0,0 +1,88 @@ +From 6110a949aee9c98ab077c9cd907881ad82be5f45 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Thu, 14 Jun 2018 17:04:13 +0200 +Subject: [PATCH] Increase WSGI process count to 5 on 64bit + +Increase the WSGI daemon worker process count from 2 processes to 5 +processes. This allows IPA RPC to handle more parallel requests. The +additional processes increase memory consumption by approximante 250 MB +in total. + +Since memory is scarce on 32bit platforms, only 64bit platforms are +bumped to 5 workers. + +Fixes: https://pagure.io/freeipa/issue/7587 +Signed-off-by: Christian Heimes +Reviewed-By: Alexander Bokovoy +--- + install/conf/ipa.conf | 2 +- + ipaplatform/base/constants.py | 5 +++++ + ipaserver/install/httpinstance.py | 1 + + ipaserver/install/server/upgrade.py | 3 ++- + 4 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf +index 01bf9a4f97fc0cf197c0ad12743affa597b54911..34ced2ab9d91ae174a42a580e1c4f9436c1a8c3b 100644 +--- a/install/conf/ipa.conf ++++ b/install/conf/ipa.conf +@@ -51,7 +51,7 @@ WSGISocketPrefix /run/httpd/wsgi + + + # Configure mod_wsgi handler for /ipa +-WSGIDaemonProcess ipa processes=2 threads=1 maximum-requests=500 \ ++WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \ + user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 + WSGIImportScript /usr/share/ipa/wsgi.py process-group=ipa application-group=ipa + WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py +diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py +index dccb0e7191cb0d9644eb286b9ec061599afa3980..db250d9a40466e852453e7309c704a6897c6bcf8 100644 +--- a/ipaplatform/base/constants.py ++++ b/ipaplatform/base/constants.py +@@ -5,9 +5,11 @@ + ''' + This base platform module exports platform dependant constants. + ''' ++import sys + + + class BaseConstantsNamespace(object): ++ IS_64BITS = sys.maxsize > 2 ** 32 + DS_USER = 'dirsrv' + DS_GROUP = 'dirsrv' + HTTPD_USER = "apache" +@@ -28,3 +30,6 @@ class BaseConstantsNamespace(object): + # nfsd init variable used to enable kerberized NFS + SECURE_NFS_VAR = "SECURE_NFS" + SSSD_USER = "sssd" ++ # WSGIDaemonProcess process count. On 64bit platforms, each process ++ # consumes about 110 MB RSS, from which are about 35 MB shared. ++ WSGI_PROCESSES = 5 if IS_64BITS else 2 +diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py +index e68bfc09b34e087dfb4872b6565b06c6c2188384..7081c7418e76afbd1b4ae28deafefb6b264c62f0 100644 +--- a/ipaserver/install/httpinstance.py ++++ b/ipaserver/install/httpinstance.py +@@ -152,6 +152,7 @@ class HTTPInstance(service.Service): + DOMAIN=self.domain, + AUTOREDIR='' if auto_redirect else '#', + CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR, ++ WSGI_PROCESSES=constants.WSGI_PROCESSES, + ) + self.ca_file = ca_file + if ca_is_configured is not None: +diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py +index 793092be86da687fd21cf2c0ef3608f32fcf9f16..667b9d214ce76031b5d0f205e03ddb46178e9b2f 100644 +--- a/ipaserver/install/server/upgrade.py ++++ b/ipaserver/install/server/upgrade.py +@@ -1615,7 +1615,8 @@ def upgrade_configuration(): + AUTOREDIR='' if auto_redirect else '#', + CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR, + DOGTAG_PORT=8009, +- CLONE='#' ++ CLONE='#', ++ WSGI_PROCESSES=constants.WSGI_PROCESSES, + ) + + subject_base = find_subject_base() +-- +2.17.1 + diff --git a/SOURCES/0062-Use-4-WSGI-workers-on-64bit-systems.patch b/SOURCES/0062-Use-4-WSGI-workers-on-64bit-systems.patch new file mode 100644 index 0000000..eee7d99 --- /dev/null +++ b/SOURCES/0062-Use-4-WSGI-workers-on-64bit-systems.patch @@ -0,0 +1,28 @@ +From e9c4bf911675d88c300458faacdc32d2b80a189e Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Mon, 25 Jun 2018 10:59:18 +0200 +Subject: [PATCH] Use 4 WSGI workers on 64bit systems + +Commit f1d5ab3a03191dbb02e5f95308cf8c4f1971cdcf increases WSGI worker +count to five. This turned out to be a bit much for our test systems. +Four workers are good enough and still double the old amount. + +See: https://pagure.io/freeipa/issue/7587 +Signed-off-by: Christian Heimes +--- + ipaplatform/base/constants.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py +index db250d9a40466e852453e7309c704a6897c6bcf8..8cfd46290d6e0021238c2f8d96a795928b2913df 100644 +--- a/ipaplatform/base/constants.py ++++ b/ipaplatform/base/constants.py +@@ -32,4 +32,4 @@ class BaseConstantsNamespace(object): + SSSD_USER = "sssd" + # WSGIDaemonProcess process count. On 64bit platforms, each process + # consumes about 110 MB RSS, from which are about 35 MB shared. +- WSGI_PROCESSES = 5 if IS_64BITS else 2 ++ WSGI_PROCESSES = 4 if IS_64BITS else 2 +-- +2.17.1 + diff --git a/SOURCES/0063-Catch-ACIError-instead-of-invalid-credentials.patch b/SOURCES/0063-Catch-ACIError-instead-of-invalid-credentials.patch new file mode 100644 index 0000000..ac76cb3 --- /dev/null +++ b/SOURCES/0063-Catch-ACIError-instead-of-invalid-credentials.patch @@ -0,0 +1,36 @@ +From 699104cf8b8d0ec2570b3801e86c6a358343527a Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 29 Jun 2018 11:08:45 +0200 +Subject: [PATCH] Catch ACIError instead of invalid credentials + +ipaldap's LDAPClient client turns INVALID_CREDENTIAL error into +ACIError. Catch the ACIError and wait until the user has been +replicated. + +Apparently no manual or automated test ran into the timeout during +testing. + +Fixes: Fixes: https://pagure.io/freeipa/issue/7593 +Signed-off-by: Christian Heimes +Reviewed-By: Alexander Bokovoy +--- + ipaserver/install/dogtaginstance.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py +index 960b8cc7ce495bf5ca359f72b46aa0d43ccec5c3..1f9742b287f58ed117aba627ad85ac3ced4b2645 100644 +--- a/ipaserver/install/dogtaginstance.py ++++ b/ipaserver/install/dogtaginstance.py +@@ -468,7 +468,8 @@ class DogtagInstance(service.Service): + time.sleep(1) + try: + master_conn.simple_bind(self.admin_dn, self.admin_password) +- except ldap.INVALID_CREDENTIALS: ++ except errors.ACIError: ++ # user not replicated yet + pass + else: + self.log.debug("Successfully logged in as %s", self.admin_dn) +-- +2.17.1 + diff --git a/SOURCES/0064-Query-for-server-role-IPA-master.patch b/SOURCES/0064-Query-for-server-role-IPA-master.patch new file mode 100644 index 0000000..1d184fb --- /dev/null +++ b/SOURCES/0064-Query-for-server-role-IPA-master.patch @@ -0,0 +1,109 @@ +From 500be304c4b218b40acfa31cf987e541958b8985 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Thu, 5 Jul 2018 23:50:37 +0200 +Subject: [PATCH] Query for server role IPA master + +server_find and server_role plugin were hiding IPA master role +information. It's now possible to fetch IPA master role information and +to filter by IPA master role, e.g. to ignore servers that have some +services configured but not (yet) enabled. + +See: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Alexander Bokovoy +Reviewed-By: Fraser Tweedale +--- + API.txt | 3 ++- + ipaserver/plugins/server.py | 9 +++++++-- + ipaserver/plugins/serverrole.py | 18 +++++++++++++++--- + 3 files changed, 24 insertions(+), 6 deletions(-) + +diff --git a/API.txt b/API.txt +index 5feed54947e044a0a2c908e70b44fe59a86972ff..7262a4122be06ab3ca2296897de84bea458fcf0a 100644 +--- a/API.txt ++++ b/API.txt +@@ -4421,9 +4421,10 @@ output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') + command: server_role_find/1 +-args: 1,8,4 ++args: 1,9,4 + arg: Str('criteria?') + option: Flag('all', autofill=True, cli_name='all', default=False) ++option: Flag('include_master', autofill=True, default=False) + option: Flag('raw', autofill=True, cli_name='raw', default=False) + option: Str('role_servrole?', autofill=False, cli_name='role') + option: Str('server_server?', autofill=False, cli_name='server') +diff --git a/ipaserver/plugins/server.py b/ipaserver/plugins/server.py +index e0dc953a1ef870c95fdcdb629fb6ab3103e8f999..eb776aa8cf676c26d80c22ec87f8b5a310d0c6dc 100644 +--- a/ipaserver/plugins/server.py ++++ b/ipaserver/plugins/server.py +@@ -199,7 +199,10 @@ class server(LDAPObject): + return + + enabled_roles = self.api.Command.server_role_find( +- server_server=entry_attrs['cn'][0], status=ENABLED)['result'] ++ server_server=entry_attrs['cn'][0], ++ status=ENABLED, ++ include_master=True, ++ )['result'] + + enabled_role_names = [r[u'role_servrole'] for r in enabled_roles] + +@@ -333,7 +336,9 @@ class server_find(LDAPSearch): + role_status = self.api.Command.server_role_find( + server_server=None, + role_servrole=role, +- status=ENABLED)['result'] ++ status=ENABLED, ++ include_master=True, ++ )['result'] + + return set( + r[u'server_server'] for r in role_status) +diff --git a/ipaserver/plugins/serverrole.py b/ipaserver/plugins/serverrole.py +index b5781b0dff4c5d6f433e6a5531fc3e830ffcd972..db88b3885c538c2800f6e4a1d649083859d43641 100644 +--- a/ipaserver/plugins/serverrole.py ++++ b/ipaserver/plugins/serverrole.py +@@ -5,7 +5,7 @@ + from ipalib.crud import Retrieve, Search + from ipalib.errors import NotFound + from ipalib.frontend import Object +-from ipalib.parameters import Int, Str, StrEnum ++from ipalib.parameters import Flag, Int, Str, StrEnum + from ipalib.plugable import Registry + from ipalib import _, ngettext + +@@ -129,6 +129,10 @@ class server_role_find(Search): + minvalue=0, + autofill=False, + ), ++ Flag( ++ 'include_master', ++ doc=_('Include IPA master entries'), ++ ) + ) + + def execute(self, *keys, **options): +@@ -151,8 +155,16 @@ class server_role_find(Search): + role_servrole=role_name, + status=status) + +- result = [ +- r for r in role_status if r[u'role_servrole'] != "IPA master"] ++ # Don't display "IPA master" information unless the role is ++ # requested explicitly. All servers are considered IPA masters, ++ # except for replicas during installation. ++ if options.get('include_master') or role_name == "IPA master": ++ result = role_status ++ else: ++ result = [ ++ r for r in role_status ++ if r[u'role_servrole'] != "IPA master" ++ ] + return dict( + result=result, + count=len(result), +-- +2.17.1 + diff --git a/SOURCES/0065-Only-create-DNS-SRV-records-for-ready-server.patch b/SOURCES/0065-Only-create-DNS-SRV-records-for-ready-server.patch new file mode 100644 index 0000000..61ef03b --- /dev/null +++ b/SOURCES/0065-Only-create-DNS-SRV-records-for-ready-server.patch @@ -0,0 +1,50 @@ +From 0f6afe8ffa39804d7bb5e86e4aa447f4d56a4dfa Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Thu, 5 Jul 2018 23:59:06 +0200 +Subject: [PATCH] Only create DNS SRV records for ready server + +When installing multiple replicas in parallel, one replica may create +SRV entries for other replicas, although the replicas aren't fully +installed yet. This may cause some services to connect to a server, that +isn't ready to serve requests. + +The DNS IPASystemRecords framework now skips all servers that aren't +ready IPA masters. + +See: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Alexander Bokovoy +Reviewed-By: Fraser Tweedale +--- + ipaserver/dns_data_management.py | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/ipaserver/dns_data_management.py b/ipaserver/dns_data_management.py +index 2008ba6e7d387046b74e3de0af644d97b145ccb7..6016d8a0044d487c3118f43f199b2a433facfa9a 100644 +--- a/ipaserver/dns_data_management.py ++++ b/ipaserver/dns_data_management.py +@@ -93,7 +93,9 @@ class IPASystemRecords(object): + self.servers_data = {} + + servers_result = self.api_instance.Command.server_find( +- no_members=False)['result'] ++ no_members=False, ++ servrole=u"IPA master", # only active, fully installed masters ++ )['result'] + for s in servers_result: + weight, location, roles = self.__get_server_attrs(s) + self.servers_data[s['cn'][0]] = { +@@ -345,7 +347,9 @@ class IPASystemRecords(object): + zone_obj = zone.Zone(self.domain_abs, relativize=False) + if servers is None: + servers_result = self.api_instance.Command.server_find( +- pkey_only=True)['result'] ++ pkey_only=True, ++ servrole=u"IPA master", # only fully installed masters ++ )['result'] + servers = [s['cn'][0] for s in servers_result] + + locations_result = self.api_instance.Command.location_find()['result'] +-- +2.17.1 + diff --git a/SOURCES/0066-Delay-enabling-services-until-end-of-installer.patch b/SOURCES/0066-Delay-enabling-services-until-end-of-installer.patch new file mode 100644 index 0000000..9395c45 --- /dev/null +++ b/SOURCES/0066-Delay-enabling-services-until-end-of-installer.patch @@ -0,0 +1,602 @@ +From f0d829754e2e35225f0dfba980c9f4bae011407e Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 6 Jul 2018 00:04:39 +0200 +Subject: [PATCH] Delay enabling services until end of installer + +Service entries in cn=FQDN,cn=masters,cn=ipa,cn=etc are no longer +created as enabled. Instead they are flagged as configuredService. At +the very end of the installer, the service entries are switched from +configured to enabled service. + +- SRV records are created at the very end of the installer. +- Dogtag installer only picks fully installed servers +- Certmonger ignores all configured but not yet enabled servers. + +Fixes: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Alexander Bokovoy +Reviewed-By: Fraser Tweedale +--- + install/tools/ipa-adtrust-install | 6 +- + install/tools/ipa-ca-install | 12 ++- + install/tools/ipa-dns-install | 4 + + ipaserver/dns_data_management.py | 18 +++-- + ipaserver/install/adtrustinstance.py | 6 +- + ipaserver/install/bindinstance.py | 6 +- + ipaserver/install/cainstance.py | 2 +- + ipaserver/install/dnskeysyncinstance.py | 4 +- + ipaserver/install/httpinstance.py | 4 +- + ipaserver/install/ipa_kra_install.py | 7 ++ + ipaserver/install/krainstance.py | 2 +- + ipaserver/install/krbinstance.py | 4 +- + ipaserver/install/odsexporterinstance.py | 4 +- + ipaserver/install/opendnssecinstance.py | 6 +- + ipaserver/install/server/install.py | 18 +++-- + ipaserver/install/server/replicainstall.py | 9 ++- + ipaserver/install/service.py | 90 ++++++++++++++++++++-- + ipaserver/plugins/serverrole.py | 7 +- + 18 files changed, 161 insertions(+), 48 deletions(-) + +diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install +index 1484598adba5b1237f00cc55e95167d45a6b40d7..4258f489873b4095a6672e20f2ac5f2858b71982 100755 +--- a/install/tools/ipa-adtrust-install ++++ b/install/tools/ipa-adtrust-install +@@ -31,7 +31,7 @@ import six + from optparse import SUPPRESS_HELP # pylint: disable=deprecated-module + + from ipalib.install import sysrestore +-from ipaserver.install import adtrust ++from ipaserver.install import adtrust, service + from ipaserver.install.installutils import ( + read_password, + check_server_configuration, +@@ -210,6 +210,10 @@ def main(): + adtrust.install_check(True, options, api) + adtrust.install(True, options, fstore, api) + ++ # Enable configured services and update DNS SRV records ++ service.enable_services(api.env.host) ++ api.Command.dns_update_system_records() ++ + print(""" + ============================================================================= + Setup complete +diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install +index a3694007e5815d1c44f642057c749879d336dfc5..215a60ae744577de73a7260bbf3dea5a09a4d118 100755 +--- a/install/tools/ipa-ca-install ++++ b/install/tools/ipa-ca-install +@@ -303,18 +303,26 @@ def main(): + ) + api.finalize() + api.Backend.ldap2.connect() +- + domain_level = dsinstance.get_domain_level(api) ++ + if domain_level > DOMAIN_LEVEL_0: + promote(safe_options, options, filename) + else: + install(safe_options, options, filename) + ++ # pki-spawn restarts 389-DS, reconnect ++ api.Backend.ldap2.close() ++ api.Backend.ldap2.connect() ++ ++ # Enable configured services and update DNS SRV records ++ service.enable_services(api.env.host) ++ api.Command.dns_update_system_records() ++ api.Backend.ldap2.disconnect() ++ + # execute ipactl to refresh services status + ipautil.run(['ipactl', 'start', '--ignore-service-failures'], + raiseonerr=False) + +- api.Backend.ldap2.disconnect() + + fail_message = ''' + Your system may be partly configured. +diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install +index cb6c5d887f101135ca593ea6d4ed0caf51478a4c..04d1b140d2c79a0fa72d7df47d556643751bddb7 100755 +--- a/install/tools/ipa-dns-install ++++ b/install/tools/ipa-dns-install +@@ -36,6 +36,7 @@ from ipapython.config import IPAOptionParser + from ipapython.ipa_log_manager import standard_logging_setup, root_logger + + from ipaserver.install import dns as dns_installer ++from ipaserver.install import service + + log_file_name = paths.IPASERVER_INSTALL_LOG + +@@ -145,6 +146,9 @@ def main(): + + dns_installer.install_check(True, api, False, options, hostname=api.env.host) + dns_installer.install(True, False, options) ++ # Enable configured services and update DNS SRV records ++ service.enable_services(api.env.host) ++ api.Command.dns_update_system_records() + + # execute ipactl to refresh services status + ipautil.run(['ipactl', 'start', '--ignore-service-failures'], +diff --git a/ipaserver/dns_data_management.py b/ipaserver/dns_data_management.py +index 6016d8a0044d487c3118f43f199b2a433facfa9a..e5987b4bd7b43d3920e9da917258153e448206b7 100644 +--- a/ipaserver/dns_data_management.py ++++ b/ipaserver/dns_data_management.py +@@ -65,11 +65,11 @@ class IPASystemRecords(object): + PRIORITY_HIGH = 0 + PRIORITY_LOW = 50 + +- def __init__(self, api_instance): ++ def __init__(self, api_instance, all_servers=False): + self.api_instance = api_instance + self.domain_abs = DNSName(self.api_instance.env.domain).make_absolute() + self.servers_data = {} +- self.__init_data() ++ self.__init_data(all_servers=all_servers) + + def reload_data(self): + """ +@@ -89,14 +89,16 @@ class IPASystemRecords(object): + def __get_location_suffix(self, location): + return location + DNSName('_locations') + self.domain_abs + +- def __init_data(self): ++ def __init_data(self, all_servers=False): + self.servers_data = {} + +- servers_result = self.api_instance.Command.server_find( +- no_members=False, +- servrole=u"IPA master", # only active, fully installed masters +- )['result'] +- for s in servers_result: ++ kwargs = dict(no_members=False) ++ if not all_servers: ++ # only active, fully installed masters] ++ kwargs["servrole"] = u"IPA master" ++ servers = self.api_instance.Command.server_find(**kwargs) ++ ++ for s in servers['result']: + weight, location, roles = self.__get_server_attrs(s) + self.servers_data[s['cn'][0]] = { + 'weight': weight, +diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py +index b4db055045823ce8ae7e3b264e1442a085f81b2d..a7261b92ac227228b5b6af41db621ed2e5e96668 100644 +--- a/ipaserver/install/adtrustinstance.py ++++ b/ipaserver/install/adtrustinstance.py +@@ -581,7 +581,7 @@ class ADTRUSTInstance(service.Service): + self.print_msg(err_msg) + self.print_msg("Add the following service records to your DNS " \ + "server for DNS zone %s: " % zone) +- system_records = IPASystemRecords(api) ++ system_records = IPASystemRecords(api, all_servers=True) + adtrust_records = system_records.get_base_records( + [self.fqdn], ["AD trust controller"], + include_master_role=False, include_kerberos_realm=False) +@@ -734,12 +734,12 @@ class ADTRUSTInstance(service.Service): + # Note that self.dm_password is None for ADTrustInstance because + # we ensure to be called as root and using ldapi to use autobind + try: +- self.ldap_enable('ADTRUST', self.fqdn, None, self.suffix) ++ self.ldap_configure('ADTRUST', self.fqdn, None, self.suffix) + except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): + root_logger.info("ADTRUST Service startup entry already exists.") + + try: +- self.ldap_enable('EXTID', self.fqdn, None, self.suffix) ++ self.ldap_configure('EXTID', self.fqdn, None, self.suffix) + except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): + root_logger.info("EXTID Service startup entry already exists.") + +diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py +index 03dce56aa0610b3dc530e6b2a185515be7956e8b..771c6b0483d07b20fbc8470397eed306651f4a8f 100644 +--- a/ipaserver/install/bindinstance.py ++++ b/ipaserver/install/bindinstance.py +@@ -664,7 +664,7 @@ class BindInstance(service.Service): + return normalize_zone(self.host_domain) == normalize_zone(self.domain) + + def create_file_with_system_records(self): +- system_records = IPASystemRecords(self.api) ++ system_records = IPASystemRecords(self.api, all_servers=True) + text = u'\n'.join( + IPASystemRecords.records_list_from_zone( + system_records.get_base_records() +@@ -741,7 +741,7 @@ class BindInstance(service.Service): + # Instead we reply on the IPA init script to start only enabled + # components as found in our LDAP configuration tree + try: +- self.ldap_enable('DNS', self.fqdn, None, self.suffix) ++ self.ldap_configure('DNS', self.fqdn, None, self.suffix) + except errors.DuplicateEntry: + # service already exists (forced DNS reinstall) + # don't crash, just report error +@@ -1175,7 +1175,7 @@ class BindInstance(service.Service): + except ValueError as error: + root_logger.debug(error) + +- # disabled by default, by ldap_enable() ++ # disabled by default, by ldap_configure() + if enabled: + self.enable() + +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index 0c4d9bf9ad8ae11ac88523857845e16eb62651b9..62e9ad7de6f00eabb48f726a3931eb8acf0ba22b 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -1218,7 +1218,7 @@ class CAInstance(DogtagInstance): + config = ['caRenewalMaster'] + else: + config = [] +- self.ldap_enable('CA', self.fqdn, None, basedn, config) ++ self.ldap_configure('CA', self.fqdn, None, basedn, config) + + def setup_lightweight_ca_key_retrieval(self): + if sysupgrade.get_upgrade_state('dogtag', 'setup_lwca_key_retrieval'): +diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py +index 3849626e5a253667271913337d1a5aa4a72755bb..28468826d7194e7103e61c0ef3957b849e1be896 100644 +--- a/ipaserver/install/dnskeysyncinstance.py ++++ b/ipaserver/install/dnskeysyncinstance.py +@@ -384,8 +384,8 @@ class DNSKeySyncInstance(service.Service): + + def __enable(self): + try: +- self.ldap_enable('DNSKeySync', self.fqdn, None, +- self.suffix, self.extra_config) ++ self.ldap_configure('DNSKeySync', self.fqdn, None, ++ self.suffix, self.extra_config) + except errors.DuplicateEntry: + self.logger.error("DNSKeySync service already exists") + +diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py +index 7081c7418e76afbd1b4ae28deafefb6b264c62f0..2df51eaa77d3ee3246027a6bcbc4023dbad61160 100644 +--- a/ipaserver/install/httpinstance.py ++++ b/ipaserver/install/httpinstance.py +@@ -200,7 +200,7 @@ class HTTPInstance(service.Service): + # We do not let the system start IPA components on its own, + # Instead we reply on the IPA init script to start only enabled + # components as found in our LDAP configuration tree +- self.ldap_enable('HTTP', self.fqdn, None, self.suffix) ++ self.ldap_configure('HTTP', self.fqdn, None, self.suffix) + + def configure_selinux_for_httpd(self): + try: +@@ -583,7 +583,7 @@ class HTTPInstance(service.Service): + if running: + self.restart() + +- # disabled by default, by ldap_enable() ++ # disabled by default, by ldap_configure() + if enabled: + self.enable() + +diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py +index 9ebabe057513141ee76d238a3f20e76a27dd932e..3a639ac20f101293edd7449f8846a451469e2297 100644 +--- a/ipaserver/install/ipa_kra_install.py ++++ b/ipaserver/install/ipa_kra_install.py +@@ -224,4 +224,11 @@ class KRAInstaller(KRAInstall): + self.log.error(dedent(self.FAIL_MESSAGE)) + raise + ++ # pki-spawn restarts 389-DS, reconnect ++ api.Backend.ldap2.close() ++ api.Backend.ldap2.connect() ++ ++ # Enable configured services and update DNS SRV records ++ service.enable_services(api.env.host) ++ api.Command.dns_update_system_records() + api.Backend.ldap2.disconnect() +diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py +index 915d3c3c6e038eeb6a8f94f1ed7f7008c0ef4ead..a23de3960f0789761b4b86227508236c80e97c2f 100644 +--- a/ipaserver/install/krainstance.py ++++ b/ipaserver/install/krainstance.py +@@ -376,4 +376,4 @@ class KRAInstance(DogtagInstance): + directives[nickname], cert, paths.KRA_CS_CFG_PATH) + + def __enable_instance(self): +- self.ldap_enable('KRA', self.fqdn, None, self.suffix) ++ self.ldap_configure('KRA', self.fqdn, None, self.suffix) +diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py +index 5971a30fc566f6e96ce0b08632772d33da5602d2..4041d1b5fb3c3cf3db78b6cb282ce5f17793a0e3 100644 +--- a/ipaserver/install/krbinstance.py ++++ b/ipaserver/install/krbinstance.py +@@ -240,7 +240,7 @@ class KrbInstance(service.Service): + # We do not let the system start IPA components on its own, + # Instead we reply on the IPA init script to start only enabled + # components as found in our LDAP configuration tree +- self.ldap_enable('KDC', self.fqdn, None, self.suffix) ++ self.ldap_configure('KDC', self.fqdn, None, self.suffix) + + def __start_instance(self): + try: +@@ -598,7 +598,7 @@ class KrbInstance(service.Service): + except ValueError as error: + root_logger.debug(error) + +- # disabled by default, by ldap_enable() ++ # disabled by default, by ldap_configure() + if enabled: + self.enable() + +diff --git a/ipaserver/install/odsexporterinstance.py b/ipaserver/install/odsexporterinstance.py +index 59f27f578dab5663b1a7b734dff3699a6996084d..1694704f967b0b2a5debe76252ae859ae8a47f2b 100644 +--- a/ipaserver/install/odsexporterinstance.py ++++ b/ipaserver/install/odsexporterinstance.py +@@ -69,8 +69,8 @@ class ODSExporterInstance(service.Service): + def __enable(self): + + try: +- self.ldap_enable('DNSKeyExporter', self.fqdn, None, +- self.suffix) ++ self.ldap_configure('DNSKeyExporter', self.fqdn, None, ++ self.suffix) + except errors.DuplicateEntry: + root_logger.error("DNSKeyExporter service already exists") + +diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py +index bc2974a2cf56e4ade1b778303c14f9ce05a8bf0f..92a46a9f6e318454084398ed625cf27a8250c2af 100644 +--- a/ipaserver/install/opendnssecinstance.py ++++ b/ipaserver/install/opendnssecinstance.py +@@ -136,8 +136,8 @@ class OpenDNSSECInstance(service.Service): + + def __enable(self): + try: +- self.ldap_enable('DNSSEC', self.fqdn, None, +- self.suffix, self.extra_config) ++ self.ldap_configure('DNSSEC', self.fqdn, None, ++ self.suffix, self.extra_config) + except errors.DuplicateEntry: + root_logger.error("DNSSEC service already exists") + +@@ -368,7 +368,7 @@ class OpenDNSSECInstance(service.Service): + + self.restore_state("kasp_db_configured") # just eat state + +- # disabled by default, by ldap_enable() ++ # disabled by default, by ldap_configure() + if enabled: + self.enable() + +diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py +index 3651cde827ecf299e5570feed4936311f91749fb..dcdd9aabb746c4973b3f73934d94225503728f0b 100644 +--- a/ipaserver/install/server/install.py ++++ b/ipaserver/install/server/install.py +@@ -869,14 +869,6 @@ def install(installer): + + if options.setup_dns: + dns.install(False, False, options) +- else: +- # Create a BIND instance +- bind = bindinstance.BindInstance(fstore) +- bind.setup(host_name, ip_addresses, realm_name, +- domain_name, (), 'first', (), +- zonemgr=options.zonemgr, +- no_dnssec_validation=options.no_dnssec_validation) +- bind.create_file_with_system_records() + + if options.setup_adtrust: + adtrust.install(False, options, fstore, api) +@@ -908,6 +900,16 @@ def install(installer): + # Make sure the files we crated in /var/run are recreated at startup + tasks.configure_tmpfiles() + ++ # Enable configured services and update DNS SRV records ++ service.enable_services(host_name) ++ api.Command.dns_update_system_records() ++ ++ if not options.setup_dns: ++ # After DNS and AD trust are configured and services are ++ # enabled, create a dummy instance to dump DNS configuration. ++ bind = bindinstance.BindInstance(fstore) ++ bind.create_file_with_system_records() ++ + # Everything installed properly, activate ipa service. + services.knownservices.ipa.enable() + +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index b10f761e3f643f9fa868451192fa4550b24b6b16..59fec452c674b9941ce731748dd63985a08fefc0 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -1518,14 +1518,10 @@ def install(installer): + + if options.setup_dns: + dns.install(False, True, options, api) +- else: +- api.Command.dns_update_system_records() + + if options.setup_adtrust: + adtrust.install(False, options, fstore, api) + +- api.Backend.ldap2.disconnect() +- + if not promote: + # Call client install script + service.print_msg("Configuring client side components") +@@ -1556,6 +1552,11 @@ def install(installer): + # Make sure the files we crated in /var/run are recreated at startup + tasks.configure_tmpfiles() + ++ # Enable configured services and update DNS SRV records ++ service.enable_services(config.host_name) ++ api.Command.dns_update_system_records() ++ api.Backend.ldap2.disconnect() ++ + # Everything installed properly, activate ipa service. + services.knownservices.ipa.enable() + +diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py +index 0523e914aa7debf6aaa82ddcce9b7b26c1833cd3..4271ebe06be03199165894f7a884e17f311896cb 100644 +--- a/ipaserver/install/service.py ++++ b/ipaserver/install/service.py +@@ -24,6 +24,7 @@ import socket + import datetime + import traceback + import tempfile ++import warnings + + import six + +@@ -59,6 +60,10 @@ SERVICE_LIST = { + 'DNSKeySync': ('ipa-dnskeysyncd', 110), + } + ++CONFIGURED_SERVICE = u'configuredService' ++ENABLED_SERVICE = 'enabledService' ++ ++ + def print_msg(message, output_fd=sys.stdout): + root_logger.debug(message) + output_fd.write(message) +@@ -120,7 +125,7 @@ def find_providing_server(svcname, conn, host_name=None, api=api): + """ + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) + query_filter = conn.make_filter({'objectClass': 'ipaConfigObject', +- 'ipaConfigString': 'enabledService', ++ 'ipaConfigString': ENABLED_SERVICE, + 'cn': svcname}, rules='&') + try: + entries, _trunc = conn.find_entries(filter=query_filter, base_dn=dn) +@@ -217,6 +222,53 @@ def set_service_entry_config(name, fqdn, config_values, + raise e + + ++def enable_services(fqdn): ++ """Change all configured services to enabled ++ ++ Server.ldap_configure() only marks a service as configured. Services ++ are enabled at the very end of installation. ++ ++ Note: DNS records must be updated with dns_update_system_records, too. ++ ++ :param fqdn: hostname of server ++ """ ++ ldap2 = api.Backend.ldap2 ++ search_base = DN(('cn', fqdn), api.env.container_masters, api.env.basedn) ++ search_filter = ldap2.make_filter( ++ { ++ 'objectClass': 'ipaConfigObject', ++ 'ipaConfigString': CONFIGURED_SERVICE ++ }, ++ rules='&' ++ ) ++ entries = ldap2.get_entries( ++ search_base, ++ filter=search_filter, ++ scope=api.Backend.ldap2.SCOPE_ONELEVEL, ++ attrs_list=['cn', 'ipaConfigString'] ++ ) ++ for entry in entries: ++ name = entry['cn'] ++ cfgstrings = entry.setdefault('ipaConfigString', []) ++ for value in list(cfgstrings): ++ if value.lower() == CONFIGURED_SERVICE.lower(): ++ cfgstrings.remove(value) ++ if not case_insensitive_attr_has_value(cfgstrings, ENABLED_SERVICE): ++ cfgstrings.append(ENABLED_SERVICE) ++ ++ try: ++ ldap2.update_entry(entry) ++ except errors.EmptyModlist: ++ root_logger.debug("Nothing to do for service %s", name) ++ except Exception: ++ root_logger.exception( ++ "failed to set service %s config values", name ++ ) ++ raise ++ else: ++ root_logger.debug("Enabled service %s for %s", name, fqdn) ++ ++ + class Service(object): + def __init__(self, service_name, service_desc=None, sstore=None, + fstore=None, api=api, realm_name=None, +@@ -522,7 +574,35 @@ class Service(object): + self.steps = [] + + def ldap_enable(self, name, fqdn, dm_password=None, ldap_suffix='', +- config=[]): ++ config=()): ++ """Legacy function, all services should use ldap_configure() ++ """ ++ warnings.warn( ++ "ldap_enable is deprecated, use ldap_configure instead.", ++ DeprecationWarning, ++ stacklevel=2 ++ ) ++ self._ldap_enable(ENABLED_SERVICE, name, fqdn, ldap_suffix, config) ++ ++ def ldap_configure(self, name, fqdn, dm_password=None, ldap_suffix='', ++ config=()): ++ """Create or modify service entry in cn=masters,cn=ipa,cn=etc ++ ++ Contrary to ldap_enable(), the method only sets ++ ipaConfigString=configuredService. ipaConfigString=enabledService ++ is set at the very end of the installation process, to ensure that ++ other machines see this master/replica after it is fully installed. ++ ++ To switch all configured services to enabled, use:: ++ ++ ipaserver.install.service.enable_services(api.env.host) ++ api.Command.dns_update_system_records() ++ """ ++ self._ldap_enable( ++ CONFIGURED_SERVICE, name, fqdn, ldap_suffix, config ++ ) ++ ++ def _ldap_enable(self, value, name, fqdn, ldap_suffix, config): + extra_config_opts = [ + ' '.join([u'startOrder', unicode(SERVICE_LIST[name][1])]) + ] +@@ -533,7 +613,7 @@ class Service(object): + set_service_entry_config( + name, + fqdn, +- [u'enabledService'], ++ [value], + ldap_suffix=ldap_suffix, + post_add_config=extra_config_opts) + +@@ -559,7 +639,7 @@ class Service(object): + + # case insensitive + for value in entry.get('ipaConfigString', []): +- if value.lower() == u'enabledservice': ++ if value.lower() == ENABLED_SERVICE: + entry['ipaConfigString'].remove(value) + break + +@@ -672,7 +752,7 @@ class SimpleServiceInstance(Service): + if self.gensvc_name == None: + self.enable() + else: +- self.ldap_enable(self.gensvc_name, self.fqdn, None, self.suffix) ++ self.ldap_configure(self.gensvc_name, self.fqdn, None, self.suffix) + + def is_installed(self): + return self.service.is_installed() +diff --git a/ipaserver/plugins/serverrole.py b/ipaserver/plugins/serverrole.py +index db88b3885c538c2800f6e4a1d649083859d43641..35b199387b4d3512d39f197d125c20571e23ad7a 100644 +--- a/ipaserver/plugins/serverrole.py ++++ b/ipaserver/plugins/serverrole.py +@@ -15,16 +15,21 @@ IPA server roles + """) + _(""" + Get status of roles (DNS server, CA, etc.) provided by IPA masters. + """) + _(""" ++The status of a role is either enabled, configured, or absent. ++""") + _(""" + EXAMPLES: + """) + _(""" + Show status of 'DNS server' role on a server: + ipa server-role-show ipa.example.com "DNS server" + """) + _(""" + Show status of all roles containing 'AD' on a server: +- ipa server-role-find --server ipa.example.com --role='AD' ++ ipa server-role-find --server ipa.example.com --role="AD trust controller" + """) + _(""" + Show status of all configured roles on a server: + ipa server-role-find ipa.example.com ++""") + _(""" ++ Show implicit IPA master role: ++ ipa server-role-find --include-master + """) + + +-- +2.17.1 + diff --git a/SOURCES/0067-replicainstall-DS-SSL-replica-install-pick-right-cer.patch b/SOURCES/0067-replicainstall-DS-SSL-replica-install-pick-right-cer.patch new file mode 100644 index 0000000..f4c2388 --- /dev/null +++ b/SOURCES/0067-replicainstall-DS-SSL-replica-install-pick-right-cer.patch @@ -0,0 +1,55 @@ +From ab325034a6d837cc51db2aa029498fa222e9d4e7 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Fri, 6 Jul 2018 09:26:19 -0400 +Subject: [PATCH] replicainstall: DS SSL replica install pick right certmonger + host + +Extend fix 0f31564b35aac250456233f98730811560eda664 to also move +the DS SSL setup so that the xmlrpc_uri is configured to point +to the remote master we are configuring against. + +https://pagure.io/freeipa/issue/7566 + +Signed-off-by: Rob Crittenden +Reviewed-By: Tibor Dudlak +--- + ipaserver/install/server/replicainstall.py | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index 59fec452c674b9941ce731748dd63985a08fefc0..a47412e39b9e2c603206c56a935de17321c71e91 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -1444,15 +1444,12 @@ def install(installer): + pkcs12_info=pkinit_pkcs12_info, + promote=promote) + +- # we now need to enable ssl on the ds +- ds.enable_ssl() +- + if promote: + # We need to point to the master when certmonger asks for +- # HTTP certificate. +- # During http installation, the HTTP/hostname principal is created +- # locally then the installer waits for the entry to appear on the +- # master selected for the installation. ++ # a DS or HTTP certificate. ++ # During http installation, the /hostname principal is ++ # created locally then the installer waits for the entry to appear ++ # on the master selected for the installation. + # In a later step, the installer requests a SSL certificate through + # Certmonger (and the op adds the principal if it does not exist yet). + # If xmlrpc_uri points to the soon-to-be replica, +@@ -1466,6 +1463,9 @@ def install(installer): + create_ipa_conf(fstore, config, ca_enabled, + master=config.master_host_name) + ++ # we now need to enable ssl on the ds ++ ds.enable_ssl() ++ + install_http( + config, + auto_redirect=not options.no_ui_redirect, +-- +2.17.1 + diff --git a/SOURCES/0068-Fix-race-condition-in-get_locations_records.patch b/SOURCES/0068-Fix-race-condition-in-get_locations_records.patch new file mode 100644 index 0000000..50317ad --- /dev/null +++ b/SOURCES/0068-Fix-race-condition-in-get_locations_records.patch @@ -0,0 +1,80 @@ +From 2347e7d3b9979533f471874919e1a39747bf4597 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Fri, 6 Jul 2018 21:47:32 +0200 +Subject: [PATCH] Fix race condition in get_locations_records() + +The method IPASystemRecords.get_locations_records() has a race condition. +The IPASystemRecords object creates a mapping of server names to server +data. get_locations_records() uses server_find() again to get a list of +servers, but then operates on the cached dict of server names. + +In parallel replication case, the second server_find() call in +get_locations_records() can return additional servers. Since the rest of +the code operates on the cached data, the method then fails with a KeyError. + +server_data is now an OrderedDict to keep same sorting as with +server_find(). + +Fixes: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Tibor Dudlak +--- + ipaserver/dns_data_management.py | 14 +++++--------- + 1 file changed, 5 insertions(+), 9 deletions(-) + +diff --git a/ipaserver/dns_data_management.py b/ipaserver/dns_data_management.py +index e5987b4bd7b43d3920e9da917258153e448206b7..c4f204c4941984d74e442c51ea206f11c05db2b9 100644 +--- a/ipaserver/dns_data_management.py ++++ b/ipaserver/dns_data_management.py +@@ -6,7 +6,7 @@ from __future__ import absolute_import + + import six + +-from collections import defaultdict ++from collections import defaultdict, OrderedDict + from dns import ( + rdata, + rdataclass, +@@ -68,7 +68,7 @@ class IPASystemRecords(object): + def __init__(self, api_instance, all_servers=False): + self.api_instance = api_instance + self.domain_abs = DNSName(self.api_instance.env.domain).make_absolute() +- self.servers_data = {} ++ self.servers_data = OrderedDict() + self.__init_data(all_servers=all_servers) + + def reload_data(self): +@@ -90,7 +90,7 @@ class IPASystemRecords(object): + return location + DNSName('_locations') + self.domain_abs + + def __init_data(self, all_servers=False): +- self.servers_data = {} ++ self.servers_data.clear() + + kwargs = dict(no_members=False) + if not all_servers: +@@ -325,7 +325,7 @@ class IPASystemRecords(object): + + zone_obj = zone.Zone(self.domain_abs, relativize=False) + if servers is None: +- servers = self.servers_data.keys() ++ servers = list(self.servers_data) + + for server in servers: + self._add_base_dns_records_for_server(zone_obj, server, +@@ -348,11 +348,7 @@ class IPASystemRecords(object): + """ + zone_obj = zone.Zone(self.domain_abs, relativize=False) + if servers is None: +- servers_result = self.api_instance.Command.server_find( +- pkey_only=True, +- servrole=u"IPA master", # only fully installed masters +- )['result'] +- servers = [s['cn'][0] for s in servers_result] ++ servers = list(self.servers_data) + + locations_result = self.api_instance.Command.location_find()['result'] + locations = [l['idnsname'][0] for l in locations_result] +-- +2.17.1 + diff --git a/SOURCES/0069-Auto-retry-failed-certmonger-requests.patch b/SOURCES/0069-Auto-retry-failed-certmonger-requests.patch new file mode 100644 index 0000000..f92e7f5 --- /dev/null +++ b/SOURCES/0069-Auto-retry-failed-certmonger-requests.patch @@ -0,0 +1,195 @@ +From 83445df2894426cdaf2f09f8bf2ac4c829922620 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Sun, 8 Jul 2018 11:53:58 +0200 +Subject: [PATCH] Auto-retry failed certmonger requests + +During parallel replica installation, a request sometimes fails with +CA_REJECTED or CA_UNREACHABLE. The error occur when the master is +either busy or some information haven't been replicated yet. Even +a stuck request can be recovered, e.g. when permission and group +information have been replicated. + +A new function request_and_retry_cert() automatically resubmits failing +requests until it times out. + +Fixes: https://pagure.io/freeipa/issue/7623 +Signed-off-by: Christian Heimes +Reviewed-By: Tibor Dudlak +--- + ipalib/install/certmonger.py | 64 ++++++++++++++++++++++++------- + ipaserver/install/cainstance.py | 4 +- + ipaserver/install/certs.py | 19 ++++++--- + ipaserver/install/dsinstance.py | 4 +- + ipaserver/install/httpinstance.py | 5 ++- + ipaserver/install/krbinstance.py | 4 +- + 6 files changed, 76 insertions(+), 24 deletions(-) + +diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py +index d2b782ddb0c746a3dfd96d0222bb31c6a960fdff..d915cbf7638c68f3ad5170b2878a706ad39def62 100644 +--- a/ipalib/install/certmonger.py ++++ b/ipalib/install/certmonger.py +@@ -302,20 +302,56 @@ def add_subject(request_id, subject): + def request_and_wait_for_cert( + certpath, subject, principal, nickname=None, passwd_fname=None, + dns=None, ca='IPA', profile=None, +- pre_command=None, post_command=None, storage='NSSDB', perms=None): +- """ +- Execute certmonger to request a server certificate. +- +- The method also waits for the certificate to be available. +- """ +- reqId = request_cert(certpath, subject, principal, nickname, +- passwd_fname, dns, ca, profile, +- pre_command, post_command, storage, perms) +- state = wait_for_request(reqId, api.env.startup_timeout) +- ca_error = get_request_value(reqId, 'ca-error') +- if state != 'MONITORING' or ca_error: +- raise RuntimeError("Certificate issuance failed ({})".format(state)) +- return reqId ++ pre_command=None, post_command=None, storage='NSSDB', perms=None, ++ resubmit_timeout=0): ++ """Request certificate, wait and possibly resubmit failing requests ++ ++ Submit a cert request to certmonger and wait until the request has ++ finished. ++ ++ With timeout, a failed request is resubmitted. During parallel replica ++ installation, a request sometimes fails with CA_REJECTED or ++ CA_UNREACHABLE. The error occurs when the master is either busy or some ++ information haven't been replicated yet. Even a stuck request can be ++ recovered, e.g. when permission and group information have been ++ replicated. ++ """ ++ req_id = request_cert( ++ certpath, subject, principal, nickname, passwd_fname, dns, ca, ++ profile, pre_command, post_command, storage, perms ++ ) ++ ++ deadline = time.time() + resubmit_timeout ++ while True: # until success, timeout, or error ++ state = wait_for_request(req_id, api.env.replication_wait_timeout) ++ ca_error = get_request_value(req_id, 'ca-error') ++ if state == 'MONITORING' and ca_error is None: ++ # we got a winner, exiting ++ root_logger.debug("Cert request %s was successful", req_id) ++ return req_id ++ ++ root_logger.debug( ++ "Cert request %s failed: %s (%s)", req_id, state, ca_error ++ ) ++ if state not in {'CA_REJECTED', 'CA_UNREACHABLE'}: ++ # probably unrecoverable error ++ root_logger.debug("Giving up on cert request %s", req_id) ++ break ++ elif not resubmit_timeout: ++ # no resubmit ++ break ++ elif time.time() > deadline: ++ root_logger.debug("Request %s reached resubmit dead line", req_id) ++ break ++ else: ++ # sleep and resubmit ++ root_logger.debug("Sleep and resubmit cert request %s", req_id) ++ time.sleep(10) ++ resubmit_request(req_id) ++ ++ raise RuntimeError( ++ "Certificate issuance failed ({}: {})".format(state, ca_error) ++ ) + + + def request_cert( +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index 62e9ad7de6f00eabb48f726a3931eb8acf0ba22b..e207911814e3553c5aa5310694170d3575337c55 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -863,7 +863,9 @@ class CAInstance(DogtagInstance): + profile='caServerCert', + pre_command='renew_ra_cert_pre', + post_command='renew_ra_cert', +- storage="FILE") ++ storage="FILE", ++ resubmit_timeout=api.env.replication_wait_timeout ++ ) + self.__set_ra_cert_perms() + + self.requestId = str(reqId) +diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py +index de96318db51b03f2515814d574cfebf1b242b6a6..5670d468bb1b168af7ada7b2d8924b8ec9f5d9c1 100644 +--- a/ipaserver/install/certs.py ++++ b/ipaserver/install/certs.py +@@ -663,12 +663,19 @@ class CertDB(object): + def export_pem_cert(self, nickname, location): + return self.nssdb.export_pem_cert(nickname, location) + +- def request_service_cert(self, nickname, principal, host): +- certmonger.request_and_wait_for_cert(certpath=self.secdir, +- nickname=nickname, +- principal=principal, +- subject=host, +- passwd_fname=self.passwd_fname) ++ def request_service_cert(self, nickname, principal, host, ++ resubmit_timeout=None): ++ if resubmit_timeout is None: ++ resubmit_timeout = api.env.replication_wait_timeout ++ return certmonger.request_and_wait_for_cert( ++ certpath=self.secdir, ++ storage='NSSDB', ++ nickname=nickname, ++ principal=principal, ++ subject=host, ++ passwd_fname=self.passwd_fname, ++ resubmit_timeout=resubmit_timeout ++ ) + + def is_ipa_issued_cert(self, api, nickname): + """ +diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py +index 7adaabd3c1280709150329003130f70233de37f4..8ec5cdd7a4a324a1a40dbe968defcc797db8f054 100644 +--- a/ipaserver/install/dsinstance.py ++++ b/ipaserver/install/dsinstance.py +@@ -847,7 +847,9 @@ class DsInstance(service.Service): + ca='IPA', + profile=dogtag.DEFAULT_PROFILE, + dns=[self.fqdn], +- post_command=cmd) ++ post_command=cmd, ++ resubmit_timeout=api.env.replication_wait_timeout ++ ) + finally: + if prev_helper is not None: + certmonger.modify_ca_helper('IPA', prev_helper) +diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py +index 2df51eaa77d3ee3246027a6bcbc4023dbad61160..2366698bb0d6a7c9cd481ba9d5568d320742f6bb 100644 +--- a/ipaserver/install/httpinstance.py ++++ b/ipaserver/install/httpinstance.py +@@ -450,7 +450,10 @@ class HTTPInstance(service.Service): + ca='IPA', + profile=dogtag.DEFAULT_PROFILE, + dns=[self.fqdn], +- post_command='restart_httpd') ++ post_command='restart_httpd', ++ storage='NSSDB', ++ resubmit_timeout=api.env.replication_wait_timeout ++ ) + finally: + if prev_helper is not None: + certmonger.modify_ca_helper('IPA', prev_helper) +diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py +index 4041d1b5fb3c3cf3db78b6cb282ce5f17793a0e3..62de118b7104fe7d72d2c1fd5577e9a76000c663 100644 +--- a/ipaserver/install/krbinstance.py ++++ b/ipaserver/install/krbinstance.py +@@ -436,7 +436,9 @@ class KrbInstance(service.Service): + storage='FILE', + profile=KDC_PROFILE, + post_command='renew_kdc_cert', +- perms=(0o644, 0o600)) ++ perms=(0o644, 0o600), ++ resubmit_timeout=api.env.replication_wait_timeout ++ ) + except dbus.DBusException as e: + # if the certificate is already tracked, ignore the error + name = e.get_dbus_name() +-- +2.17.1 + diff --git a/SOURCES/0070-Wait-for-client-certificates.patch b/SOURCES/0070-Wait-for-client-certificates.patch new file mode 100644 index 0000000..ebdea06 --- /dev/null +++ b/SOURCES/0070-Wait-for-client-certificates.patch @@ -0,0 +1,66 @@ +From 30b4300eb27ddeca50096687a9a4122e59d9b66d Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Mon, 9 Jul 2018 13:53:44 +0200 +Subject: [PATCH] Wait for client certificates + +ipa-client-install --request-cert now waits until certmonger has +provided a host certificate. In case of an error, ipa-client-install no +longer pretents to success but fails with an error code. + +The --request-cert option also ensures that certmonger is enabled and +running. + +See: Fixes: https://pagure.io/freeipa/issue/7623 +Signed-off-by: Christian Heimes +Reviewed-By: Tibor Dudlak +--- + ipaclient/install/client.py | 25 ++++++++++++++++++------- + 1 file changed, 18 insertions(+), 7 deletions(-) + +diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py +index c88061320c29faba25374ba71c53407de8e71db2..dbada726280d9a90293842370f303de6a77ceb01 100644 +--- a/ipaclient/install/client.py ++++ b/ipaclient/install/client.py +@@ -771,6 +771,7 @@ def configure_certmonger( + cmonger = services.knownservices.certmonger + try: + cmonger.enable() ++ cmonger.start() + except Exception as e: + root_logger.error( + "Failed to configure automatic startup of the %s daemon: %s", +@@ -782,14 +783,24 @@ def configure_certmonger( + subject = str(DN(('CN', hostname), subject_base)) + passwd_fname = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt') + try: +- certmonger.request_cert( ++ certmonger.request_and_wait_for_cert( + certpath=paths.IPA_NSSDB_DIR, +- nickname='Local IPA host', subject=subject, dns=[hostname], +- principal=principal, passwd_fname=passwd_fname) +- except Exception as ex: +- root_logger.error( +- "%s request for host certificate failed: %s", +- cmonger.service_name, ex) ++ storage='NSSDB', ++ nickname='Local IPA host', ++ subject=subject, ++ dns=[hostname], ++ principal=principal, ++ passwd_fname=passwd_fname, ++ resubmit_timeout=120, ++ ) ++ except Exception as e: ++ root_logger.exception("certmonger request failed") ++ raise ScriptError( ++ "{} request for host certificate failed: {}".format( ++ cmonger.service_name, e ++ ), ++ rval=CLIENT_INSTALL_ERROR ++ ) + + + def configure_sssd_conf( +-- +2.17.1 + diff --git a/SOURCES/0071-Fix-DNSSEC-install-regression.patch b/SOURCES/0071-Fix-DNSSEC-install-regression.patch new file mode 100644 index 0000000..6c438f5 --- /dev/null +++ b/SOURCES/0071-Fix-DNSSEC-install-regression.patch @@ -0,0 +1,68 @@ +From bd769b979c26c0c1ff723faca167cd83a27420d5 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 10 Jul 2018 12:51:36 +0200 +Subject: [PATCH] Fix DNSSEC install regression + +7284097eedef70dd556270732e6ab8e23501ce09 introduced a regression in +DNSSEC master installation. For standalone and replica installation, +services have to be enabled before checking bind config. + +Fixes: https://pagure.io/freeipa/issue/7635 +See: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Tibor Dudlak +Reviewed-By: Tibor Dudlak +--- + install/tools/ipa-dns-install | 5 +---- + ipaserver/install/dns.py | 5 +++++ + 2 files changed, 6 insertions(+), 4 deletions(-) + +diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install +index 04d1b140d2c79a0fa72d7df47d556643751bddb7..e7b6ea3a00468190b8dafe7c696f3bf212f81b9c 100755 +--- a/install/tools/ipa-dns-install ++++ b/install/tools/ipa-dns-install +@@ -36,7 +36,6 @@ from ipapython.config import IPAOptionParser + from ipapython.ipa_log_manager import standard_logging_setup, root_logger + + from ipaserver.install import dns as dns_installer +-from ipaserver.install import service + + log_file_name = paths.IPASERVER_INSTALL_LOG + +@@ -146,9 +145,7 @@ def main(): + + dns_installer.install_check(True, api, False, options, hostname=api.env.host) + dns_installer.install(True, False, options) +- # Enable configured services and update DNS SRV records +- service.enable_services(api.env.host) +- api.Command.dns_update_system_records() ++ # Services are enabled in dns_installer.install() + + # execute ipactl to refresh services status + ipautil.run(['ipactl', 'start', '--ignore-service-failures'], +diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py +index 1c1aac06a18fe3c1f63b5881c7887f6a4cfc9ac2..0046b78066cbb8e4f43e04af86da74118a5aa8c6 100644 +--- a/ipaserver/install/dns.py ++++ b/ipaserver/install/dns.py +@@ -43,6 +43,7 @@ from ipaserver.install import bindinstance + from ipaserver.install import dnskeysyncinstance + from ipaserver.install import odsexporterinstance + from ipaserver.install import opendnssecinstance ++from ipaserver.install import service + + if six.PY3: + unicode = str +@@ -355,6 +356,10 @@ def install(standalone, replica, options, api=api): + dnskeysyncd.start_dnskeysyncd() + bind.start_named() + ++ # Enable configured services for standalone check_global_configuration() ++ if standalone: ++ service.enable_services(api.env.host) ++ + # this must be done when bind is started and operational + bind.update_system_records() + +-- +2.17.1 + diff --git a/SOURCES/0072-Handle-races-in-replica-config.patch b/SOURCES/0072-Handle-races-in-replica-config.patch new file mode 100644 index 0000000..4deee3a --- /dev/null +++ b/SOURCES/0072-Handle-races-in-replica-config.patch @@ -0,0 +1,270 @@ +From 33f562bba3729bc596e07dc2805d78b80de55784 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 10 Jul 2018 14:03:28 +0200 +Subject: [PATCH] Handle races in replica config + +When multiple replicas are installed in parallel, two replicas may try +to create the cn=replica entry at the same time. This leads to a +conflict on one of the replicas. replica_config() and +ensure_replication_managers() now handle conflicts. + +ipaldap now maps TYPE_OR_VALUE_EXISTS to DuplicateEntry(). The type or +value exists exception is raised, when an attribute value or type is +already set. + +Fixes: https://pagure.io/freeipa/issue/7566 +Signed-off-by: Christian Heimes +Reviewed-By: Thierry Bordaz +Reviewed-By: Thierry Bordaz +--- + ipapython/ipaldap.py | 5 ++ + ipaserver/install/replication.py | 125 ++++++++++++++++++------------- + ipaserver/plugins/ldap2.py | 3 +- + 3 files changed, 81 insertions(+), 52 deletions(-) + +diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py +index 1b0aaddd63d92776448d1a7ae6c1bd26a02d5dcc..e2ff0626986aa20f3a2bc42721fba9fad3156498 100644 +--- a/ipapython/ipaldap.py ++++ b/ipapython/ipaldap.py +@@ -965,7 +965,12 @@ class LDAPClient(object): + except ldap.NO_SUCH_OBJECT: + raise errors.NotFound(reason=arg_desc or 'no such entry') + except ldap.ALREADY_EXISTS: ++ # entry already exists + raise errors.DuplicateEntry() ++ except ldap.TYPE_OR_VALUE_EXISTS: ++ # attribute type or attribute value already exists, usually only ++ # occurs, when two machines try to write at the same time. ++ raise errors.DuplicateEntry(message=unicode(desc)) + except ldap.CONSTRAINT_VIOLATION: + # This error gets thrown by the uniqueness plugin + _msg = 'Another entry with the same attribute value already exists' +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index c017764468674830670a817b3d815c5e2d78728e..ffda9a182f840317d96f1b3b914b38233022fb5b 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -34,7 +34,7 @@ from ipalib import api, errors + from ipalib.cli import textui + from ipapython.ipa_log_manager import root_logger + from ipalib.text import _ +-from ipapython import ipautil, ipaldap, kerberos ++from ipapython import ipautil, ipaldap + from ipapython.admintool import ScriptError + from ipapython.dn import DN + from ipaplatform.paths import paths +@@ -457,7 +457,7 @@ class ReplicationManager(object): + return DN(('cn', 'replica'), ('cn', self.db_suffix), + ('cn', 'mapping tree'), ('cn', 'config')) + +- def set_replica_binddngroup(self, r_conn, entry): ++ def _set_replica_binddngroup(self, r_conn, entry): + """ + Set nsds5replicabinddngroup attribute on remote master's replica entry. + Older masters (ipa < 3.3) may not support setting this attribute. In +@@ -472,11 +472,6 @@ class ReplicationManager(object): + mod.append((ldap.MOD_ADD, 'nsds5replicabinddngroup', + self.repl_man_group_dn)) + +- if 'nsds5replicabinddngroupcheckinterval' not in entry: +- mod.append( +- (ldap.MOD_ADD, +- 'nsds5replicabinddngroupcheckinterval', +- '60')) + if mod: + try: + r_conn.modify_s(entry.dn, mod) +@@ -484,49 +479,64 @@ class ReplicationManager(object): + root_logger.debug( + "nsds5replicabinddngroup attribute not supported on " + "remote master.") ++ except (ldap.ALREADY_EXISTS, ldap.CONSTRAINT_VIOLATION): ++ root_logger.debug("No update to %s necessary", entry.dn) + + def replica_config(self, conn, replica_id, replica_binddn): + assert isinstance(replica_binddn, DN) + dn = self.replica_dn() + assert isinstance(dn, DN) + ++ root_logger.debug("Add or update replica config %s", dn) + try: + entry = conn.get_entry(dn) + except errors.NotFound: +- pass +- else: +- binddns = entry.setdefault('nsDS5ReplicaBindDN', []) +- if replica_binddn not in {DN(m) for m in binddns}: +- # Add the new replication manager +- binddns.append(replica_binddn) +- for key, value in REPLICA_CREATION_SETTINGS.items(): +- entry[key] = value ++ # no entry, create new one ++ entry = conn.make_entry( ++ dn, ++ objectclass=["top", "nsds5replica", "extensibleobject"], ++ cn=["replica"], ++ nsds5replicaroot=[str(self.db_suffix)], ++ nsds5replicaid=[str(replica_id)], ++ nsds5replicatype=[self.get_replica_type()], ++ nsds5flags=["1"], ++ nsds5replicabinddn=[replica_binddn], ++ nsds5replicabinddngroup=[self.repl_man_group_dn], ++ nsds5replicalegacyconsumer=["off"], ++ **REPLICA_CREATION_SETTINGS ++ ) + try: +- conn.update_entry(entry) +- except errors.EmptyModlist: +- pass ++ conn.add_entry(entry) ++ except errors.DuplicateEntry: ++ root_logger.debug( ++ "Lost race against another replica, updating" ++ ) ++ # fetch entry that have been added by another replica ++ entry = conn.get_entry(dn) ++ else: ++ root_logger.debug("Added replica config %s", dn) ++ # added entry successfully ++ return entry + +- self.set_replica_binddngroup(conn, entry) ++ # either existing entry or lost race ++ binddns = entry.setdefault('nsDS5ReplicaBindDN', []) ++ if replica_binddn not in {DN(m) for m in binddns}: ++ # Add the new replication manager ++ binddns.append(replica_binddn) + +- # replication is already configured +- return ++ for key, value in REPLICA_CREATION_SETTINGS.items(): ++ entry[key] = value + +- replica_type = self.get_replica_type() ++ try: ++ conn.update_entry(entry) ++ except errors.EmptyModlist: ++ root_logger.debug("No update to %s necessary", entry.dn) ++ else: ++ root_logger.debug("Update replica config %s", entry.dn) + +- entry = conn.make_entry( +- dn, +- objectclass=["top", "nsds5replica", "extensibleobject"], +- cn=["replica"], +- nsds5replicaroot=[str(self.db_suffix)], +- nsds5replicaid=[str(replica_id)], +- nsds5replicatype=[replica_type], +- nsds5flags=["1"], +- nsds5replicabinddn=[replica_binddn], +- nsds5replicabinddngroup=[self.repl_man_group_dn], +- nsds5replicalegacyconsumer=["off"], +- **REPLICA_CREATION_SETTINGS +- ) +- conn.add_entry(entry) ++ self._set_replica_binddngroup(conn, entry) ++ ++ return entry + + def setup_changelog(self, conn): + ent = conn.get_entry( +@@ -686,7 +696,10 @@ class ReplicationManager(object): + uid=["passsync"], + userPassword=[password], + ) +- conn.add_entry(entry) ++ try: ++ conn.add_entry(entry) ++ except errors.DuplicateEntry: ++ pass + + # Add the user to the list of users allowed to bypass password policy + extop_dn = DN(('cn', 'ipa_pwd_extop'), ('cn', 'plugins'), ('cn', 'config')) +@@ -1644,7 +1657,10 @@ class ReplicationManager(object): + objectclass=['top', 'groupofnames'], + cn=['replication managers'] + ) +- conn.add_entry(entry) ++ try: ++ conn.add_entry(entry) ++ except errors.DuplicateEntry: ++ pass + + def ensure_replication_managers(self, conn, r_hostname): + """ +@@ -1654,23 +1670,24 @@ class ReplicationManager(object): + On FreeIPA 3.x masters lacking support for nsds5ReplicaBinddnGroup + attribute, add replica bind DN directly into the replica entry. + """ +- my_princ = kerberos.Principal((u'ldap', unicode(self.hostname)), +- realm=self.realm) +- remote_princ = kerberos.Principal((u'ldap', unicode(r_hostname)), +- realm=self.realm) +- services_dn = DN(api.env.container_service, api.env.basedn) +- +- mydn, remote_dn = tuple( +- DN(('krbprincipalname', unicode(p)), services_dn) for p in ( +- my_princ, remote_princ)) ++ my_dn = DN( ++ ('krbprincipalname', u'ldap/%s@%s' % (self.hostname, self.realm)), ++ api.env.container_service, ++ api.env.basedn ++ ) ++ remote_dn = DN( ++ ('krbprincipalname', u'ldap/%s@%s' % (r_hostname, self.realm)), ++ api.env.container_service, ++ api.env.basedn ++ ) + + try: + conn.get_entry(self.repl_man_group_dn) + except errors.NotFound: +- self._add_replica_bind_dn(conn, mydn) ++ self._add_replica_bind_dn(conn, my_dn) + self._add_replication_managers(conn) + +- self._add_dn_to_replication_managers(conn, mydn) ++ self._add_dn_to_replication_managers(conn, my_dn) + self._add_dn_to_replication_managers(conn, remote_dn) + + def add_temp_sasl_mapping(self, conn, r_hostname): +@@ -1690,7 +1707,10 @@ class ReplicationManager(object): + + entry = conn.get_entry(self.replica_dn()) + entry['nsDS5ReplicaBindDN'].append(replica_binddn) +- conn.update_entry(entry) ++ try: ++ conn.update_entry(entry) ++ except errors.EmptyModlist: ++ pass + + entry = conn.make_entry( + DN(('cn', 'Peer Master'), ('cn', 'mapping'), ('cn', 'sasl'), +@@ -1702,7 +1722,10 @@ class ReplicationManager(object): + nsSaslMapFilterTemplate=['(cn=&@%s)' % self.realm], + nsSaslMapPriority=['1'], + ) +- conn.add_entry(entry) ++ try: ++ conn.add_entry(entry) ++ except errors.DuplicateEntry: ++ pass + + def remove_temp_replication_user(self, conn, r_hostname): + """ +diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py +index 3b1e4da57a8e16e3d9b27eea24025de2caa53216..c0a0ed251f98cd06ccd13c873f38809d04d1b5d5 100644 +--- a/ipaserver/plugins/ldap2.py ++++ b/ipaserver/plugins/ldap2.py +@@ -422,7 +422,8 @@ class ldap2(CrudBackend, LDAPClient): + modlist = [(a, b, self.encode(c)) + for a, b, c in modlist] + self.conn.modify_s(str(group_dn), modlist) +- except errors.DatabaseError: ++ except errors.DuplicateEntry: ++ # TYPE_OR_VALUE_EXISTS + raise errors.AlreadyGroupMember() + + def remove_entry_from_group(self, dn, group_dn, member_attr='member'): +-- +2.17.1 + diff --git a/SOURCES/0073-Fix-KRA-replica-installation-from-CA-master.patch b/SOURCES/0073-Fix-KRA-replica-installation-from-CA-master.patch new file mode 100644 index 0000000..82559bb --- /dev/null +++ b/SOURCES/0073-Fix-KRA-replica-installation-from-CA-master.patch @@ -0,0 +1,46 @@ +From 7746bb807a15137c6dbc36f9d0ea0c3e9377ab8c Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 17 Jul 2018 08:53:39 +0200 +Subject: [PATCH] Fix KRA replica installation from CA master + +ipa-replica-install --kra-install can fail when the topology already has +a KRA, but replica is installed from a master with just CA. In that +case, Custodia may pick a machine that doesn't have the KRA auditing and +signing certs in its NSSDB. + +Example: + * master with CA + * replica1 with CA and KRA + * new replica gets installed from master + +The replica installer now always picks a KRA peer. + +The change fixes test scenario TestInstallWithCA1::()::test_replica2_ipa_dns_install + +Fixes: https://pagure.io/freeipa/issue/7518 +See: https://pagure.io/freeipa/issue/7008 +Signed-off-by: Christian Heimes +Reviewed-By: Rob Crittenden +--- + ipaserver/install/server/replicainstall.py | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index a47412e39b9e2c603206c56a935de17321c71e91..d8c55370d33d59efdf838f7ba01efedae7857406 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -1482,7 +1482,10 @@ def install(installer): + otpd.create_instance('OTPD', config.host_name, + ipautil.realm_to_suffix(config.realm_name)) + +- if ca_enabled: ++ if kra_enabled: ++ # A KRA peer always provides a CA, too. ++ mode = custodiainstance.CustodiaModes.KRA_PEER ++ elif ca_enabled: + mode = custodiainstance.CustodiaModes.CA_PEER + else: + mode = custodiainstance.CustodiaModes.MASTER_PEER +-- +2.17.1 + diff --git a/SOURCES/0074-DS-replication-settings-fix-regression-with-3.3-mast.patch b/SOURCES/0074-DS-replication-settings-fix-regression-with-3.3-mast.patch new file mode 100644 index 0000000..02929e0 --- /dev/null +++ b/SOURCES/0074-DS-replication-settings-fix-regression-with-3.3-mast.patch @@ -0,0 +1,57 @@ +From 35d8d61d53c9d99ae8a04365faa510535921ae48 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Tue, 21 Aug 2018 11:37:17 +0200 +Subject: [PATCH] DS replication settings: fix regression with <3.3 master + +Commit 811b0fdb4620938963f1a29d3fdd22257327562c introduced a regression +when configuring replication with a master < 3.3 +Even if 389-ds schema is extended with nsds5ReplicaReleaseTimeout, +nsds5ReplicaBackoffMax and nsDS5ReplicaBindDnGroupCheckInterval +attributes, it will return UNWILLING_TO_PERFORM when a mod +operation is performed on the cn=replica entry. + +This patch ignores the error and logs a debug msg. + +See: https://pagure.io/freeipa/issue/7617 +Reviewed-By: Christian Heimes +--- + ipaserver/install/replication.py | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index ffda9a182f840317d96f1b3b914b38233022fb5b..be738b249e36ca98fb2eea9e4730cefd0d30a3ee 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -21,6 +21,7 @@ from __future__ import print_function, absolute_import + + import itertools + ++import re + import six + import time + import datetime +@@ -598,7 +599,20 @@ class ReplicationManager(object): + r_conn.simple_bind(r_binddn, r_bindpw) + else: + r_conn.gssapi_bind() +- self._finalize_replica_settings(r_conn) ++ # If the remote server has 389-ds < 1.3, it does not ++ # support the attributes we are trying to set. ++ # Find which 389-ds is installed ++ rootdse = r_conn.get_entry(DN(''), ['vendorVersion']) ++ version = rootdse.single_value.get('vendorVersion') ++ mo = re.search(r'(\d+)\.(\d+)\.(\d+)[\.\d]*', version) ++ vendor_version = tuple(int(v) for v in mo.groups()) ++ if vendor_version >= (1, 3, 0): ++ # 389-ds understands the replication attributes, ++ # we can safely modify them ++ self._finalize_replica_settings(r_conn) ++ else: ++ root_logger.debug("replication attributes not supported " ++ "on remote master, skipping update.") + r_conn.close() + + def setup_chaining_backend(self, conn): +-- +2.17.1 + diff --git a/SOURCES/0075-Do-not-set-ca_host-when-setup-ca-is-used.patch b/SOURCES/0075-Do-not-set-ca_host-when-setup-ca-is-used.patch new file mode 100644 index 0000000..87363e1 --- /dev/null +++ b/SOURCES/0075-Do-not-set-ca_host-when-setup-ca-is-used.patch @@ -0,0 +1,86 @@ +From 37199834acaeee96136b096030198b2e9c8f16c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tibor=20Dudl=C3=A1k?= +Date: Thu, 26 Jul 2018 11:46:55 +0200 +Subject: [PATCH] Do not set ca_host when --setup-ca is used + +Setting ca_host caused replication failures on DL0 +because it was trying to connect to wrong CA host. +Trying to avoid corner-case in ipaserver/plugins/dogtag.py +when api.env.host nor api.env.ca_host had not CA configured +and there was ca_host set to api.env.ca_host variable. + +See: https://pagure.io/freeipa/issue/7566 +Resolves: https://pagure.io/freeipa/issue/7629 +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Alexander Bokovoy +Reviewed-By: Christian Heimes +Reviewed-By: Rob Crittenden +--- + ipaserver/install/cainstance.py | 24 ++++++++++++++++++++++ + ipaserver/install/server/replicainstall.py | 7 +++++-- + 2 files changed, 29 insertions(+), 2 deletions(-) + +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index e207911814e3553c5aa5310694170d3575337c55..cb8dc43096b8851d7554447f28c4537a35b47852 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -435,6 +435,11 @@ class CAInstance(DogtagInstance): + self.step("updating IPA configuration", update_ipa_conf) + self.step("enabling CA instance", self.__enable_instance) + if not promote: ++ if self.clone: ++ # DL0 workaround; see docstring of __expose_ca_in_ldap ++ self.step("exposing CA instance on LDAP", ++ self.__expose_ca_in_ldap) ++ + self.step("migrating certificate profiles to LDAP", + migrate_profiles_to_ldap) + self.step("importing IPA certificate profiles", +@@ -1222,6 +1227,25 @@ class CAInstance(DogtagInstance): + config = [] + self.ldap_configure('CA', self.fqdn, None, basedn, config) + ++ def __expose_ca_in_ldap(self): ++ """ ++ In a case when replica is created on DL0 we need to make ++ sure that query for CA service record of this replica in ++ ldap will succeed in time of installation. ++ This method is needed for sucessfull replica installation ++ on DL0 and should be removed alongside with code for DL0. ++ ++ To suppress deprecation warning message this method is ++ not invoking ldap_enable() but _ldap_enable() method. ++ """ ++ ++ basedn = ipautil.realm_to_suffix(self.realm) ++ if not self.clone: ++ config = ['caRenewalMaster'] ++ else: ++ config = [] ++ self._ldap_enable(u'enabledService', "CA", self.fqdn, basedn, config) ++ + def setup_lightweight_ca_key_retrieval(self): + if sysupgrade.get_upgrade_state('dogtag', 'setup_lwca_key_retrieval'): + return +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index d8c55370d33d59efdf838f7ba01efedae7857406..8a659a0fc2675df0a8fba1d3d7d8a629b376c93e 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -236,9 +236,12 @@ def create_ipa_conf(fstore, config, ca_enabled, master=None): + gopts.extend([ + ipaconf.setOption('enable_ra', 'True'), + ipaconf.setOption('ra_plugin', 'dogtag'), +- ipaconf.setOption('dogtag_version', '10'), +- ipaconf.setOption('ca_host', config.ca_host_name) ++ ipaconf.setOption('dogtag_version', '10') + ]) ++ ++ if not config.setup_ca: ++ gopts.append(ipaconf.setOption('ca_host', config.ca_host_name)) ++ + else: + gopts.extend([ + ipaconf.setOption('enable_ra', 'False'), +-- +2.17.1 + diff --git a/SOURCES/0076-Clear-next-field-when-returnining-list-elements-in-q.patch b/SOURCES/0076-Clear-next-field-when-returnining-list-elements-in-q.patch new file mode 100644 index 0000000..478b0c5 --- /dev/null +++ b/SOURCES/0076-Clear-next-field-when-returnining-list-elements-in-q.patch @@ -0,0 +1,64 @@ +From 81adf4db616f43704f0f1b8598149f6e2b123518 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Wed, 22 Aug 2018 15:32:16 -0400 +Subject: [PATCH] Clear next field when returnining list elements in queue.c + +The ipa-otpd code occasionally removes elements from one queue, +inspects and modifies them, and then inserts them into +another (possibly identical, possibly different) queue. When the next +pointer isn't cleared, this can result in element membership in both +queues, leading to double frees, or even self-referential elements, +causing infinite loops at traversal time. + +Rather than eliminating the pattern, make it safe by clearing the next +field any time an element enters or exits a queue. + +Related https://pagure.io/freeipa/issue/7262 + +Reviewed-By: Florence Blanc-Renaud +--- + daemons/ipa-otpd/queue.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/daemons/ipa-otpd/queue.c b/daemons/ipa-otpd/queue.c +index 9e29fb238d5c7a7395bcf3860ce7445c27ca98ac..2944b7ea0db6f49d0a3230b5f33c7a89281fd8c6 100644 +--- a/daemons/ipa-otpd/queue.c ++++ b/daemons/ipa-otpd/queue.c +@@ -111,6 +111,8 @@ void otpd_queue_push(struct otpd_queue *q, struct otpd_queue_item *item) + q->head = q->tail = item; + else + q->tail = q->tail->next = item; ++ ++ item->next = NULL; + } + + void otpd_queue_push_head(struct otpd_queue *q, struct otpd_queue_item *item) +@@ -118,6 +120,8 @@ void otpd_queue_push_head(struct otpd_queue *q, struct otpd_queue_item *item) + if (item == NULL) + return; + ++ item->next = NULL; ++ + if (q->head == NULL) + q->tail = q->head = item; + else { +@@ -145,6 +149,8 @@ struct otpd_queue_item *otpd_queue_pop(struct otpd_queue *q) + if (q->head == NULL) + q->tail = NULL; + ++ if (item != NULL) ++ item->next = NULL; + return item; + } + +@@ -160,6 +166,7 @@ struct otpd_queue_item *otpd_queue_pop_msgid(struct otpd_queue *q, int msgid) + *prev = item->next; + if (q->head == NULL) + q->tail = NULL; ++ item->next = NULL; + return item; + } + } +-- +2.17.1 + diff --git a/SOURCES/0077-Add-cmocka-unit-tests-for-ipa-otpd-queue-code.patch b/SOURCES/0077-Add-cmocka-unit-tests-for-ipa-otpd-queue-code.patch new file mode 100644 index 0000000..189974e --- /dev/null +++ b/SOURCES/0077-Add-cmocka-unit-tests-for-ipa-otpd-queue-code.patch @@ -0,0 +1,253 @@ +From 1f47c51e592563339c13256ea02b8fa768b8fde2 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Thu, 30 Aug 2018 15:34:31 -0400 +Subject: [PATCH] Add cmocka unit tests for ipa otpd queue code + +Reviewed-By: Florence Blanc-Renaud +--- + daemons/ipa-otpd/Makefile.am | 12 + + .../ipa-otpd/ipa_otpd_queue_cmocka_tests.c | 212 ++++++++++++++++++ + 2 files changed, 224 insertions(+) + create mode 100644 daemons/ipa-otpd/ipa_otpd_queue_cmocka_tests.c + +diff --git a/daemons/ipa-otpd/Makefile.am b/daemons/ipa-otpd/Makefile.am +index 9ba6237566634cfa6a1e84c545ee1fc9cf2dc4f6..9ccc450a5e00db90b698ff9b25a496d09f0ff6e5 100644 +--- a/daemons/ipa-otpd/Makefile.am ++++ b/daemons/ipa-otpd/Makefile.am +@@ -20,3 +20,15 @@ ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c + $< > $@ + + CLEANFILES = $(systemdsystemunit_DATA) ++ ++TESTS = ++check_PROGRAMS = ++ ++if HAVE_CMOCKA ++TESTS += queue_tests ++check_PROGRAMS += queue_tests ++endif ++ ++queue_tests_SOURCES = ipa_otpd_queue_cmocka_tests.c queue.c ++queue_tests_CFLAGS = $(CMOCKA_CFLAGS) ++queue_tests_LDADD = $(CMOCKA_LIBS) +diff --git a/daemons/ipa-otpd/ipa_otpd_queue_cmocka_tests.c b/daemons/ipa-otpd/ipa_otpd_queue_cmocka_tests.c +new file mode 100644 +index 0000000000000000000000000000000000000000..068431e6475bb74b01acbcab22115915dec1a278 +--- /dev/null ++++ b/daemons/ipa-otpd/ipa_otpd_queue_cmocka_tests.c +@@ -0,0 +1,212 @@ ++/* ++ * FreeIPA 2FA companion daemon - internal queue tests ++ * ++ * Author: Robbie Harwood ++ * ++ * Copyright (C) 2018 Robbie Harwood, Red Hat ++ * see file 'COPYING' for use and warranty information ++ * ++ * This program is free software you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the Free ++ * Software Foundation, either version 3 of the License, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program. If not, see . ++ */ ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "internal.h" ++ ++/* Bypass otpd queue allocation/freeing to avoid calling into LDAP and ++ * krad. No effort is made to make the types match. */ ++static struct otpd_queue_item *new_elt(int id) ++{ ++ krb5_error_code ret; ++ struct otpd_queue_item *e = NULL; ++ ++ ret = otpd_queue_item_new(NULL, &e); ++ assert_int_equal(ret, 0); ++ assert_ptr_not_equal(e, NULL); ++ ++ e->msgid = id; ++ return e; ++} ++static void free_elt(struct otpd_queue_item **e) ++{ ++ assert_ptr_not_equal(e, NULL); ++ free(*e); ++ *e = NULL; ++} ++static void free_elts(struct otpd_queue *q) ++{ ++ assert_ptr_not_equal(q, NULL); ++ for (struct otpd_queue_item *e = otpd_queue_pop(q); e != NULL; ++ e = otpd_queue_pop(q)) ++ free_elt(&e); ++} ++#define otpd_queue_item_new new_elt ++#define otpd_queue_item_free free_elt ++#define otpd_queue_free_items free_elts ++ ++static void assert_elt_equal(struct otpd_queue_item *e1, ++ struct otpd_queue_item *e2) ++{ ++ if (e1 == NULL && e2 == NULL) ++ return; ++ assert_ptr_not_equal(e1, NULL); ++ assert_ptr_not_equal(e2, NULL); ++ assert_int_equal(e1->msgid, e2->msgid); ++} ++ ++static void test_single_insert() ++{ ++ struct otpd_queue q = { NULL }; ++ struct otpd_queue_item *ein, *eout; ++ ++ ein = new_elt(0); ++ otpd_queue_push(&q, ein); ++ ++ eout = otpd_queue_peek(&q); ++ assert_elt_equal(ein, eout); ++ ++ eout = otpd_queue_pop(&q); ++ assert_elt_equal(ein, eout); ++ free_elt(&eout); ++ ++ eout = otpd_queue_pop(&q); ++ assert_ptr_equal(eout, NULL); ++ ++ free_elts(&q); ++} ++ ++static void test_jump_insert() ++{ ++ struct otpd_queue q = { NULL }; ++ struct otpd_queue_item *echeck; ++ ++ for (int i = 0; i < 3; i++) { ++ struct otpd_queue_item *e = new_elt(i); ++ otpd_queue_push_head(&q, e); ++ ++ echeck = otpd_queue_peek(&q); ++ assert_elt_equal(e, echeck); ++ } ++ ++ free_elts(&q); ++} ++ ++static void test_garbage_insert() ++{ ++ struct otpd_queue q = { NULL }; ++ struct otpd_queue_item *e, *g; ++ ++ g = new_elt(0); ++ g->next = g; ++ otpd_queue_push(&q, g); ++ ++ e = otpd_queue_peek(&q); ++ assert_ptr_equal(e->next, NULL); ++ ++ free_elts(&q); ++} ++ ++static void test_removal() ++{ ++ struct otpd_queue q = { NULL }; ++ ++ for (int i = 0; i < 3; i++) { ++ struct otpd_queue_item *e = new_elt(i); ++ otpd_queue_push(&q, e); ++ } ++ for (int i = 0; i < 3; i++) { ++ struct otpd_queue_item *e = otpd_queue_pop(&q); ++ assert_ptr_not_equal(e, NULL); ++ assert_ptr_equal(e->next, NULL); ++ assert_int_equal(e->msgid, i); ++ free_elt(&e); ++ } ++} ++ ++static void pick_id(struct otpd_queue *q, int msgid) ++{ ++ struct otpd_queue_item *e; ++ ++ e = otpd_queue_pop_msgid(q, msgid); ++ assert_int_equal(e->msgid, msgid); ++ assert_ptr_equal(e->next, NULL); ++ free_elt(&e); ++ e = otpd_queue_pop_msgid(q, msgid); ++ assert_ptr_equal(e, NULL); ++} ++static void test_pick_removal() ++{ ++ struct otpd_queue q = { NULL }; ++ ++ for (int i = 0; i < 4; i++) { ++ struct otpd_queue_item *e = new_elt(i); ++ otpd_queue_push(&q, e); ++ } ++ ++ pick_id(&q, 0); /* first */ ++ pick_id(&q, 2); /* middle */ ++ pick_id(&q, 3); /* last */ ++ pick_id(&q, 1); /* singleton */ ++ ++ free_elts(&q); ++} ++ ++static void test_iter() ++{ ++ krb5_error_code ret; ++ struct otpd_queue q = { NULL }; ++ const struct otpd_queue *queues[3]; ++ struct otpd_queue_iter *iter = NULL; ++ const krad_packet *p = NULL; ++ ++ for (ptrdiff_t i = 1; i <= 3; i++) { ++ struct otpd_queue_item *e = new_elt(i); ++ e->req = (void *)i; ++ otpd_queue_push(&q, e); ++ } ++ ++ queues[0] = &q; ++ queues[1] = &q; ++ queues[2] = NULL; ++ ret = otpd_queue_iter_new(queues, &iter); ++ assert_int_equal(ret, 0); ++ assert_ptr_not_equal(iter, NULL); ++ ++ for (ptrdiff_t i = 0; i < 6; i++) { ++ p = otpd_queue_iter_func(iter, FALSE); ++ assert_ptr_equal(p, (void *) (i % 3 + 1)); ++ } ++ p = otpd_queue_iter_func(iter, FALSE); ++ assert_ptr_equal(p, NULL); ++ ++ free_elts(&q); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ const struct CMUnitTest tests[] = { ++ cmocka_unit_test(test_single_insert), ++ cmocka_unit_test(test_jump_insert), ++ cmocka_unit_test(test_garbage_insert), ++ cmocka_unit_test(test_removal), ++ cmocka_unit_test(test_pick_removal), ++ cmocka_unit_test(test_iter), ++ }; ++ ++ return cmocka_run_group_tests(tests, NULL, NULL); ++} +-- +2.17.1 + diff --git a/SOURCES/0078-ipa-replica-install-fix-pkinit-setup.patch b/SOURCES/0078-ipa-replica-install-fix-pkinit-setup.patch new file mode 100644 index 0000000..a1470c8 --- /dev/null +++ b/SOURCES/0078-ipa-replica-install-fix-pkinit-setup.patch @@ -0,0 +1,42 @@ +From 5aa15e551fe9b76c3e89862fe36b661825a807ce Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Tue, 4 Sep 2018 14:15:50 +0200 +Subject: [PATCH] ipa-replica-install: fix pkinit setup + +commit 7284097 (Delay enabling services until end of installer) +introduced a regression in replica installation. +When the replica requests a cert for PKINIT, a check is done +to ensure that the hostname corresponds to a machine with a +KDC service enabled (ipaconfigstring attribute of +cn=KDC,cn=,cn=masters,cn=ipa,cn=etc,$BASEDN must contain +'enabledService'). +With the commit mentioned above, the service is set to enabled only +at the end of the installation. + +The fix makes a less strict check, ensuring that 'enabledService' +or 'configuredService' is in ipaconfigstring. + +Fixes: https://pagure.io/freeipa/issue/7566 +Reviewed-By: Christian Heimes +Reviewed-By: Christian Heimes +--- + ipaserver/plugins/cert.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py +index 501fc9015468c864215cfb604de37cdf6d805e52..60ad140da3b1483ffe5918239a489030ccc7fe96 100644 +--- a/ipaserver/plugins/cert.py ++++ b/ipaserver/plugins/cert.py +@@ -230,7 +230,8 @@ def ca_kdc_check(api_instance, hostname): + + ipaconfigstring = {val.lower() for val in kdc_entry['ipaConfigString']} + +- if 'enabledservice' not in ipaconfigstring: ++ if 'enabledservice' not in ipaconfigstring \ ++ and 'configuredservice' not in ipaconfigstring: + raise errors.NotFound() + + except errors.NotFound: +-- +2.17.1 + diff --git a/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch b/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch index c62497d..f3a7582 100644 --- a/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch +++ b/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch @@ -1,4 +1,4 @@ -From 0efc9d0a7e4c04d44eee4c408d426f91dc76be9c Mon Sep 17 00:00:00 2001 +From dfbf91e6463955a66e30eacf2bfbae4af586ef0a Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Tue, 14 Mar 2017 15:48:07 +0000 Subject: [PATCH] Change branding to IPA and Identity Management @@ -47,12 +47,12 @@ Subject: [PATCH] Change branding to IPA and Identity Management install/tools/man/ipactl.8 | 2 +- install/ui/css/patternfly.css | 2 +- install/ui/index.html | 2 +- - install/ui/less/brand.less | 103 ++++++++++++++--------------- - install/ui/less/patternfly.less | 48 ++++++++++++++ + install/ui/less/brand.less | 103 ++++++++++----------- + install/ui/less/patternfly.less | 48 ++++++++++ install/ui/reset_password.html | 2 +- install/ui/src/freeipa/widgets/App.js | 2 +- install/ui/sync_otp.html | 2 +- - ipaserver/advise/plugins/legacy_clients.py | 8 +-- + ipaserver/advise/plugins/legacy_clients.py | 8 +- ipaserver/install/dns.py | 2 +- ipaserver/install/ipa_kra_install.py | 4 +- ipaserver/install/server/install.py | 2 +- @@ -303,7 +303,7 @@ index 19e3e6832bea774244bc949ce44a27f5ebebaed0..2a92ec6aebeb0932b58dd092ba4188e1 You may place your schema files in a subdirectory too, the code that loads schema files processes recursively all subdirectories of schema.d. diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install -index 1484598adba5b1237f00cc55e95167d45a6b40d7..5aa9233ff3c8b811fa96eba8b34b0b02fb9fc90e 100755 +index 4258f489873b4095a6672e20f2ac5f2858b71982..e6231b46e74d43af9895e51366e57df4e7a79e20 100755 --- a/install/tools/ipa-adtrust-install +++ b/install/tools/ipa-adtrust-install @@ -139,11 +139,11 @@ def main(): @@ -963,10 +963,10 @@ index 7439f584aaa092590ec5ac4ebba408419d337cbe..950c2fe4c40b87aa0eeac52880dc6ce9 'are all Red Hat based platforms.') diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py -index 1c1aac06a18fe3c1f63b5881c7887f6a4cfc9ac2..c4c1bbb5ab073e4a9b357fd12018fd7e1ed5e817 100644 +index 0046b78066cbb8e4f43e04af86da74118a5aa8c6..8446b192da287a903001bff7c5022b8ccf5dfcde 100644 --- a/ipaserver/install/dns.py +++ b/ipaserver/install/dns.py -@@ -146,7 +146,7 @@ def install_check(standalone, api, replica, options, hostname): +@@ -147,7 +147,7 @@ def install_check(standalone, api, replica, options, hostname): if standalone: print("==============================================================================") @@ -976,10 +976,10 @@ index 1c1aac06a18fe3c1f63b5881c7887f6a4cfc9ac2..c4c1bbb5ab073e4a9b357fd12018fd7e print("This includes:") print(" * Configure DNS (bind)") diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py -index 3e08f4da94651b49876e1427daddbd957f0027ae..c2af9f8462d776d452e4b90d9779f38c47040baf 100644 +index 3a639ac20f101293edd7449f8846a451469e2297..436d44f5d3ee6f5317585319903435d4941d3059 100644 --- a/ipaserver/install/ipa_kra_install.py +++ b/ipaserver/install/ipa_kra_install.py -@@ -86,7 +86,7 @@ class KRAInstall(admintool.AdminTool): +@@ -87,7 +87,7 @@ class KRAInstall(admintool.AdminTool): if options.uninstall: sys.exit( 'ERROR: Standalone KRA uninstallation was removed in ' @@ -988,7 +988,7 @@ index 3e08f4da94651b49876e1427daddbd957f0027ae..c2af9f8462d776d452e4b90d9779f38c 'issues.') else: return KRAInstaller -@@ -97,7 +97,7 @@ class KRAInstaller(KRAInstall): +@@ -98,7 +98,7 @@ class KRAInstaller(KRAInstall): INSTALLER_START_MESSAGE = ''' =================================================================== @@ -998,7 +998,7 @@ index 3e08f4da94651b49876e1427daddbd957f0027ae..c2af9f8462d776d452e4b90d9779f38c ''' diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py -index 422474fa915b4876530f304ef9424f6b31cf26cc..8f2cca4f6096fc4093f180c84da7888e8710765a 100644 +index dcdd9aabb746c4973b3f73934d94225503728f0b..66c982058b7a7deecc25d17873d94d6be8d03b19 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -373,7 +373,7 @@ def install_check(installer): @@ -1011,10 +1011,10 @@ index 422474fa915b4876530f304ef9424f6b31cf26cc..8f2cca4f6096fc4093f180c84da7888e print("This includes:") if setup_ca: diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py -index 42e4615ad2dc1f604f5d8d14f8e57e3e4674bcb9..7726b782f36f884e098ca4a5f5a136f7742e5e97 100644 +index 8a659a0fc2675df0a8fba1d3d7d8a629b376c93e..9a0432bc45dbb6e3a767a93913e44c4baedbfa8a 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py -@@ -614,7 +614,7 @@ def check_domain_level_is_supported(current): +@@ -615,7 +615,7 @@ def check_domain_level_is_supported(current): above_upper_bound = current > constants.MAX_DOMAIN_LEVEL if under_lower_bound or above_upper_bound: @@ -1046,5 +1046,5 @@ index 28c3f21f113fd14160abd518663f2d582f8653fd..f70943576d861ce7b3a8bc4c29e9ded8 """) + _(""" To enable the binddn run the following command to set the password: -- -2.14.3 +2.17.1 diff --git a/SOURCES/1002-Package-copy-schema-to-ca.py.patch b/SOURCES/1002-Package-copy-schema-to-ca.py.patch index f3a2a0c..23d8eb6 100644 --- a/SOURCES/1002-Package-copy-schema-to-ca.py.patch +++ b/SOURCES/1002-Package-copy-schema-to-ca.py.patch @@ -1,4 +1,4 @@ -From 154c041a95be7e6cdbcc8e116ff0fc2a785d730f Mon Sep 17 00:00:00 2001 +From b0982e138e6ad0830b1903d8f58c0d0a25d3ace4 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Tue, 14 Mar 2017 16:07:15 +0000 Subject: [PATCH] Package copy-schema-to-ca.py @@ -22,10 +22,10 @@ index 80ae98c5515f64a8df8d981ad5e91b05c84e31c1..86189d56ded05dac695d3a7a19f726e1 %{_usr}/share/ipa/*.uldif %{_usr}/share/ipa/*.template diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py -index 20635eae22268ff72de73b8b9c430050114bb45b..190f8d851b3567638f8a41e2a4ce10e40e2ec1af 100644 +index cb8dc43096b8851d7554447f28c4537a35b47852..810ce882920490abc6fee4458082fe6611f317f5 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py -@@ -1321,9 +1321,11 @@ def replica_ca_install_check(config, promote): +@@ -1355,9 +1355,11 @@ def replica_ca_install_check(config, promote): else: root_logger.critical( 'The master CA directory server does not have necessary schema. ' @@ -40,5 +40,5 @@ index 20635eae22268ff72de73b8b9c430050114bb45b..190f8d851b3567638f8a41e2a4ce10e4 -- -2.14.3 +2.17.1 diff --git a/SOURCES/1003-Revert-Increased-mod_wsgi-socket-timeout.patch b/SOURCES/1003-Revert-Increased-mod_wsgi-socket-timeout.patch index 8a9757c..4c78b44 100644 --- a/SOURCES/1003-Revert-Increased-mod_wsgi-socket-timeout.patch +++ b/SOURCES/1003-Revert-Increased-mod_wsgi-socket-timeout.patch @@ -1,4 +1,4 @@ -From c96e727aff6be11c1d90c7b693b77f36d6deeaac Mon Sep 17 00:00:00 2001 +From 7a7d19b33cd669275323b70c6a295c991846f3be Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Wed, 22 Jun 2016 13:53:46 +0200 Subject: [PATCH] Revert "Increased mod_wsgi socket-timeout" @@ -11,18 +11,18 @@ https://bugzilla.redhat.com/show_bug.cgi?id=1348948 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf -index 01bf9a4f97fc0cf197c0ad12743affa597b54911..d3389ec5d34dba6429986b1c2a6dfb21d2e93e4a 100644 +index 34ced2ab9d91ae174a42a580e1c4f9436c1a8c3b..39f67abed6a0895c4a66878193602d237efbf550 100644 --- a/install/conf/ipa.conf +++ b/install/conf/ipa.conf @@ -52,7 +52,7 @@ WSGISocketPrefix /run/httpd/wsgi # Configure mod_wsgi handler for /ipa - WSGIDaemonProcess ipa processes=2 threads=1 maximum-requests=500 \ + WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \ - user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 + user=ipaapi group=ipaapi display-name=%{GROUP} WSGIImportScript /usr/share/ipa/wsgi.py process-group=ipa application-group=ipa WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py WSGIScriptReloading Off -- -2.14.3 +2.17.1 diff --git a/SOURCES/1004-Remove-csrgen.patch b/SOURCES/1004-Remove-csrgen.patch index 0ebfcc1..be8adac 100644 --- a/SOURCES/1004-Remove-csrgen.patch +++ b/SOURCES/1004-Remove-csrgen.patch @@ -1,4 +1,4 @@ -From 4f3522e47d1a1c26dc8283c6aa4fc72a33d7133e Mon Sep 17 00:00:00 2001 +From 5d84d3cd7e14b7d34fa1b756c027877be7562f5e Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Thu, 16 Mar 2017 09:44:21 +0000 Subject: [PATCH] Remove csrgen @@ -19,35 +19,35 @@ This reverts commits: https://bugzilla.redhat.com/show_bug.cgi?id=1432630 --- - freeipa.spec.in | 18 - - ipaclient/csrgen.py | 398 --------------------- - ipaclient/csrgen/profiles/caIPAserviceCert.json | 15 - - ipaclient/csrgen/profiles/userCert.json | 15 - - ipaclient/csrgen/rules/dataDNS.json | 15 - - ipaclient/csrgen/rules/dataEmail.json | 15 - - ipaclient/csrgen/rules/dataHostCN.json | 15 - - ipaclient/csrgen/rules/dataSubjectBase.json | 15 - - ipaclient/csrgen/rules/dataUsernameCN.json | 15 - - ipaclient/csrgen/rules/syntaxSAN.json | 15 - - ipaclient/csrgen/rules/syntaxSubject.json | 16 - - ipaclient/csrgen/templates/certutil_base.tmpl | 11 - - ipaclient/csrgen/templates/openssl_base.tmpl | 35 -- - ipaclient/csrgen/templates/openssl_macros.tmpl | 29 -- - ipaclient/plugins/cert.py | 96 +---- - ipaclient/plugins/csrgen.py | 120 ------- - ipaclient/setup.py | 7 - - ipalib/errors.py | 28 -- - ipatests/setup.py | 2 - - ipatests/test_ipaclient/__init__.py | 7 - - .../data/test_csrgen/profiles/profile.json | 8 - - .../data/test_csrgen/rules/basic.json | 12 - - .../data/test_csrgen/rules/options.json | 18 - - .../scripts/caIPAserviceCert_certutil.sh | 11 - - .../scripts/caIPAserviceCert_openssl.sh | 34 -- - .../data/test_csrgen/scripts/userCert_certutil.sh | 11 - - .../data/test_csrgen/scripts/userCert_openssl.sh | 34 -- - .../data/test_csrgen/templates/identity_base.tmpl | 1 - - ipatests/test_ipaclient/test_csrgen.py | 298 --------------- + freeipa.spec.in | 18 - + ipaclient/csrgen.py | 398 ------------------ + .../csrgen/profiles/caIPAserviceCert.json | 15 - + ipaclient/csrgen/profiles/userCert.json | 15 - + ipaclient/csrgen/rules/dataDNS.json | 15 - + ipaclient/csrgen/rules/dataEmail.json | 15 - + ipaclient/csrgen/rules/dataHostCN.json | 15 - + ipaclient/csrgen/rules/dataSubjectBase.json | 15 - + ipaclient/csrgen/rules/dataUsernameCN.json | 15 - + ipaclient/csrgen/rules/syntaxSAN.json | 15 - + ipaclient/csrgen/rules/syntaxSubject.json | 16 - + ipaclient/csrgen/templates/certutil_base.tmpl | 11 - + ipaclient/csrgen/templates/openssl_base.tmpl | 35 -- + .../csrgen/templates/openssl_macros.tmpl | 29 -- + ipaclient/plugins/cert.py | 96 +---- + ipaclient/plugins/csrgen.py | 120 ------ + ipaclient/setup.py | 7 - + ipalib/errors.py | 28 -- + ipatests/setup.py | 2 - + ipatests/test_ipaclient/__init__.py | 7 - + .../data/test_csrgen/profiles/profile.json | 8 - + .../data/test_csrgen/rules/basic.json | 12 - + .../data/test_csrgen/rules/options.json | 18 - + .../scripts/caIPAserviceCert_certutil.sh | 11 - + .../scripts/caIPAserviceCert_openssl.sh | 34 -- + .../test_csrgen/scripts/userCert_certutil.sh | 11 - + .../test_csrgen/scripts/userCert_openssl.sh | 34 -- + .../test_csrgen/templates/identity_base.tmpl | 1 - + ipatests/test_ipaclient/test_csrgen.py | 298 ------------- 29 files changed, 1 insertion(+), 1313 deletions(-) delete mode 100644 ipaclient/csrgen.py delete mode 100644 ipaclient/csrgen/profiles/caIPAserviceCert.json @@ -1649,5 +1649,5 @@ index 556f8e096976387d24057084c06d53bcb9998a69..00000000000000000000000000000000 - _script = generator.csr_script( - principal, {}, 'example', 'identity') -- -2.14.3 +2.17.1 diff --git a/SOURCES/ipa-centos-branding.patch b/SOURCES/ipa-centos-branding.patch deleted file mode 100644 index 673cd2f..0000000 --- a/SOURCES/ipa-centos-branding.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 99efecaf87dc1fc9517efaff441a6a7ce46444eb Mon Sep 17 00:00:00 2001 -From: Jim Perrin -Date: Wed, 11 Mar 2015 10:37:03 -0500 -Subject: [PATCH] update for new ntp server method - ---- - ipaplatform/base/paths.py | 1 + - ipaserver/install/ntpinstance.py | 2 ++ - 2 files changed, 3 insertions(+) - -diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py -index af50262..5090062 100644 ---- a/ipaplatform/base/paths.py -+++ b/ipaplatform/base/paths.py -@@ -99,6 +99,7 @@ class BasePathNamespace(object): - PKI_TOMCAT_ALIAS_DIR = "/etc/pki/pki-tomcat/alias/" - PKI_TOMCAT_PASSWORD_CONF = "/etc/pki/pki-tomcat/password.conf" - ETC_REDHAT_RELEASE = "/etc/redhat-release" -+ ETC_CENTOS_RELEASE = "/etc/centos-release" - RESOLV_CONF = "/etc/resolv.conf" - SAMBA_KEYTAB = "/etc/samba/samba.keytab" - SMB_CONF = "/etc/samba/smb.conf" -diff --git a/ipaserver/install/ntpinstance.py b/ipaserver/install/ntpinstance.py -index c653525..4b0578b 100644 ---- a/ipaserver/install/ntpinstance.py -+++ b/ipaserver/install/ntpinstance.py -@@ -44,6 +44,8 @@ class NTPInstance(service.Service): - os = "" - if ipautil.file_exists(paths.ETC_FEDORA_RELEASE): - os = "fedora" -+ elif ipautil.file_exists(paths.ETC_CENTOS_RELEASE): -+ os = "centos" - elif ipautil.file_exists(paths.ETC_REDHAT_RELEASE): - os = "rhel" - --- -1.8.3.1 - diff --git a/SPECS/ipa.spec b/SPECS/ipa.spec index f73c4f6..73cdb03 100644 --- a/SPECS/ipa.spec +++ b/SPECS/ipa.spec @@ -72,7 +72,7 @@ Name: ipa Version: %{IPA_VERSION} -Release: 10%{?dist}.3 +Release: 10%{?dist}.4.4 Summary: The Identity, Policy and Audit system Group: System Environment/Base @@ -80,10 +80,10 @@ License: GPLv3+ URL: http://www.freeipa.org/ Source0: https://releases.pagure.org/freeipa/freeipa-%{version}.tar.gz # RHEL spec file only: START: Change branding to IPA and Identity Management -#Source1: header-logo.png -#Source2: login-screen-background.jpg -#Source3: login-screen-logo.png -#Source4: product-name.png +Source1: header-logo.png +Source2: login-screen-background.jpg +Source3: login-screen-logo.png +Source4: product-name.png # RHEL spec file only: END: Change branding to IPA and Identity Management BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -137,6 +137,35 @@ Patch0046: 0046-Fix-upgrade-update_replica_config-in-single-master-m.patch Patch0047: 0047-Use-single-Custodia-instance-in-installers.patch Patch0048: 0048-Don-t-try-to-backup-CS.cfg-during-upgrade-if-CA-is-n.patch Patch0049: 0049-Use-one-Custodia-peer-to-retrieve-all-secrets.patch +Patch0050: 0050-Fix-elements-not-being-removed-in-otpd_queue_pop_msg.patch +Patch0051: 0051-Tune-DS-replication-settings.patch +Patch0052: 0052-In-IPA-4.4-when-updating-userpassword-with-ldapmodif.patch +Patch0053: 0053-Tests-add-integration-test-for-password-changes-by-d.patch +Patch0054: 0054-Check-if-replication-agreement-exist-before-enable-d.patch +Patch0055: 0055-Fix-ipa-restore-create-var-run-ipa-files.patch +Patch0056: 0056-Sort-and-shuffle-SRV-record-by-priority-and-weight.patch +Patch0057: 0057-Always-set-ca_host-when-installing-replica.patch +Patch0058: 0058-Improve-and-fix-timeout-bug-in-wait_for_entry.patch +Patch0059: 0059-Use-common-replication-wait-timeout-of-5min.patch +Patch0060: 0060-Fix-replication-races-in-Dogtag-admin-code.patch +Patch0061: 0061-Increase-WSGI-process-count-to-5-on-64bit.patch +Patch0062: 0062-Use-4-WSGI-workers-on-64bit-systems.patch +Patch0063: 0063-Catch-ACIError-instead-of-invalid-credentials.patch +Patch0064: 0064-Query-for-server-role-IPA-master.patch +Patch0065: 0065-Only-create-DNS-SRV-records-for-ready-server.patch +Patch0066: 0066-Delay-enabling-services-until-end-of-installer.patch +Patch0067: 0067-replicainstall-DS-SSL-replica-install-pick-right-cer.patch +Patch0068: 0068-Fix-race-condition-in-get_locations_records.patch +Patch0069: 0069-Auto-retry-failed-certmonger-requests.patch +Patch0070: 0070-Wait-for-client-certificates.patch +Patch0071: 0071-Fix-DNSSEC-install-regression.patch +Patch0072: 0072-Handle-races-in-replica-config.patch +Patch0073: 0073-Fix-KRA-replica-installation-from-CA-master.patch +Patch0074: 0074-DS-replication-settings-fix-regression-with-3.3-mast.patch +Patch0075: 0075-Do-not-set-ca_host-when-setup-ca-is-used.patch +Patch0076: 0076-Clear-next-field-when-returnining-list-elements-in-q.patch +Patch0077: 0077-Add-cmocka-unit-tests-for-ipa-otpd-queue-code.patch +Patch0078: 0078-ipa-replica-install-fix-pkinit-setup.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch Patch1002: 1002-Package-copy-schema-to-ca.py.patch Patch1003: 1003-Revert-Increased-mod_wsgi-socket-timeout.patch @@ -166,7 +195,6 @@ BuildRequires: python-setuptools BuildRequires: python3-devel BuildRequires: python3-setuptools %endif # with_python3 -# %{_unitdir}, %{_tmpfilesdir} BuildRequires: systemd # systemd-tmpfiles which is executed from make install requires apache user BuildRequires: httpd @@ -942,10 +970,10 @@ cp -r %{_builddir}/freeipa-%{version} %{_builddir}/freeipa-%{version}-python3 %endif # with_python3 # RHEL spec file only: START: Change branding to IPA and Identity Management -#cp %SOURCE1 install/ui/images/header-logo.png -#cp %SOURCE2 install/ui/images/login-screen-background.jpg -#cp %SOURCE3 install/ui/images/login-screen-logo.png -#cp %SOURCE4 install/ui/images/product-name.png +cp %SOURCE1 install/ui/images/header-logo.png +cp %SOURCE2 install/ui/images/login-screen-background.jpg +cp %SOURCE3 install/ui/images/login-screen-logo.png +cp %SOURCE4 install/ui/images/product-name.png # RHEL spec file only: END: Change branding to IPA and Identity Management @@ -969,8 +997,7 @@ find \ %configure --with-vendor-suffix=-%{release} \ %{enable_server_option} \ %{with_ipatests_option} \ - %{linter_options} \ - --with-ipaplatform=rhel + %{linter_options} %make_build @@ -991,8 +1018,7 @@ find \ %configure --with-vendor-suffix=-%{release} \ %{enable_server_option} \ %{with_ipatests_option} \ - %{linter_options} \ - --with-ipaplatform=rhel + %{linter_options} popd %endif # with_python3 @@ -1700,8 +1726,55 @@ fi %changelog -* Tue Jun 26 2018 CentOS Sources - 4.5.4-10.el7.centos.3 -- Roll in CentOS Branding +* Fri Sep 7 2018 Florence Blanc-Renaud - 4.5.4-10.el7_5.4.4 +- Resolves: #1626379 PKINIT configuration did not succeed message is received during Replica-install + - ipa-replica-install: fix pkinit setup + +* Mon Sep 3 2018 Florence Blanc-Renaud - 4.5.4-10.el7.4.3 +- Resolves: #1624811 ipa-otpd: fix potential double-free and infinite loop in queue code + - Clear next field when returnining list elements in queue.c + - Add cmocka unit tests for ipa otpd queue code + +* Thu Aug 30 2018 Florence Blanc-Renaud - 4.5.4-10.el7.4.2 +- Resolves: #1623673 SRV lookup doesn't correctly sort results + - Sort and shuffle SRV record by priority and weight +- Resolves: #1623679 Installation of replica against a specific master + - Always set ca_host when installing replica + - Query for server role IPA master + - Only create DNS SRV records for ready server + - Delay enabling services until end of installer + - replicainstall: DS SSL replica install pick right certmonger host + - Fix race condition in get_locations_records() + - Fix DNSSEC install regression + - Handle races in replica config + - Fix KRA replica installation from CA master + - Do not set ca_host when --setup-ca is used +- Resolves: #1623676 Replication races in DogtagInstance.setup_admin + - Improve and fix timeout bug in wait_for_entry() + - Use common replication wait timeout of 5min + - Fix replication races in Dogtag admin code + - Catch ACIError instead of invalid credentials +- Resolves: #1623680 Increase WSGI worker process count + - Increase WSGI process count to 5 on 64bit + - Use 4 WSGI workers on 64bit systems +- Resolves: #1623669 ipa-replica-install defines nsds5replicabinddngroup before the group contains the DN of the replication manager + - DS replication settings: fix regression with <3.3 master +- Resolves: #1623668 Replica install: certmonger sometimes fails + - Auto-retry failed certmonger requests + - Wait for client certificates + +* Thu Aug 16 2018 Florence Blanc-Renaud - 4.5.4-10.el7.4 +- Resolves: #1615983 ipa-restore fails on newly installed system. + - Fix ipa-restore: create /var/run/ipa files +- Resolves: #1615966 ipa-replica-manage re-initialize TypeError: 'NoneType' object does not support item assignment + - Check if replication agreement exist before enable/disable it +- Resolves: #1615984 ldapmodify userPassword reflects on krblastpwdchange on RHEL6 but not RHEL7 + - Tests: add integration test for password changes by dir mgr + - In IPA 4.4 when updating userpassword with ldapmodify does not update krbPasswordExpiration nor krbLastPwdChange +- Resolves: #1615893 nsds5ReplicaReleaseTimeout should be set by default. + - Tune DS replication settings +- Resolves: #1615964 Authn/TOTP defined users periodically prompt for just password credentials to access resources + - Fix elements not being removed in otpd_queue_pop_msgid() * Mon Jun 11 2018 Rob Crittenden - 4.5.4-10.el7.3 - Resolves: #1579190 Improve Custodia client and key distribution handling