ac7d03
From 1190a1b41d436de4dab7a622d78217baba44a9ef Mon Sep 17 00:00:00 2001
ac7d03
From: Christian Heimes <cheimes@redhat.com>
ac7d03
Date: Fri, 17 Mar 2017 10:44:38 +0100
ac7d03
Subject: [PATCH] Simplify KRA transport cert cache
ac7d03
ac7d03
In-memory cache causes problem in forking servers. A file based cache is
ac7d03
good enough. It's easier to understand and avoids performance regression
ac7d03
and synchronization issues when cert becomes out-of-date.
ac7d03
ac7d03
https://pagure.io/freeipa/issue/6787
ac7d03
Signed-off-by: Christian Heimes <cheimes@redhat.com>
ac7d03
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
ac7d03
---
ac7d03
 ipaclient/plugins/vault.py | 103 ++++++++++++++++++++++++---------------------
ac7d03
 1 file changed, 55 insertions(+), 48 deletions(-)
ac7d03
ac7d03
diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
ac7d03
index d677ec0287d6b37cfd63820a919c0726d3a4ae9f..3fb4900d9cf90e6902c40e1c3d8cfdafec2e28b8 100644
ac7d03
--- a/ipaclient/plugins/vault.py
ac7d03
+++ b/ipaclient/plugins/vault.py
ac7d03
@@ -20,7 +20,6 @@
ac7d03
 from __future__ import print_function
ac7d03
 
ac7d03
 import base64
ac7d03
-import collections
ac7d03
 import errno
ac7d03
 import getpass
ac7d03
 import io
ac7d03
@@ -558,74 +557,79 @@ class vault_mod(Local):
ac7d03
         return response
ac7d03
 
ac7d03
 
ac7d03
-class _TransportCertCache(collections.MutableMapping):
ac7d03
+class _TransportCertCache(object):
ac7d03
     def __init__(self):
ac7d03
         self._dirname = os.path.join(
ac7d03
-                USER_CACHE_PATH, 'ipa', 'kra-transport-certs')
ac7d03
-        self._transport_certs = {}
ac7d03
+                USER_CACHE_PATH, 'ipa', 'kra-transport-certs'
ac7d03
+        )
ac7d03
 
ac7d03
     def _get_filename(self, domain):
ac7d03
         basename = DNSName(domain).ToASCII() + '.pem'
ac7d03
         return os.path.join(self._dirname, basename)
ac7d03
 
ac7d03
-    def __getitem__(self, domain):
ac7d03
-        try:
ac7d03
-            transport_cert = self._transport_certs[domain]
ac7d03
-        except KeyError:
ac7d03
-            transport_cert = None
ac7d03
+    def load_cert(self, domain):
ac7d03
+        """Load cert from cache
ac7d03
 
ac7d03
-            filename = self._get_filename(domain)
ac7d03
+        :param domain: IPA domain
ac7d03
+        :return: cryptography.x509.Certificate or None
ac7d03
+        """
ac7d03
+        filename = self._get_filename(domain)
ac7d03
+        try:
ac7d03
             try:
ac7d03
-                try:
ac7d03
-                    transport_cert = x509.load_certificate_from_file(filename)
ac7d03
-                except EnvironmentError as e:
ac7d03
-                    if e.errno != errno.ENOENT:
ac7d03
-                        raise
ac7d03
-            except Exception:
ac7d03
-                logger.warning("Failed to load %s: %s", filename,
ac7d03
-                               exc_info=True)
ac7d03
-
ac7d03
-            if transport_cert is None:
ac7d03
-                raise KeyError(domain)
ac7d03
-
ac7d03
-            self._transport_certs[domain] = transport_cert
ac7d03
+                return x509.load_certificate_from_file(filename)
ac7d03
+            except EnvironmentError as e:
ac7d03
+                if e.errno != errno.ENOENT:
ac7d03
+                    raise
ac7d03
+        except Exception:
ac7d03
+            logger.warning("Failed to load %s", filename, exc_info=True)
ac7d03
 
ac7d03
-        return transport_cert
ac7d03
+    def store_cert(self, domain, transport_cert):
ac7d03
+        """Store a new cert or override existing cert
ac7d03
 
ac7d03
-    def __setitem__(self, domain, transport_cert):
ac7d03
+        :param domain: IPA domain
ac7d03
+        :param transport_cert: cryptography.x509.Certificate
ac7d03
+        :return: True if cert was stored successfully
ac7d03
+        """
ac7d03
         filename = self._get_filename(domain)
ac7d03
-        transport_cert_der = (
ac7d03
-            transport_cert.public_bytes(serialization.Encoding.DER))
ac7d03
+        pem = transport_cert.public_bytes(serialization.Encoding.PEM)
ac7d03
         try:
ac7d03
             try:
ac7d03
                 os.makedirs(self._dirname)
ac7d03
             except EnvironmentError as e:
ac7d03
                 if e.errno != errno.EEXIST:
ac7d03
                     raise
ac7d03
-            fd, tmpfilename = tempfile.mkstemp(dir=self._dirname)
ac7d03
-            os.close(fd)
ac7d03
-            x509.write_certificate(transport_cert_der, tmpfilename)
ac7d03
-            os.rename(tmpfilename, filename)
ac7d03
+            with tempfile.NamedTemporaryFile(dir=self._dirname, delete=False,
ac7d03
+                                             mode='wb') as f:
ac7d03
+                try:
ac7d03
+                    f.write(pem)
ac7d03
+                    f.flush()
ac7d03
+                    os.fdatasync(f.fileno())
ac7d03
+                    f.close()
ac7d03
+                    os.rename(f.name, filename)
ac7d03
+                except Exception:
ac7d03
+                    os.unlink(f.name)
ac7d03
+                    raise
ac7d03
         except Exception:
ac7d03
             logger.warning("Failed to save %s", filename, exc_info=True)
ac7d03
+            return False
ac7d03
+        else:
ac7d03
+            return True
ac7d03
 
ac7d03
-        self._transport_certs[domain] = transport_cert
ac7d03
+    def remove_cert(self, domain):
ac7d03
+        """Remove a cert from cache, ignores errors
ac7d03
 
ac7d03
-    def __delitem__(self, domain):
ac7d03
+        :param domain: IPA domain
ac7d03
+        :return: True if cert was found and removed
ac7d03
+        """
ac7d03
         filename = self._get_filename(domain)
ac7d03
         try:
ac7d03
             os.unlink(filename)
ac7d03
         except EnvironmentError as e:
ac7d03
             if e.errno != errno.ENOENT:
ac7d03
                 logger.warning("Failed to remove %s", filename, exc_info=True)
ac7d03
-
ac7d03
-        del self._transport_certs[domain]
ac7d03
-
ac7d03
-    def __len__(self):
ac7d03
-        return len(self._transport_certs)
ac7d03
-
ac7d03
-    def __iter__(self):
ac7d03
-        return iter(self._transport_certs)
ac7d03
+            return False
ac7d03
+        else:
ac7d03
+            return True
ac7d03
 
ac7d03
 
ac7d03
 _transport_cert_cache = _TransportCertCache()
ac7d03
@@ -646,7 +650,10 @@ class vaultconfig_show(MethodOverride):
ac7d03
         # cache transport certificate
ac7d03
         transport_cert = x509.load_certificate(
ac7d03
                 response['result']['transport_cert'], x509.DER)
ac7d03
-        _transport_cert_cache[self.api.env.domain] = transport_cert
ac7d03
+
ac7d03
+        _transport_cert_cache.store_cert(
ac7d03
+            self.api.env.domain, transport_cert
ac7d03
+        )
ac7d03
 
ac7d03
         if file:
ac7d03
             with open(file, 'w') as f:
ac7d03
@@ -680,7 +687,7 @@ class ModVaultData(Local):
ac7d03
         except (errors.InternalError,
ac7d03
                 errors.ExecutionError,
ac7d03
                 errors.GenericError):
ac7d03
-            _transport_cert_cache.pop(self.api.env.domain, None)
ac7d03
+            _transport_cert_cache.remove_cert(self.api.env.domain)
ac7d03
             if raise_unexpected:
ac7d03
                 raise
ac7d03
 
ac7d03
@@ -691,17 +698,17 @@ class ModVaultData(Local):
ac7d03
         domain = self.api.env.domain
ac7d03
 
ac7d03
         # try call with cached transport certificate
ac7d03
-        transport_cert = _transport_cert_cache.get(domain)
ac7d03
+        transport_cert = _transport_cert_cache.load_cert(domain)
ac7d03
         if transport_cert is not None:
ac7d03
             result = self._do_internal(algo, transport_cert, False,
ac7d03
                                        *args, **options)
ac7d03
             if result is not None:
ac7d03
                 return result
ac7d03
 
ac7d03
-        # retrieve and cache transport certificate
ac7d03
-        self.api.Command.vaultconfig_show()
ac7d03
-        transport_cert = _transport_cert_cache[domain]
ac7d03
-
ac7d03
+        # retrieve transport certificate (cached by vaultconfig_show)
ac7d03
+        response = self.api.Command.vaultconfig_show()
ac7d03
+        transport_cert = x509.load_certificate(
ac7d03
+            response['result']['transport_cert'], x509.DER)
ac7d03
         # call with the retrieved transport certificate
ac7d03
         return self._do_internal(algo, transport_cert, True,
ac7d03
                                  *args, **options)
ac7d03
-- 
ac7d03
2.12.1
ac7d03