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