483b06
From 8c576e8c3640b84869abacc43a74aa250df5a8e9 Mon Sep 17 00:00:00 2001
483b06
From: Florence Blanc-Renaud <flo@redhat.com>
483b06
Date: Tue, 5 Sep 2017 16:17:31 +0200
483b06
Subject: [PATCH] Backport 4-5: Fix ipa-server-upgrade with server cert
483b06
 tracking
483b06
483b06
ipa-server-upgrade fails with Server-Cert not found, when trying to
483b06
track httpd/ldap server certificates. There are 2 issues in the upgrade:
483b06
- the certificates should be tracked only if they were issued by IPA CA
483b06
(it is possible to have CA configured but 3rd part certs)
483b06
- the certificate nickname can be different from Server-Cert
483b06
483b06
The fix provides methods to find the server crt nickname for http and ldap,
483b06
and a method to check if the server certs are issued by IPA and need to be
483b06
tracked by certmonger.
483b06
483b06
https://pagure.io/freeipa/issue/7141
483b06
483b06
Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
483b06
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
483b06
---
483b06
 ipaserver/install/certs.py          | 27 ++++++++++++++++++++++
483b06
 ipaserver/install/dsinstance.py     | 45 +++++++++++++++++++++++++++++++++----
483b06
 ipaserver/install/httpinstance.py   | 16 ++++++++++---
483b06
 ipaserver/install/server/upgrade.py |  4 ++--
483b06
 4 files changed, 83 insertions(+), 9 deletions(-)
483b06
483b06
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
483b06
index 02c479d92511fcf4043e7d6798c85cf8256c3299..de96318db51b03f2515814d574cfebf1b242b6a6 100644
483b06
--- a/ipaserver/install/certs.py
483b06
+++ b/ipaserver/install/certs.py
483b06
@@ -42,6 +42,7 @@ from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
483b06
 from ipapython.dn import DN
483b06
 from ipalib import pkcs10, x509, api
483b06
 from ipalib.errors import CertificateOperationError
483b06
+from ipalib.install import certstore
483b06
 from ipalib.text import _
483b06
 from ipaplatform.paths import paths
483b06
483b06
@@ -669,6 +670,32 @@ class CertDB(object):
483b06
                                              subject=host,
483b06
                                              passwd_fname=self.passwd_fname)
483b06
483b06
+    def is_ipa_issued_cert(self, api, nickname):
483b06
+        """
483b06
+        Return True if the certificate contained in the CertDB with the
483b06
+        provided nickname has been issued by IPA.
483b06
+
483b06
+        Note that this method can only be executed if api has been initialized
483b06
+        """
483b06
+        # This method needs to compare the cert issuer (from the NSS DB
483b06
+        # and the subject from the CA (from LDAP), because nicknames are not
483b06
+        # always aligned.
483b06
+
483b06
+        cacert_subject = certstore.get_ca_subject(
483b06
+            api.Backend.ldap2,
483b06
+            api.env.container_ca,
483b06
+            api.env.basedn)
483b06
+
483b06
+        # The cert can be issued directly by IPA. In this case, the cert
483b06
+        # issuer is IPA CA subject.
483b06
+        cert = self.get_cert_from_db(nickname)
483b06
+        if cert is None:
483b06
+            raise RuntimeError("Could not find the cert %s in %s"
483b06
+                               % (nickname, self.secdir))
483b06
+        issuer = DN(x509.load_certificate(cert).issuer)
483b06
+
483b06
+        return issuer == cacert_subject
483b06
+
483b06
483b06
 class _CrossProcessLock(object):
483b06
     _DATETIME_FORMAT = '%Y%m%d%H%M%S%f'
483b06
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
483b06
index 39248edb285ee4d792b4500d83d88b24f5732d10..c9db8ac28c3ca10539b745ca09f4d8aaece02e0c 100644
483b06
--- a/ipaserver/install/dsinstance.py
483b06
+++ b/ipaserver/install/dsinstance.py
483b06
@@ -1028,22 +1028,59 @@ class DsInstance(service.Service):
483b06
                 root_logger.error(
483b06
                     'Unable to restart DS instance %s: %s', ds_instance, e)
483b06
483b06
+    def get_server_cert_nickname(self, serverid=None):
483b06
+        """
483b06
+        Retrieve the nickname of the server cert used by dirsrv.
483b06
+
483b06
+        The method directly reads the dse.ldif to find the attribute
483b06
+        nsSSLPersonalitySSL of cn=RSA,cn=encryption,cn=config because
483b06
+        LDAP is not always accessible when we need to get the nickname
483b06
+        (for instance during uninstall).
483b06
+        """
483b06
+        if serverid is None:
483b06
+            serverid = self.get_state("serverid")
483b06
+        if serverid is not None:
483b06
+            dirname = config_dirname(serverid)
483b06
+            config_file = os.path.join(dirname, "dse.ldif")
483b06
+            rsa_dn = "cn=RSA,cn=encryption,cn=config"
483b06
+            with open(config_file, "r") as in_file:
483b06
+                parser = upgradeinstance.GetEntryFromLDIF(
483b06
+                    in_file,
483b06
+                    entries_dn=[rsa_dn])
483b06
+                parser.parse()
483b06
+                try:
483b06
+                    config_entry = parser.get_results()[rsa_dn]
483b06
+                    nickname = config_entry["nsSSLPersonalitySSL"][0]
483b06
+                    return nickname.decode('utf-8')
483b06
+                except (KeyError, IndexError):
483b06
+                    root_logger.error("Unable to find server cert nickname in "
483b06
+                                      "%s", config_file)
483b06
+
483b06
+        root_logger.debug("Falling back to nickname Server-Cert")
483b06
+        return 'Server-Cert'
483b06
+
483b06
     def stop_tracking_certificates(self, serverid=None):
483b06
         if serverid is None:
483b06
             serverid = self.get_state("serverid")
483b06
         if not serverid is None:
483b06
+            nickname = self.get_server_cert_nickname(serverid)
483b06
             # drop the trailing / off the config_dirname so the directory
483b06
             # will match what is in certmonger
483b06
             dirname = config_dirname(serverid)[:-1]
483b06
             dsdb = certs.CertDB(self.realm, nssdir=dirname)
483b06
-            dsdb.untrack_server_cert(self.nickname)
483b06
+            dsdb.untrack_server_cert(nickname)
483b06
483b06
     def start_tracking_certificates(self, serverid):
483b06
+        nickname = self.get_server_cert_nickname(serverid)
483b06
         dirname = config_dirname(serverid)[:-1]
483b06
         dsdb = certs.CertDB(self.realm, nssdir=dirname)
483b06
-        dsdb.track_server_cert(self.nickname, self.principal,
483b06
-                               dsdb.passwd_fname,
483b06
-                               'restart_dirsrv %s' % serverid)
483b06
+        if dsdb.is_ipa_issued_cert(api, nickname):
483b06
+            dsdb.track_server_cert(nickname, self.principal,
483b06
+                                   dsdb.passwd_fname,
483b06
+                                   'restart_dirsrv %s' % serverid)
483b06
+        else:
483b06
+            root_logger.debug("Will not track DS server certificate %s as it "
483b06
+                              "is not issued by IPA", nickname)
483b06
483b06
     # we could probably move this function into the service.Service
483b06
     # class - it's very generic - all we need is a way to get an
483b06
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
483b06
index f637b97db8f21ddbc00c4f70e18e836d300b2f33..e55edebc5d4e45d7cb4cb66d28a270e6d6a56e33 100644
483b06
--- a/ipaserver/install/httpinstance.py
483b06
+++ b/ipaserver/install/httpinstance.py
483b06
@@ -266,6 +266,11 @@ class HTTPInstance(service.Service):
483b06
         installutils.set_directive(
483b06
             paths.HTTPD_NSS_CONF, 'NSSNickname', quoted_nickname, quotes=False)
483b06
483b06
+    def get_mod_nss_nickname(self):
483b06
+        cert = installutils.get_directive(paths.HTTPD_NSS_CONF, 'NSSNickname')
483b06
+        nickname = installutils.unquote_directive_value(cert, quote_char="'")
483b06
+        return nickname
483b06
+
483b06
     def set_mod_nss_protocol(self):
483b06
         installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol', 'TLSv1.0,TLSv1.1,TLSv1.2', False)
483b06
483b06
@@ -582,12 +587,17 @@ class HTTPInstance(service.Service):
483b06
483b06
     def stop_tracking_certificates(self):
483b06
         db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR)
483b06
-        db.untrack_server_cert(self.cert_nickname)
483b06
+        db.untrack_server_cert(self.get_mod_nss_nickname())
483b06
483b06
     def start_tracking_certificates(self):
483b06
         db = certs.CertDB(self.realm, nssdir=paths.HTTPD_ALIAS_DIR)
483b06
-        db.track_server_cert(self.cert_nickname, self.principal,
483b06
-                             db.passwd_fname, 'restart_httpd')
483b06
+        nickname = self.get_mod_nss_nickname()
483b06
+        if db.is_ipa_issued_cert(api, nickname):
483b06
+            db.track_server_cert(nickname, self.principal,
483b06
+                                 db.passwd_fname, 'restart_httpd')
483b06
+        else:
483b06
+            root_logger.debug("Will not track HTTP server cert %s as it is "
483b06
+                              "not issued by IPA", nickname)
483b06
483b06
     def request_service_keytab(self):
483b06
         super(HTTPInstance, self).request_service_keytab()
483b06
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
483b06
index 109e922e3a3ea25f882fdd81765788a3881e87bd..0947766c076251e7608241803d3a1eabee65ae11 100644
483b06
--- a/ipaserver/install/server/upgrade.py
483b06
+++ b/ipaserver/install/server/upgrade.py
483b06
@@ -957,13 +957,13 @@ def certificate_renewal_update(ca, ds, http):
483b06
         },
483b06
         {
483b06
             'cert-database': paths.HTTPD_ALIAS_DIR,
483b06
-            'cert-nickname': 'Server-Cert',
483b06
+            'cert-nickname': http.get_mod_nss_nickname(),
483b06
             'ca': 'IPA',
483b06
             'cert-postsave-command': template % 'restart_httpd',
483b06
         },
483b06
         {
483b06
             'cert-database': dsinstance.config_dirname(serverid),
483b06
-            'cert-nickname': 'Server-Cert',
483b06
+            'cert-nickname': ds.get_server_cert_nickname(serverid),
483b06
             'ca': 'IPA',
483b06
             'cert-postsave-command':
483b06
                 '%s %s' % (template % 'restart_dirsrv', serverid),
483b06
--
483b06
2.13.5