e3ffab
From b82f0b1bc483fe265f3e2d2089b65185cafffd74 Mon Sep 17 00:00:00 2001
e3ffab
From: Jan Cholasta <jcholast@redhat.com>
e3ffab
Date: Tue, 14 Oct 2014 10:30:07 +0200
e3ffab
Subject: [PATCH] Handle profile changes in dogtag-ipa-ca-renew-agent
e3ffab
e3ffab
To update the CA certificate in the Dogtag NSS database, the
e3ffab
"ipa-cacert-manage renew" and "ipa-certupdate" commands temporarily change
e3ffab
the profile of the CA certificate certmonger request, resubmit it and
e3ffab
change the profile back to the original one.
e3ffab
e3ffab
When something goes wrong while resubmitting the request, it needs to be
e3ffab
modified and resubmitted again manually. This might fail with invalid
e3ffab
cookie error, because changing the profile does not change the internal
e3ffab
state of the request.
e3ffab
e3ffab
Detect this in dogtag-ipa-ca-renew-agent and reset the internal state when
e3ffab
profile is changed.
e3ffab
e3ffab
https://fedorahosted.org/freeipa/ticket/4627
e3ffab
e3ffab
Reviewed-By: David Kupka <dkupka@redhat.com>
e3ffab
---
e3ffab
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 87 ++++++++++++++++++++--
e3ffab
 1 file changed, 80 insertions(+), 7 deletions(-)
e3ffab
e3ffab
diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
e3ffab
index 4f0b78accac6840471f8b2e9f17288b3b4e82105..ca4380c331cc417c0a89eca17e987920118337d7 100755
e3ffab
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
e3ffab
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
e3ffab
@@ -31,6 +31,7 @@ import tempfile
e3ffab
 import shutil
e3ffab
 import base64
e3ffab
 import contextlib
e3ffab
+import json
e3ffab
 
e3ffab
 from ipapython import ipautil
e3ffab
 from ipapython.dn import DN
e3ffab
@@ -64,6 +65,78 @@ def ldap_connect():
e3ffab
         if conn is not None and conn.isconnected():
e3ffab
             conn.disconnect()
e3ffab
 
e3ffab
+def call_handler(_handler, *args, **kwargs):
e3ffab
+    """
e3ffab
+    Request handler call wrapper
e3ffab
+
e3ffab
+    Before calling the handler, get the original profile name and cookie from
e3ffab
+    the provided cookie, if there is one. If the profile name does not match
e3ffab
+    the requested profile name, drop the cookie and restart the request.
e3ffab
+
e3ffab
+    After calling the handler, put the requested profile name and cookie
e3ffab
+    returned by the handler in a new cookie and return it.
e3ffab
+    """
e3ffab
+    operation = os.environ['CERTMONGER_OPERATION']
e3ffab
+    if operation == 'POLL':
e3ffab
+        cookie = os.environ.pop('CERTMONGER_CA_COOKIE', None)
e3ffab
+        if cookie is not None:
e3ffab
+            try:
e3ffab
+                context = json.loads(cookie)
e3ffab
+                if not isinstance(context, dict):
e3ffab
+                    raise TypeError
e3ffab
+            except (TypeError, ValueError):
e3ffab
+                return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
e3ffab
+        else:
e3ffab
+            return (UNCONFIGURED, "Cookie not provided")
e3ffab
+
e3ffab
+        if 'profile' in context:
e3ffab
+            profile = context.pop('profile')
e3ffab
+            try:
e3ffab
+                if profile is not None:
e3ffab
+                    if not isinstance(profile, unicode):
e3ffab
+                        raise TypeError
e3ffab
+                    profile = profile.encode('raw_unicode_escape')
e3ffab
+            except (TypeError, UnicodeEncodeError):
e3ffab
+                return (UNCONFIGURED,
e3ffab
+                        "Invalid 'profile' in cookie: %r" % profile)
e3ffab
+        else:
e3ffab
+            return (UNCONFIGURED, "No 'profile' in cookie")
e3ffab
+
e3ffab
+        # If profile has changed between SUBMIT and POLL, restart request
e3ffab
+        if os.environ.get('CERTMONGER_CA_PROFILE') != profile:
e3ffab
+            os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
e3ffab
+            context = {}
e3ffab
+
e3ffab
+        if 'cookie' in context:
e3ffab
+            cookie = context.pop('cookie')
e3ffab
+            try:
e3ffab
+                if not isinstance(cookie, unicode):
e3ffab
+                    raise TypeError
e3ffab
+                cookie = cookie.encode('raw_unicode_escape')
e3ffab
+            except (TypeError, UnicodeEncodeError):
e3ffab
+                return (UNCONFIGURED,
e3ffab
+                        "Invalid 'cookie' in cookie: %r" % cookie)
e3ffab
+            os.environ['CERTMONGER_CA_COOKIE'] = cookie
e3ffab
+    else:
e3ffab
+        context = {}
e3ffab
+
e3ffab
+    result = _handler(*args, **kwargs)
e3ffab
+
e3ffab
+    if result[0] in (WAIT, WAIT_WITH_DELAY):
e3ffab
+        context['cookie'] = result[-1].decode('raw_unicode_escape')
e3ffab
+
e3ffab
+    profile = os.environ.get('CERTMONGER_CA_PROFILE')
e3ffab
+    if profile is not None:
e3ffab
+        profile = profile.decode('raw_unicode_escape')
e3ffab
+    context['profile'] = profile
e3ffab
+
e3ffab
+    cookie = json.dumps(context)
e3ffab
+    os.environ['CERTMONGER_CA_COOKIE'] = cookie
e3ffab
+    if result[0] in (WAIT, WAIT_WITH_DELAY):
e3ffab
+        result = result[:-1] + (cookie,)
e3ffab
+
e3ffab
+    return result
e3ffab
+
e3ffab
 def request_cert():
e3ffab
     """
e3ffab
     Request certificate from IPA CA.
e3ffab
@@ -144,7 +217,7 @@ def store_cert():
e3ffab
             syslog.syslog(
e3ffab
                 syslog.LOG_ERR,
e3ffab
                 "Updating renewal certificate failed: %s. Sleeping 30s" % e)
e3ffab
-            return (WAIT_WITH_DELAY, 30, attempts)
e3ffab
+            return (WAIT_WITH_DELAY, 30, str(attempts))
e3ffab
         else:
e3ffab
             syslog.syslog(
e3ffab
                 syslog.LOG_ERR,
e3ffab
@@ -179,7 +252,7 @@ def request_and_store_cert():
e3ffab
         else:
e3ffab
             os.environ['CERTMONGER_CA_COOKIE'] = cookie
e3ffab
 
e3ffab
-        result = request_cert()
e3ffab
+        result = call_handler(request_cert)
e3ffab
         if result[0] == WAIT:
e3ffab
             return (result[0], 'request:%s' % result[1])
e3ffab
         elif result[0] == WAIT_WITH_DELAY:
e3ffab
@@ -198,7 +271,7 @@ def request_and_store_cert():
e3ffab
         os.environ['CERTMONGER_CA_COOKIE'] = cookie
e3ffab
     os.environ['CERTMONGER_CERTIFICATE'] = cert
e3ffab
 
e3ffab
-    result = store_cert()
e3ffab
+    result = call_handler(store_cert)
e3ffab
     if result[0] == WAIT:
e3ffab
         return (result[0], 'store:%s:%s' % (cert, result[1]))
e3ffab
     elif result[0] == WAIT_WITH_DELAY:
e3ffab
@@ -258,7 +331,7 @@ def retrieve_cert():
e3ffab
                     syslog.LOG_INFO,
e3ffab
                     "Updated certificate for %s not available" % nickname)
e3ffab
                 # No cert available yet, tell certmonger to wait another 8 hours
e3ffab
-                return (WAIT_WITH_DELAY, 8 * 60 * 60, attempts)
e3ffab
+                return (WAIT_WITH_DELAY, 8 * 60 * 60, str(attempts))
e3ffab
 
e3ffab
         cert = base64.b64encode(cert)
e3ffab
         cert = x509.make_pem(cert)
e3ffab
@@ -323,14 +396,14 @@ def renew_ca_cert():
e3ffab
         return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
e3ffab
 
e3ffab
     if state == 'retrieve':
e3ffab
-        result = retrieve_cert()
e3ffab
+        result = call_handler(retrieve_cert)
e3ffab
         if result[0] == WAIT_WITH_DELAY and not is_self_signed:
e3ffab
             syslog.syslog(syslog.LOG_ALERT,
e3ffab
                           "IPA CA certificate is about to expire, "
e3ffab
                           "use ipa-cacert-manage to renew it")
e3ffab
     elif state == 'request':
e3ffab
         os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
e3ffab
-        result = request_and_store_cert()
e3ffab
+        result = call_handler(request_and_store_cert)
e3ffab
 
e3ffab
     if result[0] == WAIT:
e3ffab
         return (result[0], '%s:%s' % (state, result[1]))
e3ffab
@@ -369,7 +442,7 @@ def main():
e3ffab
             else:
e3ffab
                 handler = retrieve_cert
e3ffab
 
e3ffab
-        res = handler()
e3ffab
+        res = call_handler(handler)
e3ffab
         for item in res[1:]:
e3ffab
             print item
e3ffab
         return res[0]
e3ffab
-- 
e3ffab
2.1.0
e3ffab