ac7d03
From 948ab2a1f44676769e1e8c9be439606d05672c9b Mon Sep 17 00:00:00 2001
ac7d03
From: Simo Sorce <simo@redhat.com>
ac7d03
Date: Fri, 31 Mar 2017 11:22:45 -0400
ac7d03
Subject: [PATCH] Make sure remote hosts have our keys
ac7d03
ac7d03
In complex replication setups a replica may try to obtain CA keys from a
ac7d03
host that is not the master we initially create the keys against.
ac7d03
In this case race conditions may happen due to replication. So we need
ac7d03
to make sure the server we are contacting to get the CA keys has our
ac7d03
keys in LDAP. We do this by waiting to positively fetch our encryption
ac7d03
public key (the last one we create) from the target host LDAP server.
ac7d03
ac7d03
Fixes: https://pagure.io/freeipa/issue/6838
ac7d03
ac7d03
Signed-off-by: Simo Sorce <simo@redhat.com>
ac7d03
Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
ac7d03
Reviewed-By: Christian Heimes <cheimes@redhat.com>
ac7d03
---
ac7d03
 ipaserver/install/custodiainstance.py | 28 +++++++++++++++++++++++++++-
ac7d03
 ipaserver/secrets/kem.py              | 12 ++++++++++++
ac7d03
 2 files changed, 39 insertions(+), 1 deletion(-)
ac7d03
ac7d03
diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py
ac7d03
index 6a613923163bccd1b59e0c3b3672905715a8de7c..390576bc0c0edfb7d8f8895eca9df30079526aa8 100644
ac7d03
--- a/ipaserver/install/custodiainstance.py
ac7d03
+++ b/ipaserver/install/custodiainstance.py
ac7d03
@@ -1,6 +1,6 @@
ac7d03
 # Copyright (C) 2015 FreeIPa Project Contributors, see 'COPYING' for license.
ac7d03
 
ac7d03
-from ipaserver.secrets.kem import IPAKEMKeys
ac7d03
+from ipaserver.secrets.kem import IPAKEMKeys, KEMLdap
ac7d03
 from ipaserver.secrets.client import CustodiaClient
ac7d03
 from ipaplatform.paths import paths
ac7d03
 from ipaplatform.constants import constants
ac7d03
@@ -18,6 +18,7 @@ import shutil
ac7d03
 import os
ac7d03
 import stat
ac7d03
 import tempfile
ac7d03
+import time
ac7d03
 import pwd
ac7d03
 
ac7d03
 
ac7d03
@@ -122,6 +123,27 @@ class CustodiaInstance(SimpleServiceInstance):
ac7d03
         cli = self.__CustodiaClient(server=master_host_name)
ac7d03
         cli.fetch_key('dm/DMHash')
ac7d03
 
ac7d03
+    def __wait_keys(self, host, timeout=300):
ac7d03
+        ldap_uri = 'ldap://%s' % host
ac7d03
+        deadline = int(time.time()) + timeout
ac7d03
+        root_logger.info("Waiting up to {} seconds to see our keys "
ac7d03
+                         "appear on host: {}".format(timeout, host))
ac7d03
+
ac7d03
+        konn = KEMLdap(ldap_uri)
ac7d03
+        saved_e = None
ac7d03
+        while True:
ac7d03
+            try:
ac7d03
+                return konn.check_host_keys(self.fqdn)
ac7d03
+            except Exception as e:
ac7d03
+                # log only once for the same error
ac7d03
+                if not isinstance(e, type(saved_e)):
ac7d03
+                    root_logger.debug(
ac7d03
+                        "Transient error getting keys: '{err}'".format(err=e))
ac7d03
+                    saved_e = e
ac7d03
+                if int(time.time()) > deadline:
ac7d03
+                    raise RuntimeError("Timed out trying to obtain keys.")
ac7d03
+                time.sleep(1)
ac7d03
+
ac7d03
     def __get_keys(self, ca_host, cacerts_file, cacerts_pwd, data):
ac7d03
         # Fecth all needed certs one by one, then combine them in a single
ac7d03
         # p12 file
ac7d03
@@ -129,6 +151,10 @@ class CustodiaInstance(SimpleServiceInstance):
ac7d03
         prefix = data['prefix']
ac7d03
         certlist = data['list']
ac7d03
 
ac7d03
+        # Before we attempt to fetch keys from this host, make sure our public
ac7d03
+        # keys have been replicated there.
ac7d03
+        self.__wait_keys(ca_host)
ac7d03
+
ac7d03
         cli = self.__CustodiaClient(server=ca_host)
ac7d03
 
ac7d03
         # Temporary nssdb
ac7d03
diff --git a/ipaserver/secrets/kem.py b/ipaserver/secrets/kem.py
ac7d03
index 28fb4d31b35fc96c77ddd3f09cb3927efb4000fa..c1991c6b2ae00ed7147b2ec18389e463784b9f98 100644
ac7d03
--- a/ipaserver/secrets/kem.py
ac7d03
+++ b/ipaserver/secrets/kem.py
ac7d03
@@ -24,6 +24,7 @@ import ldap
ac7d03
 
ac7d03
 IPA_REL_BASE_DN = 'cn=custodia,cn=ipa,cn=etc'
ac7d03
 IPA_KEYS_QUERY = '(&(ipaKeyUsage={usage:s})(memberPrincipal={princ:s}))'
ac7d03
+IPA_CHECK_QUERY = '(cn=enc/{host:s})'
ac7d03
 RFC5280_USAGE_MAP = {KEY_USAGE_SIG: 'digitalSignature',
ac7d03
                      KEY_USAGE_ENC: 'dataEncipherment'}
ac7d03
 
ac7d03
@@ -78,6 +79,17 @@ class KEMLdap(iSecLdap):
ac7d03
         jwk['use'] = KEY_USAGE_MAP[usage]
ac7d03
         return json_encode(jwk)
ac7d03
 
ac7d03
+    def check_host_keys(self, host):
ac7d03
+        conn = self.connect()
ac7d03
+        scope = ldap.SCOPE_SUBTREE
ac7d03
+
ac7d03
+        ldap_filter = self.build_filter(IPA_CHECK_QUERY, {'host': host})
ac7d03
+        r = conn.search_s(self.keysbase, scope, ldap_filter)
ac7d03
+        if len(r) != 1:
ac7d03
+            raise ValueError("Incorrect number of results (%d) searching for"
ac7d03
+                             "public key for %s" % (len(r), host))
ac7d03
+        return True
ac7d03
+
ac7d03
     def _format_public_key(self, key):
ac7d03
         if isinstance(key, str):
ac7d03
             jwkey = json_decode(key)
ac7d03
-- 
ac7d03
2.12.2
ac7d03