From 8c576e8c3640b84869abacc43a74aa250df5a8e9 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud 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 Reviewed-By: Fraser Tweedale --- 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