Blob Blame History Raw
From 8c576e8c3640b84869abacc43a74aa250df5a8e9 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Tue, 5 Sep 2017 16:17:31 +0200
Subject: [PATCH] Backport 4-5: Fix ipa-server-upgrade with server cert
 tracking

ipa-server-upgrade fails with Server-Cert not found, when trying to
track httpd/ldap server certificates. There are 2 issues in the upgrade:
- the certificates should be tracked only if they were issued by IPA CA
(it is possible to have CA configured but 3rd part certs)
- the certificate nickname can be different from Server-Cert

The fix provides methods to find the server crt nickname for http and ldap,
and a method to check if the server certs are issued by IPA and need to be
tracked by certmonger.

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

Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
---
 ipaserver/install/certs.py          | 27 ++++++++++++++++++++++
 ipaserver/install/dsinstance.py     | 45 +++++++++++++++++++++++++++++++++----
 ipaserver/install/httpinstance.py   | 16 ++++++++++---
 ipaserver/install/server/upgrade.py |  4 ++--
 4 files changed, 83 insertions(+), 9 deletions(-)

diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 02c479d92511fcf4043e7d6798c85cf8256c3299..de96318db51b03f2515814d574cfebf1b242b6a6 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -42,6 +42,7 @@ from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
 from ipapython.dn import DN
 from ipalib import pkcs10, x509, api
 from ipalib.errors import CertificateOperationError
+from ipalib.install import certstore
 from ipalib.text import _
 from ipaplatform.paths import paths

@@ -669,6 +670,32 @@ class CertDB(object):
                                              subject=host,
                                              passwd_fname=self.passwd_fname)

+    def is_ipa_issued_cert(self, api, nickname):
+        """
+        Return True if the certificate contained in the CertDB with the
+        provided nickname has been issued by IPA.
+
+        Note that this method can only be executed if api has been initialized
+        """
+        # This method needs to compare the cert issuer (from the NSS DB
+        # and the subject from the CA (from LDAP), because nicknames are not
+        # always aligned.
+
+        cacert_subject = certstore.get_ca_subject(
+            api.Backend.ldap2,
+            api.env.container_ca,
+            api.env.basedn)
+
+        # The cert can be issued directly by IPA. In this case, the cert
+        # issuer is IPA CA subject.
+        cert = self.get_cert_from_db(nickname)
+        if cert is None:
+            raise RuntimeError("Could not find the cert %s in %s"
+                               % (nickname, self.secdir))
+        issuer = DN(x509.load_certificate(cert).issuer)
+
+        return issuer == cacert_subject
+

 class _CrossProcessLock(object):
     _DATETIME_FORMAT = '%Y%m%d%H%M%S%f'
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 39248edb285ee4d792b4500d83d88b24f5732d10..c9db8ac28c3ca10539b745ca09f4d8aaece02e0c 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -1028,22 +1028,59 @@ class DsInstance(service.Service):
                 root_logger.error(
                     'Unable to restart DS instance %s: %s', ds_instance, e)

+    def get_server_cert_nickname(self, serverid=None):
+        """
+        Retrieve the nickname of the server cert used by dirsrv.
+
+        The method directly reads the dse.ldif to find the attribute
+        nsSSLPersonalitySSL of cn=RSA,cn=encryption,cn=config because
+        LDAP is not always accessible when we need to get the nickname
+        (for instance during uninstall).
+        """
+        if serverid is None:
+            serverid = self.get_state("serverid")
+        if serverid is not None:
+            dirname = config_dirname(serverid)
+            config_file = os.path.join(dirname, "dse.ldif")
+            rsa_dn = "cn=RSA,cn=encryption,cn=config"
+            with open(config_file, "r") as in_file:
+                parser = upgradeinstance.GetEntryFromLDIF(
+                    in_file,
+                    entries_dn=[rsa_dn])
+                parser.parse()
+                try:
+                    config_entry = parser.get_results()[rsa_dn]
+                    nickname = config_entry["nsSSLPersonalitySSL"][0]
+                    return nickname.decode('utf-8')
+                except (KeyError, IndexError):
+                    root_logger.error("Unable to find server cert nickname in "
+                                      "%s", config_file)
+
+        root_logger.debug("Falling back to nickname Server-Cert")
+        return 'Server-Cert'
+
     def stop_tracking_certificates(self, serverid=None):
         if serverid is None:
             serverid = self.get_state("serverid")
         if not serverid is None:
+            nickname = self.get_server_cert_nickname(serverid)
             # drop the trailing / off the config_dirname so the directory
             # will match what is in certmonger
             dirname = config_dirname(serverid)[:-1]
             dsdb = certs.CertDB(self.realm, nssdir=dirname)
-            dsdb.untrack_server_cert(self.nickname)
+            dsdb.untrack_server_cert(nickname)

     def start_tracking_certificates(self, serverid):
+        nickname = self.get_server_cert_nickname(serverid)
         dirname = config_dirname(serverid)[:-1]
         dsdb = certs.CertDB(self.realm, nssdir=dirname)
-        dsdb.track_server_cert(self.nickname, self.principal,
-                               dsdb.passwd_fname,
-                               'restart_dirsrv %s' % serverid)
+        if dsdb.is_ipa_issued_cert(api, nickname):
+            dsdb.track_server_cert(nickname, self.principal,
+                                   dsdb.passwd_fname,
+                                   'restart_dirsrv %s' % serverid)
+        else:
+            root_logger.debug("Will not track DS server certificate %s as it "
+                              "is not issued by IPA", nickname)

     # we could probably move this function into the service.Service
     # class - it's very generic - all we need is a way to get an
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index f637b97db8f21ddbc00c4f70e18e836d300b2f33..e55edebc5d4e45d7cb4cb66d28a270e6d6a56e33 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -266,6 +266,11 @@ class HTTPInstance(service.Service):
         installutils.set_directive(
             paths.HTTPD_NSS_CONF, 'NSSNickname', quoted_nickname, quotes=False)

+    def get_mod_nss_nickname(self):
+        cert = installutils.get_directive(paths.HTTPD_NSS_CONF, 'NSSNickname')
+        nickname = installutils.unquote_directive_value(cert, quote_char="'")
+        return nickname
+
     def set_mod_nss_protocol(self):
         installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol', 'TLSv1.0,TLSv1.1,TLSv1.2', False)

@@ -582,12 +587,17 @@ class HTTPInstance(service.Service):

     def stop_tracking_certificates(self):
         db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR)
-        db.untrack_server_cert(self.cert_nickname)
+        db.untrack_server_cert(self.get_mod_nss_nickname())

     def start_tracking_certificates(self):
         db = certs.CertDB(self.realm, nssdir=paths.HTTPD_ALIAS_DIR)
-        db.track_server_cert(self.cert_nickname, self.principal,
-                             db.passwd_fname, 'restart_httpd')
+        nickname = self.get_mod_nss_nickname()
+        if db.is_ipa_issued_cert(api, nickname):
+            db.track_server_cert(nickname, self.principal,
+                                 db.passwd_fname, 'restart_httpd')
+        else:
+            root_logger.debug("Will not track HTTP server cert %s as it is "
+                              "not issued by IPA", nickname)

     def request_service_keytab(self):
         super(HTTPInstance, self).request_service_keytab()
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 109e922e3a3ea25f882fdd81765788a3881e87bd..0947766c076251e7608241803d3a1eabee65ae11 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -957,13 +957,13 @@ def certificate_renewal_update(ca, ds, http):
         },
         {
             'cert-database': paths.HTTPD_ALIAS_DIR,
-            'cert-nickname': 'Server-Cert',
+            'cert-nickname': http.get_mod_nss_nickname(),
             'ca': 'IPA',
             'cert-postsave-command': template % 'restart_httpd',
         },
         {
             'cert-database': dsinstance.config_dirname(serverid),
-            'cert-nickname': 'Server-Cert',
+            'cert-nickname': ds.get_server_cert_nickname(serverid),
             'ca': 'IPA',
             'cert-postsave-command':
                 '%s %s' % (template % 'restart_dirsrv', serverid),
--
2.13.5