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 <rharwood@redhat.com>
+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 <abokovoy@redhat.com>
+Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <tbordaz@redhat.com>
+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 <frenaud@redhat.com>
+---
+ 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 <flo@redhat.com>
+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 <tbordaz@redhat.com>
+---
+ 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 <fbarreto@redhat.com>
+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 <rcritten@redhat.com>
+Reviewed-By: Christian Heimes <cheimes@redhat.com>
+---
+ 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 <flo@redhat.com>
+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 <cheimes@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ 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 <http://www.gnu.org/licenses/>.
+ #
+ 
++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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <http://www.gnu.org/licenses/>.
+ #
+ 
+-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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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=[<type 'unicode'>, <type 'NoneType'>])
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
+---
+ 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 <rcritten@redhat.com>
+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 <rcritten@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <service>/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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
+Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
+---
+ 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 <cheimes@redhat.com>
+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 <cheimes@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ 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 <flo@redhat.com>
+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 <cheimes@redhat.com>
+---
+ 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?= <tdudlak@redhat.com>
+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 <frenaud@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Christian Heimes <cheimes@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ 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 <rharwood@redhat.com>
+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 <frenaud@redhat.com>
+---
+ 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 <rharwood@redhat.com>
+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 <frenaud@redhat.com>
+---
+ 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 <rharwood@redhat.com>
++ *
++ * 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 <http://www.gnu.org/licenses/>.
++ */
++
++#include <setjmp.h>
++#include <stdarg.h>
++#include <stddef.h>
++
++#include <cmocka.h>
++
++#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 <flo@redhat.com>
+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=<hostname>,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 <cheimes@redhat.com>
+Reviewed-By: Christian Heimes <cheimes@redhat.com>
+---
+ 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 <jcholast@redhat.com>
 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 <jcholast@redhat.com>
 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 <jcholast@redhat.com>
 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 <jcholast@redhat.com>
 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 <jperrin@centos.org>
-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 a8ae8c5..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,11 +137,39 @@ 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
 Patch1004:      1004-Remove-csrgen.patch
-Patch1005:      ipa-centos-branding.patch
 # RHEL spec file only: END
 
 BuildRequires: libtool, automake, autoconf
@@ -167,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
@@ -943,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
 
 
@@ -1699,8 +1726,55 @@ fi
 
 
 %changelog
-* Tue Jun 26 2018 CentOS Sources <bugs@centos.org> - 4.5.4-10.el7.centos.3
-- Roll in CentOS Branding
+* Fri Sep 7 2018 Florence Blanc-Renaud <frenaud@redhat.com> - 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 <frenaud@redhat.com> - 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 <frenaud@redhat.com> - 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 <frenaud@redhat.com> - 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 <rcritten@redhat.com> - 4.5.4-10.el7.3
 - Resolves: #1579190 Improve Custodia client and key distribution handling