Blob Blame History Raw
From b82f0b1bc483fe265f3e2d2089b65185cafffd74 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jcholast@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

Reviewed-By: David Kupka <dkupka@redhat.com>
---
 .../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 4f0b78accac6840471f8b2e9f17288b3b4e82105..ca4380c331cc417c0a89eca17e987920118337d7 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]
-- 
2.1.0