From 726a8b269c8843e495168deea3a4951c61de0e72 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Sep 19 2017 13:07:47 +0000 Subject: 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 Reviewed-By: Fraser Tweedale --- diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index dbea3ba..b122839 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 @@ -659,6 +660,31 @@ 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)) + + return DN(cert.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 3eeb7f7..4ec6cee 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -1033,22 +1033,59 @@ class DsInstance(service.Service): 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): + logger.error("Unable to find server cert nickname in %s", + config_file) + + 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: + 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 b8afc41..8f3b593 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -262,6 +262,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) @@ -578,12 +583,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: + 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 0bc1de3..a3a2864 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -972,13 +972,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),