2f4199
From d3c09a6de06d8ae19d05c0b96cb9c2a5b789b472 Mon Sep 17 00:00:00 2001
2f4199
From: Christian Heimes <cheimes@redhat.com>
2f4199
Date: Thu, 7 Jun 2018 18:17:20 +0200
2f4199
Subject: [PATCH] Use one Custodia peer to retrieve all secrets
2f4199
2f4199
Fix 994f71ac8a1bb7ba6bc9caf0f6e4f59af44ad9c4 was incomplete. Under some
2f4199
circumstancs the DM hash and CA keys were still retrieved from two different
2f4199
machines.
2f4199
2f4199
Custodia client now uses a single remote to upload keys and download all
2f4199
secrets.
2f4199
2f4199
Fixes: https://pagure.io/freeipa/issue/7518
2f4199
Signed-off-by: Christian Heimes <cheimes@redhat.com>
2f4199
Reviewed-By: Simo Sorce <ssorce@redhat.com>
2f4199
---
2f4199
 ipaserver/install/ca.py                    |  1 -
2f4199
 ipaserver/install/cainstance.py            |  2 +-
2f4199
 ipaserver/install/custodiainstance.py      | 74 +++++++++++++++++-------------
2f4199
 ipaserver/install/kra.py                   |  1 -
2f4199
 ipaserver/install/server/install.py        |  4 +-
2f4199
 ipaserver/install/server/replicainstall.py |  2 +-
2f4199
 6 files changed, 46 insertions(+), 38 deletions(-)
2f4199
2f4199
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
2f4199
index 2ac4382..1c7d375 100644
2f4199
--- a/ipaserver/install/ca.py
2f4199
+++ b/ipaserver/install/ca.py
2f4199
@@ -235,7 +235,6 @@ def install_step_0(standalone, replica_config, options, custodia):
2f4199
         cafile = os.path.join(replica_config.dir, 'cacert.p12')
2f4199
         if options.promote:
2f4199
             custodia.get_ca_keys(
2f4199
-                replica_config.ca_host_name,
2f4199
                 cafile,
2f4199
                 replica_config.dirman_password)
2f4199
 
2f4199
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
2f4199
index eefc30b..cc0dbf3 100644
2f4199
--- a/ipaserver/install/cainstance.py
2f4199
+++ b/ipaserver/install/cainstance.py
2f4199
@@ -712,7 +712,7 @@ class CAInstance(DogtagInstance):
2f4199
         self.configure_agent_renewal()
2f4199
 
2f4199
     def __import_ra_key(self):
2f4199
-        self._custodia.import_ra_key(self.master_host)
2f4199
+        self._custodia.import_ra_key()
2f4199
         self.__set_ra_cert_perms()
2f4199
 
2f4199
         self.configure_agent_renewal()
2f4199
diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py
2f4199
index 262ae58..ada8d03 100644
2f4199
--- a/ipaserver/install/custodiainstance.py
2f4199
+++ b/ipaserver/install/custodiainstance.py
2f4199
@@ -71,41 +71,45 @@ def get_custodia_instance(config, mode):
2f4199
         if mode == CustodiaModes.CA_PEER:
2f4199
             # In case we install replica with CA, prefer CA host as source for
2f4199
             # all Custodia secret material.
2f4199
-            custodia_master = config.ca_host_name
2f4199
+            custodia_peer = config.ca_host_name
2f4199
         elif mode == CustodiaModes.KRA_PEER:
2f4199
-            custodia_master = config.kra_host_name
2f4199
+            custodia_peer = config.kra_host_name
2f4199
         elif mode == CustodiaModes.MASTER_PEER:
2f4199
-            custodia_master = config.master_host_name
2f4199
+            custodia_peer = config.master_host_name
2f4199
         elif mode == CustodiaModes.STANDALONE:
2f4199
-            custodia_master = None
2f4199
+            custodia_peer = None
2f4199
     else:
2f4199
-        custodia_master = None
2f4199
+        custodia_peer = None
2f4199
 
2f4199
-    if custodia_master is None:
2f4199
+    if custodia_peer is None:
2f4199
         # use ldapi with local dirsrv instance
2f4199
         root_logger.info("Custodia uses LDAPI.")
2f4199
-        ldap_uri = None
2f4199
     else:
2f4199
-        root_logger.info("Custodia uses '%s' as master peer.",
2f4199
-                         custodia_master)
2f4199
-        ldap_uri = 'ldap://{}'.format(custodia_master)
2f4199
+        root_logger.info("Custodia uses '%s' as master peer.", custodia_peer)
2f4199
 
2f4199
     return CustodiaInstance(
2f4199
         host_name=config.host_name,
2f4199
         realm=config.realm_name,
2f4199
-        ldap_uri=ldap_uri
2f4199
+        custodia_peer=custodia_peer
2f4199
     )
2f4199
 
2f4199
 
2f4199
 class CustodiaInstance(SimpleServiceInstance):
2f4199
-    def __init__(self, host_name=None, realm=None, ldap_uri=None):
2f4199
+    def __init__(self, host_name=None, realm=None, custodia_peer=None):
2f4199
         super(CustodiaInstance, self).__init__("ipa-custodia")
2f4199
         self.config_file = paths.IPA_CUSTODIA_CONF
2f4199
         self.server_keys = paths.IPA_CUSTODIA_KEYS
2f4199
-        self.ldap_uri = ldap_uri
2f4199
+        self.custodia_peer = custodia_peer
2f4199
         self.fqdn = host_name
2f4199
         self.realm = realm
2f4199
 
2f4199
+    @property
2f4199
+    def ldap_uri(self):
2f4199
+        if self.custodia_peer is None:
2f4199
+            return installutils.realm_to_ldapi_uri(self.realm)
2f4199
+        else:
2f4199
+            return "ldap://{}".format(self.custodia_peer)
2f4199
+
2f4199
     def __config_file(self):
2f4199
         template_file = os.path.basename(self.config_file) + '.template'
2f4199
         template = os.path.join(paths.USR_SHARE_IPA_DIR, template_file)
2f4199
@@ -125,7 +129,7 @@ class CustodiaInstance(SimpleServiceInstance):
2f4199
             ipautil.flush_sync(f)
2f4199
 
2f4199
     def create_instance(self):
2f4199
-        if self.ldap_uri is None or self.ldap_uri.startswith('ldapi://'):
2f4199
+        if self.ldap_uri.startswith('ldapi://'):
2f4199
             # local case, ensure container exists
2f4199
             self.step("Making sure custodia container exists",
2f4199
                       self.__create_container)
2f4199
@@ -174,25 +178,24 @@ class CustodiaInstance(SimpleServiceInstance):
2f4199
         updater = ldapupdate.LDAPUpdate(sub_dict=sub_dict)
2f4199
         updater.update([os.path.join(paths.UPDATES_DIR, '73-custodia.update')])
2f4199
 
2f4199
-    def import_ra_key(self, master_host_name):
2f4199
-        cli = self._get_custodia_client(server=master_host_name)
2f4199
+    def import_ra_key(self):
2f4199
+        cli = self._get_custodia_client()
2f4199
         # please note that ipaCert part has to stay here for historical
2f4199
         # reasons (old servers expect you to ask for ra/ipaCert during
2f4199
         # replication as they store the RA agent cert in an NSS database
2f4199
         # with this nickname)
2f4199
         cli.fetch_key('ra/ipaCert')
2f4199
 
2f4199
-    def import_dm_password(self, master_host_name):
2f4199
-        cli = self._get_custodia_client(server=master_host_name)
2f4199
+    def import_dm_password(self):
2f4199
+        cli = self._get_custodia_client()
2f4199
         cli.fetch_key('dm/DMHash')
2f4199
 
2f4199
-    def _wait_keys(self, host, timeout=300):
2f4199
-        ldap_uri = 'ldap://%s' % host
2f4199
+    def _wait_keys(self, timeout=300):
2f4199
         deadline = int(time.time()) + timeout
2f4199
         root_logger.info("Waiting up to %s seconds to see our keys "
2f4199
-                         "appear on host: %s", timeout, host)
2f4199
+                         "appear on host %s", timeout, self.ldap_uri)
2f4199
 
2f4199
-        konn = KEMLdap(ldap_uri)
2f4199
+        konn = KEMLdap(self.ldap_uri)
2f4199
         saved_e = None
2f4199
         while True:
2f4199
             try:
2f4199
@@ -202,8 +205,11 @@ class CustodiaInstance(SimpleServiceInstance):
2f4199
                 if saved_e is None:
2f4199
                     # FIXME: Change once there's better way to show this
2f4199
                     # message in installer output,
2f4199
-                    print("  Waiting for keys to appear on host: {}, please "
2f4199
-                          "wait until this has completed.".format(host))
2f4199
+                    print(
2f4199
+                        "  Waiting for keys to appear on host: {}, please "
2f4199
+                        "wait until this has completed.".format(
2f4199
+                            self.ldap_uri)
2f4199
+                    )
2f4199
                 # log only once for the same error
2f4199
                 if not isinstance(e, type(saved_e)):
2f4199
                     root_logger.debug(
2f4199
@@ -213,23 +219,25 @@ class CustodiaInstance(SimpleServiceInstance):
2f4199
                     raise RuntimeError("Timed out trying to obtain keys.")
2f4199
                 time.sleep(1)
2f4199
 
2f4199
-    def _get_custodia_client(self, server):
2f4199
+    def _get_custodia_client(self):
2f4199
+        if self.custodia_peer is None:
2f4199
+            raise ValueError("Can't replicate secrets without Custodia peer")
2f4199
         # Before we attempt to fetch keys from this host, make sure our public
2f4199
         # keys have been replicated there.
2f4199
-        self._wait_keys(server)
2f4199
+        self._wait_keys()
2f4199
 
2f4199
         return CustodiaClient(
2f4199
             client_service='host@{}'.format(self.fqdn),
2f4199
             keyfile=self.server_keys, keytab=paths.KRB5_KEYTAB,
2f4199
-            server=server, realm=self.realm
2f4199
+            server=self.custodia_peer, realm=self.realm
2f4199
         )
2f4199
 
2f4199
-    def _get_keys(self, ca_host, cacerts_file, cacerts_pwd, data):
2f4199
+    def _get_keys(self, cacerts_file, cacerts_pwd, data):
2f4199
         # Fetch all needed certs one by one, then combine them in a single
2f4199
         # PKCS12 file
2f4199
         prefix = data['prefix']
2f4199
         certlist = data['list']
2f4199
-        cli = self._get_custodia_client(server=ca_host)
2f4199
+        cli = self._get_custodia_client()
2f4199
 
2f4199
         # Temporary nssdb
2f4199
         tmpnssdir = tempfile.mkdtemp(dir=paths.TMP)
2f4199
@@ -272,23 +280,23 @@ class CustodiaInstance(SimpleServiceInstance):
2f4199
         finally:
2f4199
             shutil.rmtree(tmpnssdir)
2f4199
 
2f4199
-    def get_ca_keys(self, ca_host, cacerts_file, cacerts_pwd):
2f4199
+    def get_ca_keys(self, cacerts_file, cacerts_pwd):
2f4199
         certlist = ['caSigningCert cert-pki-ca',
2f4199
                     'ocspSigningCert cert-pki-ca',
2f4199
                     'auditSigningCert cert-pki-ca',
2f4199
                     'subsystemCert cert-pki-ca']
2f4199
         data = {'prefix': 'ca',
2f4199
                 'list': certlist}
2f4199
-        self._get_keys(ca_host, cacerts_file, cacerts_pwd, data)
2f4199
+        self._get_keys(cacerts_file, cacerts_pwd, data)
2f4199
 
2f4199
-    def get_kra_keys(self, ca_host, cacerts_file, cacerts_pwd):
2f4199
+    def get_kra_keys(self, cacerts_file, cacerts_pwd):
2f4199
         certlist = ['auditSigningCert cert-pki-kra',
2f4199
                     'storageCert cert-pki-kra',
2f4199
                     'subsystemCert cert-pki-ca',
2f4199
                     'transportCert cert-pki-kra']
2f4199
         data = {'prefix': 'ca',
2f4199
                 'list': certlist}
2f4199
-        self._get_keys(ca_host, cacerts_file, cacerts_pwd, data)
2f4199
+        self._get_keys(cacerts_file, cacerts_pwd, data)
2f4199
 
2f4199
     def __start(self):
2f4199
         super(CustodiaInstance, self).__start()
2f4199
diff --git a/ipaserver/install/kra.py b/ipaserver/install/kra.py
2f4199
index 3333970..d8773b9 100644
2f4199
--- a/ipaserver/install/kra.py
2f4199
+++ b/ipaserver/install/kra.py
2f4199
@@ -91,7 +91,6 @@ def install(api, replica_config, options, custodia):
2f4199
                     paths.KRB5_KEYTAB,
2f4199
                     ccache)
2f4199
                 custodia.get_kra_keys(
2f4199
-                    replica_config.kra_host_name,
2f4199
                     krafile,
2f4199
                     replica_config.dirman_password)
2f4199
         else:
2f4199
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
2f4199
index f4338ed..3651cde 100644
2f4199
--- a/ipaserver/install/server/install.py
2f4199
+++ b/ipaserver/install/server/install.py
2f4199
@@ -1085,7 +1085,9 @@ def uninstall(installer):
2f4199
     dsinstance.DsInstance(fstore=fstore).uninstall()
2f4199
     if _server_trust_ad_installed:
2f4199
         adtrustinstance.ADTRUSTInstance(fstore).uninstall()
2f4199
-    custodiainstance.CustodiaInstance().uninstall()
2f4199
+    # realm isn't used, but IPAKEMKeys parses /etc/ipa/default.conf
2f4199
+    # otherwise, see https://pagure.io/freeipa/issue/7474 .
2f4199
+    custodiainstance.CustodiaInstance(realm='REALM.INVALID').uninstall()
2f4199
     otpdinstance.OtpdInstance().uninstall()
2f4199
     tasks.restore_hostname(fstore, sstore)
2f4199
     fstore.restore_all_files()
2f4199
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
2f4199
index ef61590..b9cd518 100644
2f4199
--- a/ipaserver/install/server/replicainstall.py
2f4199
+++ b/ipaserver/install/server/replicainstall.py
2f4199
@@ -1512,7 +1512,7 @@ def install(installer):
2f4199
     krb.restart()
2f4199
 
2f4199
     if promote:
2f4199
-        custodia.import_dm_password(config.master_host_name)
2f4199
+        custodia.import_dm_password()
2f4199
         promote_sssd(config.host_name)
2f4199
         promote_openldap_conf(config.host_name, config.master_host_name)
2f4199
 
2f4199
-- 
2f4199
2.9.3
2f4199