URL: https://github.com/freeipa/freeipa/pull/728
Author: HonzaCholasta
 Title: #728: ipa-cacert-manage: add --external-ca-type
Action: opened

PR body:
"""
**server upgrade: always fix certmonger tracking request**

Fix certmonger tracking requests on every run of ipa-server-upgrade rather
than only when the tracking configuration has changed and the requests have
not yet been updated.

This allows fixing broken tracking requests just by re-running
ipa-server-upgrade.

**cainstance: use correct profile for lightweight CA certificates**

Use Dogtag's `caCACert` CA certificate profile rather than the
`ipaCACertRenewal` virtual profile for lightweight CA certificates.

The `ipaCACertRenewal` virtual profile adds special handling of externally
signed CA certificates and LDAP replication of issued certificates on top
of `caCACert`, neither of which is relevant for lightweight CA
certificates.

Remove all of the special casing of lightweight CA certificates from
dogtag-ipa-ca-renew-agent-submit.

Make sure existing lightweight CA certmonger tracking requests are updated
on server upgrade.

**renew agent: allow reusing existing certs**

Add a switch which makes `dogtag-ipa-ca-renew-agent-submit` reuse the
existing certificate rather than request a new one from the CA while
maintaining LDAP replication of the certificate.

Make this available as a new `dogtag-ipa-ca-renew-agent-reuse` certmonger
CA.

This allows redoing the LDAP replication and reexecuting pre- and post-save
commands of a tracking request without reissuing the certificate.

**renew agent: always export CSR on IPA CA certificate renewal**

Make sure a CSR is exported for the IPA CA whenever certmonger detects that
the CA certificate is about to expire.

This is a pre-requisite for using the `dogtag-ipa-ca-renew-agent-reuse` CA
instead of the `ipaCSRExport` virtual profile to export the CSR.

**renew agent: get rid of virtual profiles**

Replace all uses of virtual profiles with `dogtag-ipa-ca-renew-agent-reuse`
and remove profile from the IPA CA certificate tracking request.

This prevents virtual profiles from making their way into CSRs and in turn
being rejected by certain CAs. This affected the IPA CA CSR with Microsoft
CS in particular.

**ipa-cacert-manage: add --external-ca-type**

Add the `--external-ca-type`, as known from `ipa-server-install` and
`ipa-ca-install`, to `ipa-cacert-manage`.

This allows creating IPA CA CSRs suitable for use with Microsoft CS using
`ipa-cacert-manage`:

```
ipa-cacert-manage renew --external-ca --external-ca-type=ms-cs
```

https://pagure.io/freeipa/issue/5799
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/728/head:pr728
git checkout pr728
From b7ead617441712d6d7286ac66a2b2feea97f72af Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 24 Apr 2017 05:24:24 +0000
Subject: [PATCH 1/7] renew agent: respect CA renewal master setting

Do not bypass the renewal master check when a non-virtual profile is used
in dogtag-ipa-ca-renew-agent-submit.

This fixes dogtag-ipa-ca-renew-agent not respecting the CA renewal master
setting for certificates tracked with a real profile. (Note that there
currently aren't any such certificates tracked by us.)

Request the RA certificate using dogtag-submit rather than
dogtag-ipa-ca-renew-agent-submit as the CA renewal master setting is not
available so early in the install process.

https://pagure.io/freeipa/issue/5799
---
 install/certmonger/dogtag-ipa-ca-renew-agent-submit | 2 +-
 ipaserver/install/cainstance.py                     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 7a3d955..f253fd9 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -535,7 +535,7 @@ def main():
 
         profile = os.environ.get('CERTMONGER_CA_PROFILE')
         if is_replicated():
-            if profile or is_renewal_master():
+            if is_renewal_master():
                 handler = request_and_store_cert
             else:
                 handler = retrieve_cert_continuous
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 84d60bf..336299c 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -823,7 +823,7 @@ def __request_ra_certificate(self):
              "-out", chain_file.name,
              ], stdin=data, capture_output=False)
 
-        agent_args = [paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT,
+        agent_args = [paths.CERTMONGER_DOGTAG_SUBMIT,
                       "--dbdir", self.tmp_agent_db,
                       "--nickname", "ipa-ca-agent",
                       "--cafile", chain_file.name,

From 6a64cc75f17ce029b487ff6c5bdd46bcd05c645d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 28 Feb 2017 10:55:54 +0000
Subject: [PATCH 2/7] server upgrade: always fix certmonger tracking request

Fix certmonger tracking requests on every run of ipa-server-upgrade rather
than only when the tracking configuration has changed and the requests have
not yet been updated.

This allows fixing broken tracking requests just by re-running
ipa-server-upgrade.

https://pagure.io/freeipa/issue/5799
---
 ipaserver/install/server/upgrade.py | 28 +++++++---------------------
 1 file changed, 7 insertions(+), 21 deletions(-)

diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 927acb0..5951c7d 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -905,8 +905,6 @@ def certificate_renewal_update(ca, ds, http):
     template = paths.CERTMONGER_COMMAND_TEMPLATE
     serverid = installutils.realm_to_serverid(api.env.realm)
 
-    # bump version when requests is changed
-    version = 6
     requests = [
         {
             'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
@@ -971,25 +969,17 @@ def certificate_renewal_update(ca, ds, http):
         }
     ]
 
-    root_logger.info("[Update certmonger certificate renewal configuration to "
-                     "version %d]" % version)
+    root_logger.info("[Update certmonger certificate renewal configuration]")
     if not ca.is_configured():
         root_logger.info('CA is not configured')
         return False
 
-    state = 'certificate_renewal_update_%d' % version
-    if sysupgrade.get_upgrade_state('dogtag', state):
-        return False
-
     # State not set, lets see if we are already configured
     for request in requests:
         request_id = certmonger.get_request_id(request)
         if request_id is None:
             break
     else:
-        sysupgrade.set_upgrade_state('dogtag', state, True)
-        root_logger.info("Certmonger certificate renewal configuration is "
-                         "already at version %d" % version)
         return False
 
     # Ok, now we need to stop tracking, then we can start tracking them
@@ -998,13 +988,11 @@ def certificate_renewal_update(ca, ds, http):
     ds.stop_tracking_certificates(serverid)
     http.stop_tracking_certificates()
 
-    if not sysupgrade.get_upgrade_state('dogtag',
-                                        'certificate_renewal_update_1'):
-        filename = paths.CERTMONGER_CAS_CA_RENEWAL
-        if os.path.exists(filename):
-            with installutils.stopped_service('certmonger'):
-                root_logger.info("Removing %s" % filename)
-                installutils.remove_file(filename)
+    filename = paths.CERTMONGER_CAS_CA_RENEWAL
+    if os.path.exists(filename):
+        with installutils.stopped_service('certmonger'):
+            root_logger.info("Removing %s" % filename)
+            installutils.remove_file(filename)
 
     ca.configure_certmonger_renewal()
     ca.configure_renewal()
@@ -1013,9 +1001,7 @@ def certificate_renewal_update(ca, ds, http):
     ds.start_tracking_certificates(serverid)
     http.start_tracking_certificates()
 
-    sysupgrade.set_upgrade_state('dogtag', state, True)
-    root_logger.info("Certmonger certificate renewal configuration updated to "
-                     "version %d" % version)
+    root_logger.info("Certmonger certificate renewal configuration updated")
     return True
 
 def copy_crl_file(old_path, new_path=None):

From 6e59724559f820c3fc47cfb5abdc3ad7681a7547 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 28 Feb 2017 10:58:28 +0000
Subject: [PATCH 3/7] cainstance: use correct profile for lightweight CA
 certificates

Use Dogtag's `caCACert` CA certificate profile rather than the
`ipaCACertRenewal` virtual profile for lightweight CA certificates.

The `ipaCACertRenewal` virtual profile adds special handling of externally
signed CA certificates and LDAP replication of issued certificates on top
of `caCACert`, neither of which is relevant for lightweight CA
certificates.

Remove all of the special casing of lightweight CA certificates from
dogtag-ipa-ca-renew-agent-submit.

Make sure existing lightweight CA certmonger tracking requests are updated
on server upgrade.

https://pagure.io/freeipa/issue/5799
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 36 +++-------------------
 ipaserver/install/cainstance.py                    |  7 ++---
 ipaserver/install/server/upgrade.py                | 16 ++++++++++
 3 files changed, 23 insertions(+), 36 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index f253fd9..51b0880 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -98,25 +98,7 @@ def get_nickname():
         DN('CN=IPA RA', subject_base): 'ipaCert',
     }
 
-    try:
-        return nickname_by_subject_dn[DN(subject)]
-    except KeyError:
-        cas = api.Command.ca_find(ipacasubjectdn=DN(subject))['result']
-        if len(cas) == 0:
-            return None
-        return 'caSigningCert cert-pki-ca {}'.format(cas[0]['ipacaid'][0])
-
-
-def is_lightweight_ca():
-    nickname = get_nickname() or ''
-    return nickname != IPA_CA_NICKNAME and nickname.startswith(IPA_CA_NICKNAME)
-
-def is_renewable():
-    cert = os.environ.get('CERTMONGER_CERTIFICATE')
-    if not cert:
-        return False
-    else:
-        return x509.is_self_signed(cert) or is_lightweight_ca()
+    return nickname_by_subject_dn.get(DN(subject))
 
 
 def is_replicated():
@@ -276,11 +258,6 @@ def store_cert():
     if not cert:
         return (REJECTED, "New certificate requests not supported")
 
-    if is_lightweight_ca():
-        # Lightweight CAs are updated in Dogtag's NSSDB
-        # by Dogtag itself, so do not store it
-        return (ISSUED, cert)
-
     dercert = x509.normalize_certificate(cert)
 
     dn = DN(('cn', nickname), ('cn', 'ca_renewal'),
@@ -405,12 +382,6 @@ def retrieve_cert_continuous():
     if old_cert:
         old_cert = x509.normalize_certificate(old_cert)
 
-    if is_lightweight_ca():
-        # Lightweight CAs are updated in Dogtag's NSSDB
-        # by Dogtag itself, so do not try to retrieve it.
-        # Everything is fine as is.
-        return (ISSUED, os.environ.get('CERTMONGER_CERTIFICATE'))
-
     result = call_handler(retrieve_or_reuse_cert)
     if result[0] != ISSUED:
         return result
@@ -466,12 +437,13 @@ def renew_ca_cert():
     cert = os.environ.get('CERTMONGER_CERTIFICATE')
     if not cert:
         return (REJECTED, "New certificate requests not supported")
+    is_self_signed = x509.is_self_signed(cert)
 
     operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
         state = 'retrieve'
 
-        if is_renewable() and is_renewal_master():
+        if is_self_signed and is_renewal_master():
             state = 'request'
     elif operation == 'POLL':
         cookie = os.environ.get('CERTMONGER_CA_COOKIE')
@@ -489,7 +461,7 @@ def renew_ca_cert():
 
     if state == 'retrieve':
         result = call_handler(retrieve_cert)
-        if result[0] == REJECTED and not is_renewable():
+        if result[0] == REJECTED and not is_self_signed:
             syslog.syslog(syslog.LOG_ALERT,
                           "Certificate with subject '%s' is about to expire, "
                           "use ipa-cacert-manage to renew it"
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 336299c..8303ae7 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -437,7 +437,7 @@ def configure_instance(self, host_name, dm_password, admin_password,
                 self.step("enabling CA instance", self.__enable_instance)
 
                 self.step("configuring certmonger renewal for lightweight CAs",
-                          self.__add_lightweight_ca_tracking_requests)
+                          self.add_lightweight_ca_tracking_requests)
 
         if ra_only:
             runtime = None
@@ -1247,7 +1247,7 @@ def __setup_lightweight_ca_key_retrieval_custodia(self):
         os.chmod(keyfile, 0o600)
         os.chown(keyfile, pent.pw_uid, pent.pw_gid)
 
-    def __add_lightweight_ca_tracking_requests(self):
+    def add_lightweight_ca_tracking_requests(self):
         try:
             lwcas = api.Backend.ldap2.get_entries(
                 base_dn=api.env.basedn,
@@ -1811,11 +1811,10 @@ def add_lightweight_ca_tracking_requests(logger, lwcas):
                     pin=certmonger.get_pin('internal'),
                     nickname=nickname,
                     ca=ipalib.constants.RENEWAL_CA_NAME,
+                    profile='caCACert',
                     pre_command='stop_pkicad',
                     post_command='renew_ca_cert "%s"' % nickname,
                 )
-                request_id = certmonger.get_request_id(criteria)
-                certmonger.modify(request_id, profile='ipaCACertRenewal')
                 logger.debug(
                     'Lightweight CA renewal: '
                     'added tracking request for "%s"', nickname)
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 5951c7d..5aef7cf 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -974,6 +974,21 @@ def certificate_renewal_update(ca, ds, http):
         root_logger.info('CA is not configured')
         return False
 
+    db = certs.CertDB(api.env.realm, paths.PKI_TOMCAT_ALIAS_DIR)
+    for nickname, _trust_flags in db.list_certs():
+        if nickname.startswith('caSigningCert cert-pki-ca '):
+            requests.append(
+                {
+                    'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
+                    'cert-nickname': nickname,
+                    'ca': 'dogtag-ipa-ca-renew-agent',
+                    'cert-presave-command': template % 'stop_pkicad',
+                    'cert-postsave-command':
+                        (template % ('renew_ca_cert "%s"' % nickname)),
+                    'template-profile': 'caCACert',
+                }
+            )
+
     # State not set, lets see if we are already configured
     for request in requests:
         request_id = certmonger.get_request_id(request)
@@ -998,6 +1013,7 @@ def certificate_renewal_update(ca, ds, http):
     ca.configure_renewal()
     ca.configure_agent_renewal()
     ca.track_servercert()
+    ca.add_lightweight_ca_tracking_requests()
     ds.start_tracking_certificates(serverid)
     http.start_tracking_certificates()
 

From f94430d2e8683e79fa6461e8fb42e3aa81c5d987 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 19 Apr 2017 12:55:47 +0000
Subject: [PATCH 4/7] renew agent: allow reusing existing certs

Add a switch which makes `dogtag-ipa-ca-renew-agent-submit` reuse the
existing certificate rather than request a new one from the CA while
maintaining LDAP replication of the certificate.

Make this available as a new `dogtag-ipa-ca-renew-agent-reuse` certmonger
CA.

This allows redoing the LDAP replication and reexecuting pre- and post-save
commands of a tracking request without reissuing the certificate.

https://pagure.io/freeipa/issue/5799
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 67 ++++++++++++++++------
 ipaserver/install/cainstance.py                    |  8 ++-
 ipaserver/install/dogtaginstance.py                | 15 +++--
 3 files changed, 63 insertions(+), 27 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 51b0880..7b54895 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -193,10 +193,18 @@ def call_handler(_handler, *args, **kwargs):
 
     return result
 
-def request_cert():
+
+def request_cert(reuse_existing, **kwargs):
     """
     Request certificate from IPA CA.
     """
+    if reuse_existing:
+        cert = os.environ.get('CERTMONGER_CERTIFICATE')
+        if cert:
+            return (ISSUED, cert)
+        else:
+            return (REJECTED, "New certificate requests not supported")
+
     syslog.syslog(syslog.LOG_NOTICE,
                   "Forwarding request to dogtag-ipa-renew-agent")
 
@@ -231,7 +239,8 @@ def request_cert():
     else:
         return (rc, stdout)
 
-def store_cert():
+
+def store_cert(**kwargs):
     """
     Store certificate in LDAP.
     """
@@ -292,7 +301,8 @@ def store_cert():
 
     return (ISSUED, cert)
 
-def request_and_store_cert():
+
+def request_and_store_cert(**kwargs):
     """
     Request certificate from IPA CA and store it in LDAP.
     """
@@ -318,7 +328,7 @@ def request_and_store_cert():
         else:
             os.environ['CERTMONGER_CA_COOKIE'] = cookie
 
-        result = call_handler(request_cert)
+        result = call_handler(request_cert, **kwargs)
         if result[0] == WAIT:
             return (result[0], 'request:%s' % result[1])
         elif result[0] == WAIT_WITH_DELAY:
@@ -337,7 +347,7 @@ def request_and_store_cert():
         os.environ['CERTMONGER_CA_COOKIE'] = cookie
     os.environ['CERTMONGER_CERTIFICATE'] = cert
 
-    result = call_handler(store_cert)
+    result = call_handler(store_cert, **kwargs)
     if result[0] == WAIT:
         return (result[0], 'store:%s:%s' % (cert, result[1]))
     elif result[0] == WAIT_WITH_DELAY:
@@ -345,7 +355,8 @@ def request_and_store_cert():
     else:
         return result
 
-def retrieve_or_reuse_cert():
+
+def retrieve_or_reuse_cert(**kwargs):
     """
     Retrieve certificate from LDAP. If the certificate is not available, reuse
     the old certificate.
@@ -373,7 +384,8 @@ def retrieve_or_reuse_cert():
 
     return (ISSUED, cert)
 
-def retrieve_cert_continuous():
+
+def retrieve_cert_continuous(reuse_existing, **kwargs):
     """
     Retrieve new certificate from LDAP. Repeat every eight hours until the
     certificate is available.
@@ -382,8 +394,10 @@ def retrieve_cert_continuous():
     if old_cert:
         old_cert = x509.normalize_certificate(old_cert)
 
-    result = call_handler(retrieve_or_reuse_cert)
-    if result[0] != ISSUED:
+    result = call_handler(retrieve_or_reuse_cert,
+                          reuse_existing=reuse_existing,
+                          **kwargs)
+    if result[0] != ISSUED or reuse_existing:
         return result
 
     new_cert = x509.normalize_certificate(result[1])
@@ -394,17 +408,19 @@ def retrieve_cert_continuous():
 
     return result
 
-def retrieve_cert():
+
+def retrieve_cert(**kwargs):
     """
     Retrieve new certificate from LDAP.
     """
-    result = call_handler(retrieve_cert_continuous)
+    result = call_handler(retrieve_cert_continuous, **kwargs)
     if result[0] == WAIT_WITH_DELAY:
         return (REJECTED, "Updated certificate not available")
 
     return result
 
-def export_csr():
+
+def export_csr(**kwargs):
     """
     This does not actually renew the cert, it just writes the CSR provided
     by certmonger to /var/lib/ipa/ca.csr and returns the existing cert.
@@ -430,7 +446,8 @@ def export_csr():
 
     return (ISSUED, cert)
 
-def renew_ca_cert():
+
+def renew_ca_cert(reuse_existing, **kwargs):
     """
     This is used for automatic CA certificate renewal.
     """
@@ -443,7 +460,7 @@ def renew_ca_cert():
     if operation == 'SUBMIT':
         state = 'retrieve'
 
-        if is_self_signed and is_renewal_master():
+        if is_self_signed and not reuse_existing and is_renewal_master():
             state = 'request'
     elif operation == 'POLL':
         cookie = os.environ.get('CERTMONGER_CA_COOKIE')
@@ -460,8 +477,10 @@ def renew_ca_cert():
         return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
 
     if state == 'retrieve':
-        result = call_handler(retrieve_cert)
-        if result[0] == REJECTED and not is_self_signed:
+        result = call_handler(retrieve_cert,
+                              reuse_existing=reuse_existing,
+                              **kwargs)
+        if result[0] == REJECTED and not is_self_signed and not reuse_existing:
             syslog.syslog(syslog.LOG_ALERT,
                           "Certificate with subject '%s' is about to expire, "
                           "use ipa-cacert-manage to renew it"
@@ -469,7 +488,9 @@ def renew_ca_cert():
     elif state == 'request':
         profile = os.environ['CERTMONGER_CA_PROFILE']
         os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
-        result = call_handler(request_and_store_cert)
+        result = call_handler(request_and_store_cert,
+                              reuse_existing=reuse_existing,
+                              **kwargs)
         os.environ['CERTMONGER_CA_PROFILE'] = profile
 
     if result[0] == WAIT:
@@ -480,6 +501,16 @@ def renew_ca_cert():
         return result
 
 def main():
+    kwargs = {
+        'reuse_existing': False,
+    }
+    try:
+        sys.argv.remove('--reuse-existing')
+    except ValueError:
+        pass
+    else:
+        kwargs['reuse_existing'] = True
+
     handlers = {
         'ipaStorage':           store_cert,
         'ipaRetrievalOrReuse':  retrieve_or_reuse_cert,
@@ -515,7 +546,7 @@ def main():
             handler = request_cert
         handler = handlers.get(profile, handler)
 
-        res = call_handler(handler)
+        res = call_handler(handler, **kwargs)
         for item in res[1:]:
             print(item)
         return res[0]
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 8303ae7..e33d555 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -965,9 +965,11 @@ def uninstall(self):
         obj = bus.get_object('org.fedorahosted.certmonger',
                              '/org/fedorahosted/certmonger')
         iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
-        path = iface.find_ca_by_nickname('dogtag-ipa-ca-renew-agent')
-        if path:
-            iface.remove_known_ca(path)
+        for suffix in ['', '-reuse']:
+            name = 'dogtag-ipa-ca-renew-agent' + suffix
+            path = iface.find_ca_by_nickname(name)
+            if path:
+                iface.remove_known_ca(path)
 
         cmonger.stop()
 
diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
index 356358a..e051597 100644
--- a/ipaserver/install/dogtaginstance.py
+++ b/ipaserver/install/dogtaginstance.py
@@ -265,12 +265,15 @@ def configure_certmonger_renewal(self):
         obj = bus.get_object('org.fedorahosted.certmonger',
                              '/org/fedorahosted/certmonger')
         iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
-        path = iface.find_ca_by_nickname('dogtag-ipa-ca-renew-agent')
-        if not path:
-            iface.add_known_ca(
-                'dogtag-ipa-ca-renew-agent',
-                paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT,
-                dbus.Array([], dbus.Signature('s')))
+        for suffix, args in [('', ''), ('-reuse', ' --reuse-existing')]:
+            name = 'dogtag-ipa-ca-renew-agent' + suffix
+            path = iface.find_ca_by_nickname(name)
+            if not path:
+                command = paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT + args
+                iface.add_known_ca(
+                    name,
+                    command,
+                    dbus.Array([], dbus.Signature('s')))
 
     def __get_pin(self):
         try:

From ce0713237a06f16e7786b730028e23d46bab4c7f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 24 Apr 2017 06:20:07 +0000
Subject: [PATCH 5/7] renew agent: always export CSR on IPA CA certificate
 renewal

Make sure a CSR is exported for the IPA CA whenever certmonger detects that
the CA certificate is about to expire.

This is a pre-requisite for using the `dogtag-ipa-ca-renew-agent-reuse` CA
instead of the `ipaCSRExport` virtual profile to export the CSR.

https://pagure.io/freeipa/issue/5799
---
 install/certmonger/dogtag-ipa-ca-renew-agent-submit | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 7b54895..657a1bc 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -451,6 +451,10 @@ def renew_ca_cert(reuse_existing, **kwargs):
     """
     This is used for automatic CA certificate renewal.
     """
+    csr = os.environ.get('CERTMONGER_CSR')
+    if not csr:
+        return (UNCONFIGURED, "Certificate request not provided")
+
     cert = os.environ.get('CERTMONGER_CERTIFICATE')
     if not cert:
         return (REJECTED, "New certificate requests not supported")
@@ -462,6 +466,13 @@ def renew_ca_cert(reuse_existing, **kwargs):
 
         if is_self_signed and not reuse_existing and is_renewal_master():
             state = 'request'
+
+        csr_file = paths.IPA_CA_CSR
+        try:
+            with open(csr_file, 'wb') as f:
+                f.write(csr)
+        except Exception as e:
+            return (UNREACHABLE, "Failed to write %s: %s" % (csr_file, e))
     elif operation == 'POLL':
         cookie = os.environ.get('CERTMONGER_CA_COOKIE')
         if not cookie:

From 317e842eee541e9c82fa4eed096d2a1d5d8585de Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 24 Apr 2017 06:40:11 +0000
Subject: [PATCH 6/7] renew agent: get rid of virtual profiles

Replace all uses of virtual profiles with `dogtag-ipa-ca-renew-agent-reuse`
and remove profile from the IPA CA certificate tracking request.

This prevents virtual profiles from making their way into CSRs and in turn
being rejected by certain CAs. This affected the IPA CA CSR with Microsoft
CS in particular.

https://pagure.io/freeipa/issue/5799
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 43 ++--------------------
 ipaclient/install/ipa_certupdate.py                |  4 +-
 ipalib/install/certmonger.py                       | 25 +++++++++----
 ipaserver/install/cainstance.py                    |  2 +-
 ipaserver/install/ipa_cacert_manage.py             | 12 +++---
 ipaserver/install/server/upgrade.py                |  2 +-
 6 files changed, 32 insertions(+), 56 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 657a1bc..f3dd02d 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -297,7 +297,7 @@ def store_cert(**kwargs):
             syslog.syslog(
                 syslog.LOG_ERR,
                 "Giving up. To retry storing the certificate, resubmit the "
-                "request with profile \"ipaStorage\"")
+                "request with CA \"dogtag-ipa-ca-renew-agent-reuse\"")
 
     return (ISSUED, cert)
 
@@ -420,33 +420,6 @@ def retrieve_cert(**kwargs):
     return result
 
 
-def export_csr(**kwargs):
-    """
-    This does not actually renew the cert, it just writes the CSR provided
-    by certmonger to /var/lib/ipa/ca.csr and returns the existing cert.
-    """
-    operation = os.environ.get('CERTMONGER_OPERATION')
-    if operation != 'SUBMIT':
-        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
-    csr = os.environ.get('CERTMONGER_CSR')
-    if not csr:
-        return (UNCONFIGURED, "Certificate request not provided")
-
-    cert = os.environ.get('CERTMONGER_CERTIFICATE')
-    if not cert:
-        return (REJECTED, "New certificate requests not supported")
-
-    csr_file = paths.IPA_CA_CSR
-    try:
-        with open(csr_file, 'wb') as f:
-            f.write(csr)
-    except Exception as e:
-        return (UNREACHABLE, "Failed to write %s: %s" % (csr_file, e))
-
-    return (ISSUED, cert)
-
-
 def renew_ca_cert(reuse_existing, **kwargs):
     """
     This is used for automatic CA certificate renewal.
@@ -522,14 +495,6 @@ def main():
     else:
         kwargs['reuse_existing'] = True
 
-    handlers = {
-        'ipaStorage':           store_cert,
-        'ipaRetrievalOrReuse':  retrieve_or_reuse_cert,
-        'ipaRetrieval':         retrieve_cert,
-        'ipaCSRExport':         export_csr,
-        'ipaCACertRenewal':     renew_ca_cert,
-    }
-
     api.bootstrap(in_server=True, context='renew', confdir=paths.ETC_IPA)
     api.finalize()
 
@@ -547,15 +512,15 @@ def main():
 
         api.Backend.ldap2.connect()
 
-        profile = os.environ.get('CERTMONGER_CA_PROFILE')
-        if is_replicated():
+        if get_nickname() == IPA_CA_NICKNAME:
+            handler = renew_ca_cert
+        elif is_replicated():
             if is_renewal_master():
                 handler = request_and_store_cert
             else:
                 handler = retrieve_cert_continuous
         else:
             handler = request_cert
-        handler = handlers.get(profile, handler)
 
         res = call_handler(handler, **kwargs)
         for item in res[1:]:
diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py
index d6ffbde..7dc88f0 100644
--- a/ipaclient/install/ipa_certupdate.py
+++ b/ipaclient/install/ipa_certupdate.py
@@ -153,7 +153,7 @@ def update_server(self, certs):
 
             self.log.debug("resubmitting certmonger request '%s'", request_id)
             certmonger.resubmit_request(
-                request_id, profile='ipaRetrievalOrReuse')
+                request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='')
             try:
                 state = certmonger.wait_for_request(request_id, timeout)
             except RuntimeError:
@@ -167,7 +167,7 @@ def update_server(self, certs):
                     "please check the request manually" % request_id)
 
             self.log.debug("modifying certmonger request '%s'", request_id)
-            certmonger.modify(request_id, profile='ipaCACertRenewal')
+            certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent')
 
         self.update_file(paths.CA_CRT, certs)
 
diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
index 2a7876e..5709853 100644
--- a/ipalib/install/certmonger.py
+++ b/ipalib/install/certmonger.py
@@ -501,18 +501,29 @@ def stop_tracking(secdir=None, request_id=None, nickname=None, certfile=None):
         request.parent.obj_if.remove_request(request.path)
 
 
-def modify(request_id, profile=None):
-    if profile:
+def modify(request_id, ca=None, profile=None):
+    if ca or profile:
         request = _get_request({'nickname': request_id})
-        if request:
-            request.obj_if.modify({'template-profile': profile})
+        update = {}
+        if ca is not None:
+            cm = _certmonger()
+            update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
+        if profile is not None:
+            update['template-profile'] = profile
+        request.obj_if.modify(update)
 
 
-def resubmit_request(request_id, profile=None):
+def resubmit_request(request_id, ca=None, profile=None):
     request = _get_request({'nickname': request_id})
     if request:
-        if profile:
-            request.obj_if.modify({'template-profile': profile})
+        if ca or profile:
+            update = {}
+            if ca is not None:
+                cm = _certmonger()
+                update['CA'] = cm.obj_if.find_ca_by_nickname(ca)
+            if profile is not None:
+                update['template-profile'] = profile
+            request.obj_if.modify(update)
         request.obj_if.resubmit()
 
 
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index e33d555..b002c0c 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -282,7 +282,7 @@ class CAInstance(DogtagInstance):
     tracking_reqs = (('auditSigningCert cert-pki-ca', None),
                      ('ocspSigningCert cert-pki-ca', None),
                      ('subsystemCert cert-pki-ca', None),
-                     ('caSigningCert cert-pki-ca', 'ipaCACertRenewal'))
+                     ('caSigningCert cert-pki-ca', None))
     server_cert_name = 'Server-Cert cert-pki-ca'
 
     def __init__(self, realm=None, host_name=None):
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index 363ba37..6d28c62 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -172,14 +172,14 @@ def renew_self_signed(self, ca):
         except errors.NotFound:
             raise admintool.ScriptError("CA renewal master not found")
 
-        self.resubmit_request(ca, 'caCACert')
+        self.resubmit_request()
 
         print("CA certificate successfully renewed")
 
     def renew_external_step_1(self, ca):
         print("Exporting CA certificate signing request, please wait")
 
-        self.resubmit_request(ca, 'ipaCSRExport')
+        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse')
 
         print(("The next step is to get %s signed by your CA and re-run "
               "ipa-cacert-manage as:" % paths.IPA_CA_CSR))
@@ -282,15 +282,15 @@ def renew_external_step_2(self, ca, old_cert_der):
         except errors.NotFound:
             raise admintool.ScriptError("CA renewal master not found")
 
-        self.resubmit_request(ca, 'ipaRetrieval')
+        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse')
 
         print("CA certificate successfully renewed")
 
-    def resubmit_request(self, ca, profile):
+    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent'):
         timeout = api.env.startup_timeout + 60
 
         self.log.debug("resubmitting certmonger request '%s'", self.request_id)
-        certmonger.resubmit_request(self.request_id, profile=profile)
+        certmonger.resubmit_request(self.request_id, ca=ca, profile='')
         try:
             state = certmonger.wait_for_request(self.request_id, timeout)
         except RuntimeError:
@@ -304,7 +304,7 @@ def resubmit_request(self, ca, profile):
                 "please check the request manually" % self.request_id)
 
         self.log.debug("modifying certmonger request '%s'", self.request_id)
-        certmonger.modify(self.request_id, profile='ipaCACertRenewal')
+        certmonger.modify(self.request_id, ca='dogtag-ipa-ca-renew-agent')
 
     def install(self):
         print("Installing CA certificate, please wait")
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 5aef7cf..cd59ef7 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -937,7 +937,7 @@ def certificate_renewal_update(ca, ds, http):
             'cert-presave-command': template % 'stop_pkicad',
             'cert-postsave-command':
                 (template % 'renew_ca_cert "caSigningCert cert-pki-ca"'),
-            'template-profile': 'ipaCACertRenewal',
+            'template-profile': '',
         },
         {
             'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,

From c19f2f4e5923624add5d4d406cf14983501f52e2 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 24 Apr 2017 07:10:41 +0000
Subject: [PATCH 7/7] ipa-cacert-manage: add --external-ca-type

Add the `--external-ca-type`, as known from `ipa-server-install` and
`ipa-ca-install`, to `ipa-cacert-manage`.

This allows creating IPA CA CSRs suitable for use with Microsoft CS using
`ipa-cacert-manage`:

```
ipa-cacert-manage renew --external-ca --external-ca-type=ms-cs
```

https://pagure.io/freeipa/issue/5799
---
 install/tools/man/ipa-cacert-manage.1  |  3 +++
 ipaserver/install/ipa_cacert_manage.py | 21 +++++++++++++++++----
 2 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1
index 128edd8..e36258d 100644
--- a/install/tools/man/ipa-cacert-manage.1
+++ b/install/tools/man/ipa-cacert-manage.1
@@ -78,6 +78,9 @@ Sign the renewed certificate by itself.
 \fB\-\-external\-ca\fR
 Sign the renewed certificate by external CA.
 .TP
+\fB\-\-external\-ca\-type\fR=\fITYPE\fR
+Type of the external CA. Possible values are "generic", "ms-cs". Default value is "generic". Use "ms-cs" to include template name required by Microsoft Certificate Services (MS CS) in the generated CSR.
+.TP
 \fB\-\-external\-cert\-file\fR=\fIFILE\fR
 File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
 .RE
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index 6d28c62..3b732e4 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -54,6 +54,12 @@ def add_options(cls, parser):
             "--self-signed", dest='self_signed',
             action='store_true',
             help="Sign the renewed certificate by itself")
+        ext_cas = ("generic", "ms-cs")
+        renew_group.add_option(
+            "--external-ca-type", dest="external_ca_type",
+            type="choice", choices=ext_cas,
+            metavar="{{{0}}}".format(",".join(ext_cas)),
+            help="Type of the external CA. Default: generic")
         renew_group.add_option(
             "--external-ca", dest='self_signed',
             action='store_false',
@@ -179,7 +185,12 @@ def renew_self_signed(self, ca):
     def renew_external_step_1(self, ca):
         print("Exporting CA certificate signing request, please wait")
 
-        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse')
+        if self.options.external_ca_type == 'ms-cs':
+            profile = 'SubCA'
+        else:
+            profile = ''
+
+        self.resubmit_request('dogtag-ipa-ca-renew-agent-reuse', profile)
 
         print(("The next step is to get %s signed by your CA and re-run "
               "ipa-cacert-manage as:" % paths.IPA_CA_CSR))
@@ -286,11 +297,11 @@ def renew_external_step_2(self, ca, old_cert_der):
 
         print("CA certificate successfully renewed")
 
-    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent'):
+    def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent', profile=''):
         timeout = api.env.startup_timeout + 60
 
         self.log.debug("resubmitting certmonger request '%s'", self.request_id)
-        certmonger.resubmit_request(self.request_id, ca=ca, profile='')
+        certmonger.resubmit_request(self.request_id, ca=ca, profile=profile)
         try:
             state = certmonger.wait_for_request(self.request_id, timeout)
         except RuntimeError:
@@ -304,7 +315,9 @@ def resubmit_request(self, ca='dogtag-ipa-ca-renew-agent'):
                 "please check the request manually" % self.request_id)
 
         self.log.debug("modifying certmonger request '%s'", self.request_id)
-        certmonger.modify(self.request_id, ca='dogtag-ipa-ca-renew-agent')
+        certmonger.modify(self.request_id,
+                          ca='dogtag-ipa-ca-renew-agent',
+                          profile='')
 
     def install(self):
         print("Installing CA certificate, please wait")
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to