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