Blob Blame History Raw
From 3d7d58d8214f3c899c0afd1a3a6a6678f38b7b39 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 13 2020 18:41:53 +0000
Subject: Allow an empty cookie in dogtag-ipa-ca-renew-agent-submit


A "cookie" is used with certmonger to track the state of a
request across multiple requests to a CA (in ca-cookie). This
is used with the certmonger POLL operation to submit a request
to the CA for the status of a certificate request. This, along
with the profile, are passed to the certmonger CA helper
scripts via environment variables when a request is made. It is
cleared from the certmonger request once the certificate is
issued.

This CA helper can do a number of things:

- SUBMIT new certicate requests (including the CA)
- POLL for status of an existing certificate request
- For non renewal masters, POLL to see if an updated cert is in
  LDAP

A POLL operation requires a cookie so that the state about the
request can be passed to the CA. For the case of retrieving an
updated cert from LDAP there is no state to maintain. It just
checks LDAP and returns either a cert or WAIT_WITH_DELAY if one
is not yet available.

There are two kinds of cookies in operation here:
1. The CERTMONGER_CA_COOKIE environment variable passed via
   certmonger to this helper which is a JSON object.
2. The cookie value within the JSON object which contains the
   URL to be passed to dogtag.

For the purposes of clarity "cookie" here is the value within
the JSON.

The CERTMONGER_CA_COOKIE is deconstructed and reconstructed as
the request is processed, doing double duty. It initially comes
in as a JSON dict object with two keys: profile and cookie.
In call_handler the CERTMONGER_CA_COOKIE is decomposed into a
python object and the profile compared to the requested profile
(and request rejected if they don't match) and the cookie key
overrides the CERTMONGER_CA_COOKIE environment variable. This is
then reversed at the end of the request when it again becomes a
JSON object containing the profile and cookie.

This script was previously enforcing that a cookie be available on
all POLL requests, whether it is actually required or not. This
patch relaxes that requirement.

The first request of a non-renewal master for an updated certicate
from LDAP is a SUBMIT operation. This is significant because it
doesn't require a cookie: there is no state on a new request. If
there is no updated cert in LDAP then the tracking request goes
into the CA_WORKING state and certmonger will wait 8 hours (as
returned by this script) and try again.

Subsequent requests are done using POLL. This required a cookie
so all such requests would fail with the ca-error
Invalid cookie: u'' as it was empty (because there is no state).

There is no need to fail early on a missing cookie. Enforcement
will be done later if needed (and it isn't always needed). So
if CERTMONGER_CA_COOKIE is an empty string then generate a new
CERTMONGER_CA_COOKIE containing the requested profile and an empty
cookie. It still will fail if certmonger doesn't set a cookie at
all.

An example of a cookie when retrieving a new RA Agent certificate
is:

{"profile": "caServerCert", "cookie": "state=retrieve&requestId=20"}

This will result in this request to the CA:
[09/Jan/2020:14:29:54 -0500] "GET
/ca/ee/ca/displayCertFromRequest?requestId=20&importCert=true&xml=true
HTTP/1.1" 200 9857

For a renewal, the reconstructed cookie will consist of:

{"profile": "caServerCert", "cookie": ""}

https://pagure.io/freeipa/issue/8164

Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>

---

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in b/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
index 10efb4c..95ad080 100644
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
@@ -123,7 +123,9 @@ def call_handler(_handler, *args, **kwargs):
     operation = os.environ['CERTMONGER_OPERATION']
     if operation == 'POLL':
         cookie = os.environ.pop('CERTMONGER_CA_COOKIE', None)
-        if cookie is not None:
+        if cookie is None:
+            return (UNCONFIGURED, "Cookie not provided")
+        if len(cookie) > 0:
             try:
                 context = json.loads(cookie)
                 if not isinstance(context, dict):
@@ -131,7 +133,13 @@ def call_handler(_handler, *args, **kwargs):
             except (TypeError, ValueError):
                 return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
         else:
-            return (UNCONFIGURED, "Cookie not provided")
+            # Reconstruct the data for the missing cookie. Sanity checking
+            # is done elsewhere, when needed.
+            context = dict(cookie=u'')
+            profile = os.environ.get('CERTMONGER_CA_PROFILE')
+            if profile is not None:
+                profile = profile.encode('ascii').decode('raw_unicode_escape')
+            context['profile'] = profile
 
         if 'profile' in context:
             profile = context.pop('profile')