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