95ea96
From 3e8427f3aa392f961923f5c9d509bab64ce3c9ab Mon Sep 17 00:00:00 2001
2737e7
From: Christian Heimes <cheimes@redhat.com>
2737e7
Date: Sun, 8 Jul 2018 11:53:58 +0200
2737e7
Subject: [PATCH] Auto-retry failed certmonger requests
2737e7
2737e7
During parallel replica installation, a request sometimes fails with
2737e7
CA_REJECTED or CA_UNREACHABLE. The error occur when the master is
2737e7
either busy or some information haven't been replicated yet. Even
2737e7
a stuck request can be recovered, e.g. when permission and group
2737e7
information have been replicated.
2737e7
2737e7
A new function request_and_retry_cert() automatically resubmits failing
2737e7
requests until it times out.
2737e7
2737e7
Fixes: https://pagure.io/freeipa/issue/7623
2737e7
Signed-off-by: Christian Heimes <cheimes@redhat.com>
95ea96
Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
2737e7
---
2737e7
 ipalib/install/certmonger.py      | 64 ++++++++++++++++++++++++-------
2737e7
 ipaserver/install/cainstance.py   |  4 +-
2737e7
 ipaserver/install/certs.py        | 19 ++++++---
2737e7
 ipaserver/install/dsinstance.py   |  4 +-
2737e7
 ipaserver/install/httpinstance.py |  5 ++-
2737e7
 ipaserver/install/krbinstance.py  |  4 +-
2737e7
 6 files changed, 76 insertions(+), 24 deletions(-)
2737e7
2737e7
diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
95ea96
index c07242412affa29eca3312fd27985f65869d3f7a..3e1862192eb0d9245797ebbe8abf2ff69d7e7767 100644
2737e7
--- a/ipalib/install/certmonger.py
2737e7
+++ b/ipalib/install/certmonger.py
95ea96
@@ -305,20 +305,56 @@ def add_subject(request_id, subject):
2737e7
 def request_and_wait_for_cert(
2737e7
         certpath, subject, principal, nickname=None, passwd_fname=None,
2737e7
         dns=None, ca='IPA', profile=None,
2737e7
-        pre_command=None, post_command=None, storage='NSSDB', perms=None):
2737e7
-    """
2737e7
-    Execute certmonger to request a server certificate.
2737e7
-
2737e7
-    The method also waits for the certificate to be available.
2737e7
-    """
2737e7
-    reqId = request_cert(certpath, subject, principal, nickname,
2737e7
-                         passwd_fname, dns, ca, profile,
2737e7
-                         pre_command, post_command, storage, perms)
2737e7
-    state = wait_for_request(reqId, api.env.startup_timeout)
2737e7
-    ca_error = get_request_value(reqId, 'ca-error')
2737e7
-    if state != 'MONITORING' or ca_error:
2737e7
-        raise RuntimeError("Certificate issuance failed ({})".format(state))
2737e7
-    return reqId
2737e7
+        pre_command=None, post_command=None, storage='NSSDB', perms=None,
2737e7
+        resubmit_timeout=0):
2737e7
+    """Request certificate, wait and possibly resubmit failing requests
2737e7
+
2737e7
+    Submit a cert request to certmonger and wait until the request has
2737e7
+    finished.
2737e7
+
2737e7
+    With timeout, a failed request is resubmitted. During parallel replica
2737e7
+    installation, a request sometimes fails with CA_REJECTED or
2737e7
+    CA_UNREACHABLE. The error occurs when the master is either busy or some
2737e7
+    information haven't been replicated yet. Even a stuck request can be
2737e7
+    recovered, e.g. when permission and group information have been
2737e7
+    replicated.
2737e7
+    """
2737e7
+    req_id = request_cert(
2737e7
+        certpath, subject, principal, nickname, passwd_fname, dns, ca,
2737e7
+        profile, pre_command, post_command, storage, perms
2737e7
+    )
2737e7
+
2737e7
+    deadline = time.time() + resubmit_timeout
2737e7
+    while True:  # until success, timeout, or error
2737e7
+        state = wait_for_request(req_id, api.env.replication_wait_timeout)
2737e7
+        ca_error = get_request_value(req_id, 'ca-error')
2737e7
+        if state == 'MONITORING' and ca_error is None:
2737e7
+            # we got a winner, exiting
95ea96
+            logger.debug("Cert request %s was successful", req_id)
2737e7
+            return req_id
2737e7
+
95ea96
+        logger.debug(
2737e7
+            "Cert request %s failed: %s (%s)", req_id, state, ca_error
2737e7
+        )
2737e7
+        if state not in {'CA_REJECTED', 'CA_UNREACHABLE'}:
2737e7
+            # probably unrecoverable error
95ea96
+            logger.debug("Giving up on cert request %s", req_id)
2737e7
+            break
2737e7
+        elif not resubmit_timeout:
2737e7
+            # no resubmit
2737e7
+            break
2737e7
+        elif time.time() > deadline:
95ea96
+            logger.debug("Request %s reached resubmit dead line", req_id)
2737e7
+            break
2737e7
+        else:
2737e7
+            # sleep and resubmit
95ea96
+            logger.debug("Sleep and resubmit cert request %s", req_id)
2737e7
+            time.sleep(10)
2737e7
+            resubmit_request(req_id)
2737e7
+
2737e7
+    raise RuntimeError(
2737e7
+        "Certificate issuance failed ({}: {})".format(state, ca_error)
2737e7
+    )
2737e7
 
2737e7
 
2737e7
 def request_cert(
2737e7
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
95ea96
index 8193f3da854b3a20d175de523fbc453f5c5104d8..6dbf69b3e5833f220a4d7d640b66a8fcf824f445 100644
2737e7
--- a/ipaserver/install/cainstance.py
2737e7
+++ b/ipaserver/install/cainstance.py
95ea96
@@ -917,7 +917,9 @@ class CAInstance(DogtagInstance):
2737e7
                 profile='caServerCert',
2737e7
                 pre_command='renew_ra_cert_pre',
2737e7
                 post_command='renew_ra_cert',
2737e7
-                storage="FILE")
2737e7
+                storage="FILE",
2737e7
+                resubmit_timeout=api.env.replication_wait_timeout
2737e7
+            )
2737e7
             self.__set_ra_cert_perms()
2737e7
 
2737e7
             self.requestId = str(reqId)
2737e7
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
95ea96
index 3f843399f4f964223f52242d610e842a5dc473e8..30b2aa0d3e7b2cafbcc17ad3d04764a342ae8002 100644
2737e7
--- a/ipaserver/install/certs.py
2737e7
+++ b/ipaserver/install/certs.py
95ea96
@@ -600,12 +600,19 @@ class CertDB(object):
2737e7
     def export_pem_cert(self, nickname, location):
2737e7
         return self.nssdb.export_pem_cert(nickname, location)
2737e7
 
2737e7
-    def request_service_cert(self, nickname, principal, host):
2737e7
-        certmonger.request_and_wait_for_cert(certpath=self.secdir,
2737e7
-                                             nickname=nickname,
2737e7
-                                             principal=principal,
2737e7
-                                             subject=host,
2737e7
-                                             passwd_fname=self.passwd_fname)
2737e7
+    def request_service_cert(self, nickname, principal, host,
2737e7
+                             resubmit_timeout=None):
2737e7
+        if resubmit_timeout is None:
2737e7
+            resubmit_timeout = api.env.replication_wait_timeout
2737e7
+        return certmonger.request_and_wait_for_cert(
2737e7
+            certpath=self.secdir,
2737e7
+            storage='NSSDB',
2737e7
+            nickname=nickname,
2737e7
+            principal=principal,
2737e7
+            subject=host,
2737e7
+            passwd_fname=self.passwd_fname,
2737e7
+            resubmit_timeout=resubmit_timeout
2737e7
+        )
2737e7
 
2737e7
     def is_ipa_issued_cert(self, api, nickname):
2737e7
         """
2737e7
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
95ea96
index eefbde3356e1077d490d09c4ea47d961ce3ce8e6..ac95f8c746477da375de518526dea0d02d51d984 100644
2737e7
--- a/ipaserver/install/dsinstance.py
2737e7
+++ b/ipaserver/install/dsinstance.py
95ea96
@@ -851,7 +851,9 @@ class DsInstance(service.Service):
2737e7
                     ca='IPA',
2737e7
                     profile=dogtag.DEFAULT_PROFILE,
2737e7
                     dns=[self.fqdn],
2737e7
-                    post_command=cmd)
2737e7
+                    post_command=cmd,
2737e7
+                    resubmit_timeout=api.env.replication_wait_timeout
2737e7
+                )
2737e7
             finally:
2737e7
                 if prev_helper is not None:
2737e7
                     certmonger.modify_ca_helper('IPA', prev_helper)
2737e7
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
95ea96
index 0b7023c2f1b0feb996e0dd0adbefbd49c51da757..3f83248dd89118aeecfbf458c5079dde8b2cb93d 100644
2737e7
--- a/ipaserver/install/httpinstance.py
2737e7
+++ b/ipaserver/install/httpinstance.py
95ea96
@@ -447,7 +447,10 @@ class HTTPInstance(service.Service):
2737e7
                     ca='IPA',
2737e7
                     profile=dogtag.DEFAULT_PROFILE,
2737e7
                     dns=[self.fqdn],
2737e7
-                    post_command='restart_httpd')
2737e7
+                    post_command='restart_httpd',
2737e7
+                    storage='NSSDB',
2737e7
+                    resubmit_timeout=api.env.replication_wait_timeout
2737e7
+                )
2737e7
             finally:
2737e7
                 if prev_helper is not None:
2737e7
                     certmonger.modify_ca_helper('IPA', prev_helper)
2737e7
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
95ea96
index 33d66fb94b0a1f7571b22120e5159a0e0ad2e675..09cafb7b84623594fe88083f5b914cee0f050409 100644
2737e7
--- a/ipaserver/install/krbinstance.py
2737e7
+++ b/ipaserver/install/krbinstance.py
95ea96
@@ -445,7 +445,9 @@ class KrbInstance(service.Service):
2737e7
                 storage='FILE',
2737e7
                 profile=KDC_PROFILE,
2737e7
                 post_command='renew_kdc_cert',
2737e7
-                perms=(0o644, 0o600))
2737e7
+                perms=(0o644, 0o600),
2737e7
+                resubmit_timeout=api.env.replication_wait_timeout
2737e7
+            )
2737e7
         except dbus.DBusException as e:
2737e7
             # if the certificate is already tracked, ignore the error
2737e7
             name = e.get_dbus_name()
2737e7
-- 
2737e7
2.17.1
2737e7