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>
This commit is contained in:
Florence Blanc-Renaud 2017-09-05 16:17:31 +02:00 committed by Stanislav Laznicka
parent 62e72c2a53
commit 87540fe1ef
4 changed files with 82 additions and 9 deletions

View File

@ -42,6 +42,7 @@ from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
from ipapython.dn import DN from ipapython.dn import DN
from ipalib import pkcs10, x509, api from ipalib import pkcs10, x509, api
from ipalib.errors import CertificateOperationError from ipalib.errors import CertificateOperationError
from ipalib.install import certstore
from ipalib.text import _ from ipalib.text import _
from ipaplatform.paths import paths from ipaplatform.paths import paths
@ -659,6 +660,31 @@ class CertDB(object):
subject=host, subject=host,
passwd_fname=self.passwd_fname) 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): class _CrossProcessLock(object):
_DATETIME_FORMAT = '%Y%m%d%H%M%S%f' _DATETIME_FORMAT = '%Y%m%d%H%M%S%f'

View File

@ -1033,22 +1033,59 @@ class DsInstance(service.Service):
logger.error( logger.error(
'Unable to restart DS instance %s: %s', ds_instance, e) '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): def stop_tracking_certificates(self, serverid=None):
if serverid is None: if serverid is None:
serverid = self.get_state("serverid") serverid = self.get_state("serverid")
if not serverid is None: if not serverid is None:
nickname = self.get_server_cert_nickname(serverid)
# drop the trailing / off the config_dirname so the directory # drop the trailing / off the config_dirname so the directory
# will match what is in certmonger # will match what is in certmonger
dirname = config_dirname(serverid)[:-1] dirname = config_dirname(serverid)[:-1]
dsdb = certs.CertDB(self.realm, nssdir=dirname) dsdb = certs.CertDB(self.realm, nssdir=dirname)
dsdb.untrack_server_cert(self.nickname) dsdb.untrack_server_cert(nickname)
def start_tracking_certificates(self, serverid): def start_tracking_certificates(self, serverid):
nickname = self.get_server_cert_nickname(serverid)
dirname = config_dirname(serverid)[:-1] dirname = config_dirname(serverid)[:-1]
dsdb = certs.CertDB(self.realm, nssdir=dirname) dsdb = certs.CertDB(self.realm, nssdir=dirname)
dsdb.track_server_cert(self.nickname, self.principal, if dsdb.is_ipa_issued_cert(api, nickname):
dsdb.passwd_fname, dsdb.track_server_cert(nickname, self.principal,
'restart_dirsrv %s' % serverid) 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 # we could probably move this function into the service.Service
# class - it's very generic - all we need is a way to get an # class - it's very generic - all we need is a way to get an

View File

@ -262,6 +262,11 @@ class HTTPInstance(service.Service):
installutils.set_directive( installutils.set_directive(
paths.HTTPD_NSS_CONF, 'NSSNickname', quoted_nickname, quotes=False) 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): def set_mod_nss_protocol(self):
installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol', 'TLSv1.0,TLSv1.1,TLSv1.2', False) 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): def stop_tracking_certificates(self):
db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR) 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): def start_tracking_certificates(self):
db = certs.CertDB(self.realm, nssdir=paths.HTTPD_ALIAS_DIR) db = certs.CertDB(self.realm, nssdir=paths.HTTPD_ALIAS_DIR)
db.track_server_cert(self.cert_nickname, self.principal, nickname = self.get_mod_nss_nickname()
db.passwd_fname, 'restart_httpd') 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): def request_service_keytab(self):
super(HTTPInstance, self).request_service_keytab() super(HTTPInstance, self).request_service_keytab()

View File

@ -972,13 +972,13 @@ def certificate_renewal_update(ca, ds, http):
}, },
{ {
'cert-database': paths.HTTPD_ALIAS_DIR, 'cert-database': paths.HTTPD_ALIAS_DIR,
'cert-nickname': 'Server-Cert', 'cert-nickname': http.get_mod_nss_nickname(),
'ca': 'IPA', 'ca': 'IPA',
'cert-postsave-command': template % 'restart_httpd', 'cert-postsave-command': template % 'restart_httpd',
}, },
{ {
'cert-database': dsinstance.config_dirname(serverid), 'cert-database': dsinstance.config_dirname(serverid),
'cert-nickname': 'Server-Cert', 'cert-nickname': ds.get_server_cert_nickname(serverid),
'ca': 'IPA', 'ca': 'IPA',
'cert-postsave-command': 'cert-postsave-command':
'%s %s' % (template % 'restart_dirsrv', serverid), '%s %s' % (template % 'restart_dirsrv', serverid),