Hi,

the attached patch fixes <https://fedorahosted.org/freeipa/ticket/4627>.

(The original patch was posted at <http://www.redhat.com/archives/freeipa-devel/2014-September/msg00454.html>.)

How to test:

 1. install server

 2. run "ipa-certupdate"

3. run "getcert list -d /etc/pki/pki-tomcat/alias -n 'caSigningCert cert-pki-ca'", the request should be in CA_WORKING state

 4. run "ipa-cacert-manage renew"

5. run "getcert list -d /etc/pki/pki-tomcat/alias -n 'caSigningCert cert-pki-ca'", the request should be in MONITORING state, there should be no ca-error

Honza

--
Jan Cholasta
>From 6ac462b18d0a31d8eb697e949bc909327f41d618 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 14 Oct 2014 10:30:07 +0200
Subject: [PATCH] Handle profile changes in dogtag-ipa-ca-renew-agent

To update the CA certificate in the Dogtag NSS database, the
"ipa-cacert-manage renew" and "ipa-certupdate" commands temporarily change
the profile of the CA certificate certmonger request, resubmit it and
change the profile back to the original one.

When something goes wrong while resubmitting the request, it needs to be
modified and resubmitted again manually. This might fail with invalid
cookie error, because changing the profile does not change the internal
state of the request.

Detect this in dogtag-ipa-ca-renew-agent and reset the internal state when
profile is changed.

https://fedorahosted.org/freeipa/ticket/4627
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 87 ++++++++++++++++++++--
 1 file changed, 80 insertions(+), 7 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 4f0b78a..ca4380c 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -31,6 +31,7 @@ import tempfile
 import shutil
 import base64
 import contextlib
+import json
 
 from ipapython import ipautil
 from ipapython.dn import DN
@@ -64,6 +65,78 @@ def ldap_connect():
         if conn is not None and conn.isconnected():
             conn.disconnect()
 
+def call_handler(_handler, *args, **kwargs):
+    """
+    Request handler call wrapper
+
+    Before calling the handler, get the original profile name and cookie from
+    the provided cookie, if there is one. If the profile name does not match
+    the requested profile name, drop the cookie and restart the request.
+
+    After calling the handler, put the requested profile name and cookie
+    returned by the handler in a new cookie and return it.
+    """
+    operation = os.environ['CERTMONGER_OPERATION']
+    if operation == 'POLL':
+        cookie = os.environ.pop('CERTMONGER_CA_COOKIE', None)
+        if cookie is not None:
+            try:
+                context = json.loads(cookie)
+                if not isinstance(context, dict):
+                    raise TypeError
+            except (TypeError, ValueError):
+                return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
+        else:
+            return (UNCONFIGURED, "Cookie not provided")
+
+        if 'profile' in context:
+            profile = context.pop('profile')
+            try:
+                if profile is not None:
+                    if not isinstance(profile, unicode):
+                        raise TypeError
+                    profile = profile.encode('raw_unicode_escape')
+            except (TypeError, UnicodeEncodeError):
+                return (UNCONFIGURED,
+                        "Invalid 'profile' in cookie: %r" % profile)
+        else:
+            return (UNCONFIGURED, "No 'profile' in cookie")
+
+        # If profile has changed between SUBMIT and POLL, restart request
+        if os.environ.get('CERTMONGER_CA_PROFILE') != profile:
+            os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
+            context = {}
+
+        if 'cookie' in context:
+            cookie = context.pop('cookie')
+            try:
+                if not isinstance(cookie, unicode):
+                    raise TypeError
+                cookie = cookie.encode('raw_unicode_escape')
+            except (TypeError, UnicodeEncodeError):
+                return (UNCONFIGURED,
+                        "Invalid 'cookie' in cookie: %r" % cookie)
+            os.environ['CERTMONGER_CA_COOKIE'] = cookie
+    else:
+        context = {}
+
+    result = _handler(*args, **kwargs)
+
+    if result[0] in (WAIT, WAIT_WITH_DELAY):
+        context['cookie'] = result[-1].decode('raw_unicode_escape')
+
+    profile = os.environ.get('CERTMONGER_CA_PROFILE')
+    if profile is not None:
+        profile = profile.decode('raw_unicode_escape')
+    context['profile'] = profile
+
+    cookie = json.dumps(context)
+    os.environ['CERTMONGER_CA_COOKIE'] = cookie
+    if result[0] in (WAIT, WAIT_WITH_DELAY):
+        result = result[:-1] + (cookie,)
+
+    return result
+
 def request_cert():
     """
     Request certificate from IPA CA.
@@ -144,7 +217,7 @@ def store_cert():
             syslog.syslog(
                 syslog.LOG_ERR,
                 "Updating renewal certificate failed: %s. Sleeping 30s" % e)
-            return (WAIT_WITH_DELAY, 30, attempts)
+            return (WAIT_WITH_DELAY, 30, str(attempts))
         else:
             syslog.syslog(
                 syslog.LOG_ERR,
@@ -179,7 +252,7 @@ def request_and_store_cert():
         else:
             os.environ['CERTMONGER_CA_COOKIE'] = cookie
 
-        result = request_cert()
+        result = call_handler(request_cert)
         if result[0] == WAIT:
             return (result[0], 'request:%s' % result[1])
         elif result[0] == WAIT_WITH_DELAY:
@@ -198,7 +271,7 @@ def request_and_store_cert():
         os.environ['CERTMONGER_CA_COOKIE'] = cookie
     os.environ['CERTMONGER_CERTIFICATE'] = cert
 
-    result = store_cert()
+    result = call_handler(store_cert)
     if result[0] == WAIT:
         return (result[0], 'store:%s:%s' % (cert, result[1]))
     elif result[0] == WAIT_WITH_DELAY:
@@ -258,7 +331,7 @@ def retrieve_cert():
                     syslog.LOG_INFO,
                     "Updated certificate for %s not available" % nickname)
                 # No cert available yet, tell certmonger to wait another 8 hours
-                return (WAIT_WITH_DELAY, 8 * 60 * 60, attempts)
+                return (WAIT_WITH_DELAY, 8 * 60 * 60, str(attempts))
 
         cert = base64.b64encode(cert)
         cert = x509.make_pem(cert)
@@ -323,14 +396,14 @@ def renew_ca_cert():
         return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
 
     if state == 'retrieve':
-        result = retrieve_cert()
+        result = call_handler(retrieve_cert)
         if result[0] == WAIT_WITH_DELAY and not is_self_signed:
             syslog.syslog(syslog.LOG_ALERT,
                           "IPA CA certificate is about to expire, "
                           "use ipa-cacert-manage to renew it")
     elif state == 'request':
         os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
-        result = request_and_store_cert()
+        result = call_handler(request_and_store_cert)
 
     if result[0] == WAIT:
         return (result[0], '%s:%s' % (state, result[1]))
@@ -369,7 +442,7 @@ def main():
             else:
                 handler = retrieve_cert
 
-        res = handler()
+        res = call_handler(handler)
         for item in res[1:]:
             print item
         return res[0]
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to