|
|
f00d0e |
From f762f8f5e2f9b6d66d786b426d4d2fe40c994192 Mon Sep 17 00:00:00 2001
|
|
|
f00d0e |
From: Antonio Torres <antorres@redhat.com>
|
|
|
f00d0e |
Date: Fri, 23 Apr 2021 17:42:21 +0200
|
|
|
f00d0e |
Subject: [PATCH] Add checks to detect mismatch of certificates
|
|
|
f00d0e |
|
|
|
f00d0e |
Add checks to detect mismatch of certificates between LDAP
|
|
|
f00d0e |
and NSS databases. Check for existance of entries as well as
|
|
|
f00d0e |
ensure the certificates match between the different databases.
|
|
|
f00d0e |
|
|
|
f00d0e |
Related: https://bugzilla.redhat.com/show_bug.cgi?id=1886770
|
|
|
f00d0e |
Signed-off-by: Antonio Torres <antorres@redhat.com>
|
|
|
f00d0e |
---
|
|
|
f00d0e |
README.md | 24 ++++
|
|
|
f00d0e |
src/ipahealthcheck/ipa/certs.py | 219 ++++++++++++++++++++++++++++++++
|
|
|
f00d0e |
2 files changed, 243 insertions(+)
|
|
|
f00d0e |
|
|
|
f00d0e |
diff --git a/README.md b/README.md
|
|
|
f00d0e |
index 0f3ed6a..11e0e88 100644
|
|
|
f00d0e |
--- a/README.md
|
|
|
f00d0e |
+++ b/README.md
|
|
|
f00d0e |
@@ -507,6 +507,30 @@ The trust for certificates stored in NSS databases is compared against a known g
|
|
|
f00d0e |
}
|
|
|
f00d0e |
}
|
|
|
f00d0e |
|
|
|
f00d0e |
+### IPACertMatchCheck
|
|
|
f00d0e |
+Ensure CA certificate entries in LDAP and NSS databases match.
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ {
|
|
|
f00d0e |
+ "source": "ipahealthcheck.ipa.certs",
|
|
|
f00d0e |
+ "check": "IPACertMatchCheck",
|
|
|
f00d0e |
+ "result": "ERROR",
|
|
|
f00d0e |
+ "kw": {
|
|
|
f00d0e |
+ "msg": "CA Certificate from /etc/ipa/nssdb does not match /etc/ipa/ca.crt"
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+### IPADogtagCertsMatchCheck
|
|
|
f00d0e |
+Check if Dogtag certificates present in both NSS DB and LDAP match.
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ {
|
|
|
f00d0e |
+ "source": "ipahealthcheck.ipa.certs",
|
|
|
f00d0e |
+ "check": "IPADogtagCertsMatchCheck",
|
|
|
f00d0e |
+ "result": "ERROR",
|
|
|
f00d0e |
+ "kw": {
|
|
|
f00d0e |
+ "msg": "'subsystemCert cert-pki-ca' certificate in NSS DB does not match entry in LDAP"
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+
|
|
|
f00d0e |
### IPANSSChainValidation
|
|
|
f00d0e |
Validate the certificate chain of the NSS certificates. This executes: certutil -V -u V -e -d [dbdir] -n [nickname].
|
|
|
f00d0e |
|
|
|
f00d0e |
diff --git a/src/ipahealthcheck/ipa/certs.py b/src/ipahealthcheck/ipa/certs.py
|
|
|
f00d0e |
index c668093..82435f3 100644
|
|
|
f00d0e |
--- a/src/ipahealthcheck/ipa/certs.py
|
|
|
f00d0e |
+++ b/src/ipahealthcheck/ipa/certs.py
|
|
|
f00d0e |
@@ -29,6 +29,7 @@ from ipaserver.plugins import ldap2
|
|
|
f00d0e |
from ipapython import certdb
|
|
|
f00d0e |
from ipapython import ipautil
|
|
|
f00d0e |
from ipapython.dn import DN
|
|
|
f00d0e |
+from ipapython.ipaldap import realm_to_serverid
|
|
|
f00d0e |
|
|
|
f00d0e |
logger = logging.getLogger()
|
|
|
f00d0e |
DAY = 60 * 60 * 24
|
|
|
f00d0e |
@@ -587,6 +588,224 @@ class IPACertNSSTrust(IPAPlugin):
|
|
|
f00d0e |
'verifying trust')
|
|
|
f00d0e |
|
|
|
f00d0e |
|
|
|
f00d0e |
+@registry
|
|
|
f00d0e |
+class IPACertMatchCheck(IPAPlugin):
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+ Ensure certificates match between LDAP and NSS databases
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ requires = ('dirsrv',)
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ def get_cert_list_from_db(self, nssdb, nickname):
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+ Retrieve all certificates from an NSS database for nickname.
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ args = ["-L", "-n", nickname, "-a"]
|
|
|
f00d0e |
+ result = nssdb.run_certutil(args, capture_output=True)
|
|
|
f00d0e |
+ return x509.load_certificate_list(result.raw_output)
|
|
|
f00d0e |
+ except ipautil.CalledProcessError:
|
|
|
f00d0e |
+ return []
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ @duration
|
|
|
f00d0e |
+ def check(self):
|
|
|
f00d0e |
+ if not self.ca.is_configured():
|
|
|
f00d0e |
+ logger.debug("No CA configured, skipping certificate match check")
|
|
|
f00d0e |
+ return
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ # Ensure /etc/ipa/ca.crt matches the NSS DB CA certificates
|
|
|
f00d0e |
+ def match_cacert_and_db(plugin, cacerts, dbpath):
|
|
|
f00d0e |
+ db = certs.CertDB(api.env.realm, dbpath)
|
|
|
f00d0e |
+ nickname = '%s IPA CA' % api.env.realm
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ dbcacerts = self.get_cert_list_from_db(db, nickname)
|
|
|
f00d0e |
+ except Exception as e:
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ error=str(e),
|
|
|
f00d0e |
+ msg='Unable to load CA cert: {error}')
|
|
|
f00d0e |
+ return False
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ ok = True
|
|
|
f00d0e |
+ for cert in dbcacerts:
|
|
|
f00d0e |
+ if cert not in cacerts:
|
|
|
f00d0e |
+ ok = False
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ nickname=nickname,
|
|
|
f00d0e |
+ serial_number=cert.serial_number,
|
|
|
f00d0e |
+ dbdir=dbpath,
|
|
|
f00d0e |
+ certdir=paths.IPA_CA_CRT,
|
|
|
f00d0e |
+ msg=('CA Certificate nickname {nickname} '
|
|
|
f00d0e |
+ 'with serial number {serial} '
|
|
|
f00d0e |
+ 'is in {dbdir} but is not in'
|
|
|
f00d0e |
+ '%s' % paths.IPA_CA_CRT))
|
|
|
f00d0e |
+ return ok
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ cacerts = x509.load_certificate_list_from_file(paths.IPA_CA_CRT)
|
|
|
f00d0e |
+ except Exception:
|
|
|
f00d0e |
+ yield Result(self, constants.ERROR,
|
|
|
f00d0e |
+ path=paths.IPA_CA_CRT,
|
|
|
f00d0e |
+ msg='Unable to load CA cert file {path}: {error}')
|
|
|
f00d0e |
+ return
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ # Ensure CA cert entry from LDAP matches /etc/ipa/ca.crt
|
|
|
f00d0e |
+ dn = DN('cn=%s IPA CA' % api.env.realm,
|
|
|
f00d0e |
+ 'cn=certificates,cn=ipa,cn=etc',
|
|
|
f00d0e |
+ api.env.basedn)
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ entry = self.conn.get_entry(dn)
|
|
|
f00d0e |
+ except errors.NotFound:
|
|
|
f00d0e |
+ yield Result(self, constants.ERROR,
|
|
|
f00d0e |
+ dn=str(dn),
|
|
|
f00d0e |
+ msg='CA Certificate entry \'{dn}\' '
|
|
|
f00d0e |
+ 'not found in LDAP')
|
|
|
f00d0e |
+ return
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ cacerts_ok = True
|
|
|
f00d0e |
+ # Are all the certs in LDAP for the IPA CA in /etc/ipa/ca.crt
|
|
|
f00d0e |
+ for cert in entry['CACertificate']:
|
|
|
f00d0e |
+ if cert not in cacerts:
|
|
|
f00d0e |
+ cacerts_ok = False
|
|
|
f00d0e |
+ yield Result(self, constants.ERROR,
|
|
|
f00d0e |
+ dn=str(dn),
|
|
|
f00d0e |
+ serial_number=cert.serial_number,
|
|
|
f00d0e |
+ msg=('CA Certificate serial number {serial} is '
|
|
|
f00d0e |
+ 'in LDAP \'{dn}\' but is not in '
|
|
|
f00d0e |
+ '%s' % paths.IPA_CA_CRT))
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ # Ensure NSS DBs have matching CA certs for /etc/ipa/ca.crt
|
|
|
f00d0e |
+ serverid = realm_to_serverid(api.env.realm)
|
|
|
f00d0e |
+ dspath = paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % serverid
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ cacertds_ok = yield from match_cacert_and_db(self, cacerts, dspath)
|
|
|
f00d0e |
+ cacertnss_ok = yield from match_cacert_and_db(self, cacerts,
|
|
|
f00d0e |
+ paths.IPA_NSSDB_DIR)
|
|
|
f00d0e |
+ if cacerts_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=paths.IPA_CA_CRT)
|
|
|
f00d0e |
+ if cacertds_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=dspath)
|
|
|
f00d0e |
+ if cacertnss_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=paths.IPA_NSSDB_DIR)
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+@registry
|
|
|
f00d0e |
+class IPADogtagCertsMatchCheck(IPAPlugin):
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+ Check if dogtag certs present in both NSS DB and LDAP match
|
|
|
f00d0e |
+ """
|
|
|
f00d0e |
+ requires = ('dirsrv',)
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ @duration
|
|
|
f00d0e |
+ def check(self):
|
|
|
f00d0e |
+ if not self.ca.is_configured():
|
|
|
f00d0e |
+ logger.debug('CA is not configured, skipping connectivity check')
|
|
|
f00d0e |
+ return
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ def match_ldap_nss_cert(plugin, ldap, db, cert_dn, attr, cert_nick):
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ entry = ldap.get_entry(cert_dn)
|
|
|
f00d0e |
+ except errors.NotFound:
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ msg='%s entry not found in LDAP' % cert_dn)
|
|
|
f00d0e |
+ return False
|
|
|
f00d0e |
+ try:
|
|
|
f00d0e |
+ nsscert = db.get_cert_from_db(cert_nick)
|
|
|
f00d0e |
+ except Exception as e:
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ error=str(e),
|
|
|
f00d0e |
+ msg=('Unable to load %s certificate:'
|
|
|
f00d0e |
+ '{error}' % cert_nick))
|
|
|
f00d0e |
+ return False
|
|
|
f00d0e |
+ cert_matched = any([cert == nsscert for cert in entry[attr]])
|
|
|
f00d0e |
+ if not cert_matched:
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ key=cert_nick,
|
|
|
f00d0e |
+ nickname=cert_nick,
|
|
|
f00d0e |
+ dbdir=db.secdir,
|
|
|
f00d0e |
+ msg=('{nickname} certificate in NSS DB {dbdir} '
|
|
|
f00d0e |
+ 'does not match entry in LDAP'))
|
|
|
f00d0e |
+ return False
|
|
|
f00d0e |
+ return True
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ def match_ldap_nss_certs_by_subject(plugin, ldap, db, dn,
|
|
|
f00d0e |
+ expected_nicks_subjects):
|
|
|
f00d0e |
+ entries = ldap.get_entries(dn)
|
|
|
f00d0e |
+ all_ok = True
|
|
|
f00d0e |
+ for nick, subject in expected_nicks_subjects.items():
|
|
|
f00d0e |
+ cert = db.get_cert_from_db(nick)
|
|
|
f00d0e |
+ ok = any([cert in entry['userCertificate'] and
|
|
|
f00d0e |
+ subject == entry['subjectName'][0]
|
|
|
f00d0e |
+ for entry in entries
|
|
|
f00d0e |
+ if 'userCertificate' in entry])
|
|
|
f00d0e |
+ if not ok:
|
|
|
f00d0e |
+ all_ok = False
|
|
|
f00d0e |
+ yield Result(plugin, constants.ERROR,
|
|
|
f00d0e |
+ key=nick,
|
|
|
f00d0e |
+ nickname=nick,
|
|
|
f00d0e |
+ dbdir=db.secdir,
|
|
|
f00d0e |
+ msg=('{nickname} certificate in NSS DB '
|
|
|
f00d0e |
+ '{dbdir} does not match entry in LDAP'))
|
|
|
f00d0e |
+ return all_ok
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ db = certs.CertDB(api.env.realm, paths.PKI_TOMCAT_ALIAS_DIR)
|
|
|
f00d0e |
+ dn = DN('uid=pkidbuser,ou=people,o=ipaca')
|
|
|
f00d0e |
+ subsystem_nick = 'subsystemCert cert-pki-ca'
|
|
|
f00d0e |
+ subsystem_ok = yield from match_ldap_nss_cert(self, self.conn,
|
|
|
f00d0e |
+ db, dn,
|
|
|
f00d0e |
+ 'userCertificate',
|
|
|
f00d0e |
+ subsystem_nick)
|
|
|
f00d0e |
+ dn = DN('cn=%s IPA CA' % api.env.realm,
|
|
|
f00d0e |
+ 'cn=certificates,cn=ipa,cn=etc',
|
|
|
f00d0e |
+ api.env.basedn)
|
|
|
f00d0e |
+ casigning_nick = 'caSigningCert cert-pki-ca'
|
|
|
f00d0e |
+ casigning_ok = yield from match_ldap_nss_cert(self, self.conn,
|
|
|
f00d0e |
+ db, dn, 'CACertificate',
|
|
|
f00d0e |
+ casigning_nick)
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ expected_nicks_subjects = {
|
|
|
f00d0e |
+ 'ocspSigningCert cert-pki-ca':
|
|
|
f00d0e |
+ 'CN=OCSP Subsystem,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ 'subsystemCert cert-pki-ca':
|
|
|
f00d0e |
+ 'CN=CA Subsystem,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ 'auditSigningCert cert-pki-ca':
|
|
|
f00d0e |
+ 'CN=CA Audit,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ 'Server-Cert cert-pki-ca':
|
|
|
f00d0e |
+ 'CN=%s,O=%s' % (api.env.host, api.env.realm),
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ kra = krainstance.KRAInstance(api.env.realm)
|
|
|
f00d0e |
+ if kra.is_installed():
|
|
|
f00d0e |
+ kra_expected_nicks_subjects = {
|
|
|
f00d0e |
+ 'transportCert cert-pki-kra':
|
|
|
f00d0e |
+ 'CN=KRA Transport Certificate,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ 'storageCert cert-pki-kra':
|
|
|
f00d0e |
+ 'CN=KRA Storage Certificate,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ 'auditSigningCert cert-pki-kra':
|
|
|
f00d0e |
+ 'CN=KRA Audit,O=%s' % api.env.realm,
|
|
|
f00d0e |
+ }
|
|
|
f00d0e |
+ expected_nicks_subjects.update(kra_expected_nicks_subjects)
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ ipaca_basedn = DN('ou=certificateRepository,ou=ca,o=ipaca')
|
|
|
f00d0e |
+ ipaca_certs_ok = yield from match_ldap_nss_certs_by_subject(
|
|
|
f00d0e |
+ self, self.conn, db,
|
|
|
f00d0e |
+ ipaca_basedn,
|
|
|
f00d0e |
+ expected_nicks_subjects
|
|
|
f00d0e |
+ )
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+ if subsystem_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=subsystem_nick)
|
|
|
f00d0e |
+ if casigning_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=casigning_nick)
|
|
|
f00d0e |
+ if ipaca_certs_ok:
|
|
|
f00d0e |
+ yield Result(self, constants.SUCCESS,
|
|
|
f00d0e |
+ key=str(ipaca_basedn))
|
|
|
f00d0e |
+
|
|
|
f00d0e |
+
|
|
|
f00d0e |
@registry
|
|
|
f00d0e |
class IPANSSChainValidation(IPAPlugin):
|
|
|
f00d0e |
"""Validate the certificate chain of the certs."""
|
|
|
f00d0e |
--
|
|
|
f00d0e |
2.26.3
|
|
|
f00d0e |
|