From 8cfcc85ced6cc905ca670d82088601fe13c03d64 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Sep 07 2021 14:26:30 +0000 Subject: import ipa-healthcheck-0.7-6.module+el8.5.0+11410+91a33fe4 --- diff --git a/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch b/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch new file mode 100644 index 0000000..a2528fd --- /dev/null +++ b/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch @@ -0,0 +1,236 @@ +From 3f6ed4393dfa9ddf982e326065a3ea160bef90b6 Mon Sep 17 00:00:00 2001 +From: Antonio Torres +Date: Tue, 23 Feb 2021 16:11:59 +0100 +Subject: [PATCH] Add check for IPA KRA Agent + +Add check to validate KRA Agent in case KRA is installed, including +checking for the KRA Agent LDAP entry. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1894781 +Signed-off-by: Antonio Torres +--- + README.md | 16 ++- + src/ipahealthcheck/ipa/certs.py | 167 +++++++++++++++++++------------- + 2 files changed, 112 insertions(+), 71 deletions(-) + +diff --git a/README.md b/README.md +index b9c60a2..0f3ed6a 100644 +--- a/README.md ++++ b/README.md +@@ -547,7 +547,21 @@ Verify the description and userCertificate values in uid=ipara,ou=People,o=ipaca + "kw": { + "expected": "2;125;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST", + "got": "2;7;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST", +- "msg": "RA agent description does not match 2;7;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST in LDAP and expected 2;125;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST" ++ "msg": "RA agent description does not match. Found 2;7;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST in LDAP and expected 2;125;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST" ++ } ++ } ++ ++### IPAKRAAgent ++Verify the description and userCertificate values in uid=ipakra,ou=people,o=kra,o=ipaca. ++ ++ { ++ "source": "ipahealthcheck.ipa.certs", ++ "check": "IPAKRAAgent", ++ "result": "ERROR", ++ "kw": { ++ "expected": "2;125;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST", ++ "got": "2;7;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST", ++ "msg": "KRA agent description does not match. Found 2;7;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST in LDAP and expected 2;125;CN=Certificate Authority,O=EXAMPLE.TEST;CN=IPA RA,O=EXAMPLE.TEST" + } + } + +diff --git a/src/ipahealthcheck/ipa/certs.py b/src/ipahealthcheck/ipa/certs.py +index d3043d0..32c0d76 100644 +--- a/src/ipahealthcheck/ipa/certs.py ++++ b/src/ipahealthcheck/ipa/certs.py +@@ -724,6 +724,83 @@ class IPAOpenSSLChainValidation(IPAPlugin): + self, constants.SUCCESS, key=cert) + + ++def check_agent(plugin, base_dn, agent_type): ++ """Check RA/KRA Agent""" ++ ++ try: ++ cert = x509.load_certificate_from_file(paths.RA_AGENT_PEM) ++ except Exception as e: ++ yield Result(plugin, constants.ERROR, ++ error=str(e), ++ msg='Unable to load RA cert: {error}') ++ return ++ serial_number = cert.serial_number ++ subject = DN(cert.subject) ++ issuer = DN(cert.issuer) ++ description = '2;%d;%s;%s' % (serial_number, issuer, subject) ++ logger.debug('%s agent description should be %s', agent_type, description) ++ db_filter = ldap2.ldap2.combine_filters( ++ [ ++ ldap2.ldap2.make_filter({'objectClass': 'inetOrgPerson'}), ++ ldap2.ldap2.make_filter( ++ {'description': ';%s;%s' % (issuer, subject)}, ++ exact=False, trailing_wildcard=False), ++ ], ++ ldap2.ldap2.MATCH_ALL) ++ try: ++ entries = plugin.conn.get_entries(base_dn, ++ plugin.conn.SCOPE_SUBTREE, ++ db_filter) ++ except errors.NotFound: ++ yield Result(plugin, constants.ERROR, ++ description=description, ++ msg='%s agent not found in LDAP' % agent_type) ++ return ++ except Exception as e: ++ yield Result(plugin, constants.ERROR, ++ error=str(e), ++ msg='Retrieving %s agent from LDAP failed {error}' ++ % agent_type) ++ return ++ else: ++ logger.debug('%s agent description is %s', agent_type, description) ++ if len(entries) != 1: ++ yield Result(plugin, constants.ERROR, ++ found=len(entries), ++ msg='Too many %s agent entries found, {found}' ++ % agent_type) ++ return ++ entry = entries[0] ++ raw_desc = entry.get('description') ++ if raw_desc is None: ++ yield Result(plugin, constants.ERROR, ++ msg='%s agent is missing the description ' ++ 'attribute or it is not readable' % agent_type) ++ return ++ ra_desc = raw_desc[0] ++ ra_certs = entry.get('usercertificate') ++ if ra_desc != description: ++ yield Result(plugin, constants.ERROR, ++ expected=description, ++ got=ra_desc, ++ msg='%s agent description does not match. Found ' ++ '{got} in LDAP and expected {expected}' % agent_type) ++ return ++ found = False ++ for candidate in ra_certs: ++ if candidate == cert: ++ found = True ++ break ++ if not found: ++ yield Result(plugin, constants.ERROR, ++ certfile=paths.RA_AGENT_PEM, ++ dn=str(entry.dn), ++ msg='%s agent certificate in {certfile} not ' ++ 'found in LDAP userCertificate attribute ' ++ 'for the entry {dn}' % agent_type) ++ yield Result(plugin, constants.SUCCESS) ++ ++ + @registry + class IPARAAgent(IPAPlugin): + """Validate the RA Agent used to talk to the CA +@@ -739,82 +816,32 @@ class IPARAAgent(IPAPlugin): + logger.debug('CA is not configured, skipping RA Agent check') + return + +- try: +- cert = x509.load_certificate_from_file(paths.RA_AGENT_PEM) +- except Exception as e: +- yield Result(self, constants.ERROR, +- error=str(e), +- msg='Unable to load RA cert: {error}') +- return ++ base_dn = DN('uid=ipara,ou=people,o=ipaca') ++ yield from check_agent(self, base_dn, 'RA') + +- serial_number = cert.serial_number +- subject = DN(cert.subject) +- issuer = DN(cert.issuer) +- description = '2;%d;%s;%s' % (serial_number, issuer, subject) + +- logger.debug('RA agent description should be %s', description) ++@registry ++class IPAKRAAgent(IPAPlugin): ++ """Validate the KRA Agent + +- db_filter = ldap2.ldap2.combine_filters( +- [ +- ldap2.ldap2.make_filter({'objectClass': 'inetOrgPerson'}), +- ldap2.ldap2.make_filter({'sn': 'ipara'}), +- ldap2.ldap2.make_filter( +- {'description': ';%s;%s' % (issuer, subject)}, +- exact=False, trailing_wildcard=False), +- ], +- ldap2.ldap2.MATCH_ALL) ++ Compare the description and usercertificate values. ++ """ + +- base_dn = DN(('o', 'ipaca')) +- try: +- entries = self.conn.get_entries(base_dn, +- self.conn.SCOPE_SUBTREE, +- db_filter) +- except errors.NotFound: +- yield Result(self, constants.ERROR, +- description=description, +- msg='RA agent not found in LDAP') ++ requires = ('dirsrv',) ++ ++ @duration ++ def check(self): ++ if not self.ca.is_configured(): ++ logger.debug('CA is not configured, skipping KRA Agent check') + return +- except Exception as e: +- yield Result(self, constants.ERROR, +- error=str(e), +- msg='Retrieving RA agent from LDAP failed {error}') ++ ++ kra = krainstance.KRAInstance(api.env.realm) ++ if not kra.is_installed(): ++ logger.debug('KRA is not installed, skipping KRA Agent check') + return +- else: +- logger.debug('RA agent description is %s', description) +- if len(entries) != 1: +- yield Result(self, constants.ERROR, +- found=len(entries), +- msg='Too many RA agent entries found, {found}') +- return +- entry = entries[0] +- raw_desc = entry.get('description') +- if raw_desc is None: +- yield Result(self, constants.ERROR, +- msg='RA agent is missing the description ' +- 'attribute or it is not readable') +- return +- ra_desc = raw_desc[0] +- ra_certs = entry.get('usercertificate') +- if ra_desc != description: +- yield Result(self, constants.ERROR, +- expected=description, +- got=ra_desc, +- msg='RA agent description does not match. Found ' +- '{got} in LDAP and expected {expected}') +- return +- found = False +- for candidate in ra_certs: +- if candidate == cert: +- found = True +- break +- if not found: +- yield Result(self, constants.ERROR, +- certfile=paths.RA_AGENT_PEM, +- dn=str(entry.dn), +- msg='RA agent certificate in {certfile} not ' +- 'found in LDAP userCertificate attribute ' +- 'for the entry {dn}') +- yield Result(self, constants.SUCCESS) ++ ++ base_dn = DN('uid=ipakra,ou=people,o=kra,o=ipaca') ++ yield from check_agent(self, base_dn, 'KRA') + + + @registry +-- +2.26.2 + diff --git a/SOURCES/0006-Add-tests-for-KRA-Agent-validation.patch b/SOURCES/0006-Add-tests-for-KRA-Agent-validation.patch new file mode 100644 index 0000000..ccd5d82 --- /dev/null +++ b/SOURCES/0006-Add-tests-for-KRA-Agent-validation.patch @@ -0,0 +1,207 @@ +From a6504bd7d32fe3553b9f6f807f3d84a1b87bb77c Mon Sep 17 00:00:00 2001 +From: Antonio Torres +Date: Wed, 24 Feb 2021 17:26:08 +0100 +Subject: [PATCH] Add tests for KRA Agent validation + +Add unit tests for KRA Agent validation. + +Signed-off-by: Antonio Torres +--- + tests/test_ipa_agent.py | 174 +++++++++++++++++++++++++++++++++++++++- + 1 file changed, 172 insertions(+), 2 deletions(-) + +diff --git a/tests/test_ipa_agent.py b/tests/test_ipa_agent.py +index 6605745..9b691f7 100644 +--- a/tests/test_ipa_agent.py ++++ b/tests/test_ipa_agent.py +@@ -4,11 +4,11 @@ + + from base import BaseTest + from unittest.mock import Mock, patch +-from util import capture_results, CAInstance ++from util import capture_results, CAInstance, KRAInstance + + from ipahealthcheck.core import config, constants + from ipahealthcheck.ipa.plugin import registry +-from ipahealthcheck.ipa.certs import IPARAAgent ++from ipahealthcheck.ipa.certs import IPARAAgent, IPAKRAAgent + + from ipalib import errors + from ipapython.dn import DN +@@ -218,3 +218,173 @@ class TestNSSAgent(BaseTest): + assert result.result == constants.SUCCESS + assert result.source == 'ipahealthcheck.ipa.certs' + assert result.check == 'IPARAAgent' ++ ++ ++class TestKRAAgent(BaseTest): ++ cert = IPACertificate() ++ patches = { ++ 'ldap.initialize': ++ Mock(return_value=mock_ldap_conn()), ++ 'ipaserver.install.krainstance.KRAInstance': ++ Mock(return_value=KRAInstance()), ++ 'ipalib.x509.load_certificate_from_file': ++ Mock(return_value=cert), ++ } ++ ++ def test_kra_agent_ok(self): ++ ++ attrs = dict( ++ description=['2;1;CN=ISSUER;CN=RA AGENT'], ++ usercertificate=[self.cert], ++ ) ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ ldapentry = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap([ldapentry]) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 1 ++ ++ result = self.results.results[0] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPAKRAAgent' ++ ++ def test_kra_agent_no_description(self): ++ ++ attrs = dict( ++ usercertificate=[self.cert], ++ ) ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ ldapentry = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap([ldapentry]) ++ self.results = capture_results(f) ++ result = self.results.results[0] ++ ++ assert result.result == constants.ERROR ++ assert 'description' in result.kw.get('msg') ++ ++ @patch('ipalib.x509.load_certificate_from_file') ++ def test_kra_agent_load_failure(self, mock_load_cert): ++ ++ mock_load_cert.side_effect = IOError('test') ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ self.results = capture_results(f) ++ result = self.results.results[0] ++ ++ assert result.result == constants.ERROR ++ assert result.kw.get('error') == 'test' ++ ++ def test_kra_agent_no_entry_found(self): ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap(None) # None == NotFound ++ self.results = capture_results(f) ++ result = self.results.results[0] ++ ++ assert result.result == constants.ERROR ++ assert result.kw.get('msg') == 'KRA agent not found in LDAP' ++ ++ def test_kra_agent_too_many(self): ++ ++ attrs = dict( ++ description=['2;1;CN=ISSUER;CN=RA AGENT'], ++ usercertificate=[self.cert], ++ ) ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ ldapentry = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ ldapentry2 = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap([ldapentry, ldapentry2]) ++ self.results = capture_results(f) ++ result = self.results.results[0] ++ ++ assert result.result == constants.ERROR ++ assert result.kw.get('found') == 2 ++ ++ def test_kra_agent_nonmatching_cert(self): ++ ++ cert2 = IPACertificate(2) ++ ++ attrs = dict( ++ description=['2;1;CN=ISSUER;CN=RA AGENT'], ++ usercertificate=[cert2], ++ ) ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ ldapentry = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap([ldapentry]) ++ self.results = capture_results(f) ++ result = self.results.results[0] ++ ++ assert result.result == constants.ERROR ++ assert result.kw.get('certfile') == paths.RA_AGENT_PEM ++ assert result.kw.get('dn') == 'uid=ipakra,ou=people,o=kra,o=ipaca' ++ ++ def test_kra_agent_multiple_certs(self): ++ ++ cert2 = IPACertificate(2) ++ ++ attrs = dict( ++ description=['2;1;CN=ISSUER;CN=RA AGENT'], ++ usercertificate=[cert2, self.cert], ++ ) ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ ldapentry = LDAPEntry(fake_conn, ++ DN('uid=ipakra,ou=people,o=kra,o=ipaca')) ++ for attr, values in attrs.items(): ++ ldapentry[attr] = values ++ ++ framework = object() ++ registry.initialize(framework, config.Config) ++ f = IPAKRAAgent(registry) ++ ++ f.conn = mock_ldap([ldapentry]) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 1 ++ ++ result = self.results.results[0] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPAKRAAgent' +-- +2.26.2 + diff --git a/SOURCES/0007-Return-user-friendly-message-when-no-issues-found.patch b/SOURCES/0007-Return-user-friendly-message-when-no-issues-found.patch new file mode 100644 index 0000000..2df3c72 --- /dev/null +++ b/SOURCES/0007-Return-user-friendly-message-when-no-issues-found.patch @@ -0,0 +1,30 @@ +From e23e38124098d37b514e02531341af21e6fb0688 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 14 Jun 2021 11:37:23 -0400 +Subject: [PATCH] Return user-friendly message when no issues found + +Return user-friendly message instead of empty string +when no issues found and using the "human" output type. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1780062 +Signed-off-by: Antonio Torres +--- + src/ipahealthcheck/core/output.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/ipahealthcheck/core/output.py b/src/ipahealthcheck/core/output.py +index cfd0f94..784263d 100644 +--- a/src/ipahealthcheck/core/output.py ++++ b/src/ipahealthcheck/core/output.py +@@ -129,6 +129,8 @@ class Human(Output): + super(Human, self).__init__(options) + + def generate(self, data): ++ if not data: ++ return "No issues found.\n" + output = '' + for line in data: + kw = line.get('kw') +-- +2.26.3 + diff --git a/SOURCES/0009-Add-checks-to-detect-mismatch-of-certificates.patch b/SOURCES/0009-Add-checks-to-detect-mismatch-of-certificates.patch new file mode 100644 index 0000000..ae8d15a --- /dev/null +++ b/SOURCES/0009-Add-checks-to-detect-mismatch-of-certificates.patch @@ -0,0 +1,291 @@ +From f762f8f5e2f9b6d66d786b426d4d2fe40c994192 Mon Sep 17 00:00:00 2001 +From: Antonio Torres +Date: Fri, 23 Apr 2021 17:42:21 +0200 +Subject: [PATCH] Add checks to detect mismatch of certificates + +Add checks to detect mismatch of certificates between LDAP +and NSS databases. Check for existance of entries as well as +ensure the certificates match between the different databases. + +Related: https://bugzilla.redhat.com/show_bug.cgi?id=1886770 +Signed-off-by: Antonio Torres +--- + README.md | 24 ++++ + src/ipahealthcheck/ipa/certs.py | 219 ++++++++++++++++++++++++++++++++ + 2 files changed, 243 insertions(+) + +diff --git a/README.md b/README.md +index 0f3ed6a..11e0e88 100644 +--- a/README.md ++++ b/README.md +@@ -507,6 +507,30 @@ The trust for certificates stored in NSS databases is compared against a known g + } + } + ++### IPACertMatchCheck ++Ensure CA certificate entries in LDAP and NSS databases match. ++ ++ { ++ "source": "ipahealthcheck.ipa.certs", ++ "check": "IPACertMatchCheck", ++ "result": "ERROR", ++ "kw": { ++ "msg": "CA Certificate from /etc/ipa/nssdb does not match /etc/ipa/ca.crt" ++ } ++ } ++ ++### IPADogtagCertsMatchCheck ++Check if Dogtag certificates present in both NSS DB and LDAP match. ++ ++ { ++ "source": "ipahealthcheck.ipa.certs", ++ "check": "IPADogtagCertsMatchCheck", ++ "result": "ERROR", ++ "kw": { ++ "msg": "'subsystemCert cert-pki-ca' certificate in NSS DB does not match entry in LDAP" ++ } ++ } ++ + ### IPANSSChainValidation + Validate the certificate chain of the NSS certificates. This executes: certutil -V -u V -e -d [dbdir] -n [nickname]. + +diff --git a/src/ipahealthcheck/ipa/certs.py b/src/ipahealthcheck/ipa/certs.py +index c668093..82435f3 100644 +--- a/src/ipahealthcheck/ipa/certs.py ++++ b/src/ipahealthcheck/ipa/certs.py +@@ -29,6 +29,7 @@ from ipaserver.plugins import ldap2 + from ipapython import certdb + from ipapython import ipautil + from ipapython.dn import DN ++from ipapython.ipaldap import realm_to_serverid + + logger = logging.getLogger() + DAY = 60 * 60 * 24 +@@ -587,6 +588,224 @@ class IPACertNSSTrust(IPAPlugin): + 'verifying trust') + + ++@registry ++class IPACertMatchCheck(IPAPlugin): ++ """ ++ Ensure certificates match between LDAP and NSS databases ++ """ ++ ++ requires = ('dirsrv',) ++ ++ def get_cert_list_from_db(self, nssdb, nickname): ++ """ ++ Retrieve all certificates from an NSS database for nickname. ++ """ ++ try: ++ args = ["-L", "-n", nickname, "-a"] ++ result = nssdb.run_certutil(args, capture_output=True) ++ return x509.load_certificate_list(result.raw_output) ++ except ipautil.CalledProcessError: ++ return [] ++ ++ @duration ++ def check(self): ++ if not self.ca.is_configured(): ++ logger.debug("No CA configured, skipping certificate match check") ++ return ++ ++ # Ensure /etc/ipa/ca.crt matches the NSS DB CA certificates ++ def match_cacert_and_db(plugin, cacerts, dbpath): ++ db = certs.CertDB(api.env.realm, dbpath) ++ nickname = '%s IPA CA' % api.env.realm ++ try: ++ dbcacerts = self.get_cert_list_from_db(db, nickname) ++ except Exception as e: ++ yield Result(plugin, constants.ERROR, ++ error=str(e), ++ msg='Unable to load CA cert: {error}') ++ return False ++ ++ ok = True ++ for cert in dbcacerts: ++ if cert not in cacerts: ++ ok = False ++ yield Result(plugin, constants.ERROR, ++ nickname=nickname, ++ serial_number=cert.serial_number, ++ dbdir=dbpath, ++ certdir=paths.IPA_CA_CRT, ++ msg=('CA Certificate nickname {nickname} ' ++ 'with serial number {serial} ' ++ 'is in {dbdir} but is not in' ++ '%s' % paths.IPA_CA_CRT)) ++ return ok ++ ++ try: ++ cacerts = x509.load_certificate_list_from_file(paths.IPA_CA_CRT) ++ except Exception: ++ yield Result(self, constants.ERROR, ++ path=paths.IPA_CA_CRT, ++ msg='Unable to load CA cert file {path}: {error}') ++ return ++ ++ # Ensure CA cert entry from LDAP matches /etc/ipa/ca.crt ++ dn = DN('cn=%s IPA CA' % api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ api.env.basedn) ++ try: ++ entry = self.conn.get_entry(dn) ++ except errors.NotFound: ++ yield Result(self, constants.ERROR, ++ dn=str(dn), ++ msg='CA Certificate entry \'{dn}\' ' ++ 'not found in LDAP') ++ return ++ ++ cacerts_ok = True ++ # Are all the certs in LDAP for the IPA CA in /etc/ipa/ca.crt ++ for cert in entry['CACertificate']: ++ if cert not in cacerts: ++ cacerts_ok = False ++ yield Result(self, constants.ERROR, ++ dn=str(dn), ++ serial_number=cert.serial_number, ++ msg=('CA Certificate serial number {serial} is ' ++ 'in LDAP \'{dn}\' but is not in ' ++ '%s' % paths.IPA_CA_CRT)) ++ ++ # Ensure NSS DBs have matching CA certs for /etc/ipa/ca.crt ++ serverid = realm_to_serverid(api.env.realm) ++ dspath = paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % serverid ++ ++ cacertds_ok = yield from match_cacert_and_db(self, cacerts, dspath) ++ cacertnss_ok = yield from match_cacert_and_db(self, cacerts, ++ paths.IPA_NSSDB_DIR) ++ if cacerts_ok: ++ yield Result(self, constants.SUCCESS, ++ key=paths.IPA_CA_CRT) ++ if cacertds_ok: ++ yield Result(self, constants.SUCCESS, ++ key=dspath) ++ if cacertnss_ok: ++ yield Result(self, constants.SUCCESS, ++ key=paths.IPA_NSSDB_DIR) ++ ++ ++@registry ++class IPADogtagCertsMatchCheck(IPAPlugin): ++ """ ++ Check if dogtag certs present in both NSS DB and LDAP match ++ """ ++ requires = ('dirsrv',) ++ ++ @duration ++ def check(self): ++ if not self.ca.is_configured(): ++ logger.debug('CA is not configured, skipping connectivity check') ++ return ++ ++ def match_ldap_nss_cert(plugin, ldap, db, cert_dn, attr, cert_nick): ++ try: ++ entry = ldap.get_entry(cert_dn) ++ except errors.NotFound: ++ yield Result(plugin, constants.ERROR, ++ msg='%s entry not found in LDAP' % cert_dn) ++ return False ++ try: ++ nsscert = db.get_cert_from_db(cert_nick) ++ except Exception as e: ++ yield Result(plugin, constants.ERROR, ++ error=str(e), ++ msg=('Unable to load %s certificate:' ++ '{error}' % cert_nick)) ++ return False ++ cert_matched = any([cert == nsscert for cert in entry[attr]]) ++ if not cert_matched: ++ yield Result(plugin, constants.ERROR, ++ key=cert_nick, ++ nickname=cert_nick, ++ dbdir=db.secdir, ++ msg=('{nickname} certificate in NSS DB {dbdir} ' ++ 'does not match entry in LDAP')) ++ return False ++ return True ++ ++ def match_ldap_nss_certs_by_subject(plugin, ldap, db, dn, ++ expected_nicks_subjects): ++ entries = ldap.get_entries(dn) ++ all_ok = True ++ for nick, subject in expected_nicks_subjects.items(): ++ cert = db.get_cert_from_db(nick) ++ ok = any([cert in entry['userCertificate'] and ++ subject == entry['subjectName'][0] ++ for entry in entries ++ if 'userCertificate' in entry]) ++ if not ok: ++ all_ok = False ++ yield Result(plugin, constants.ERROR, ++ key=nick, ++ nickname=nick, ++ dbdir=db.secdir, ++ msg=('{nickname} certificate in NSS DB ' ++ '{dbdir} does not match entry in LDAP')) ++ return all_ok ++ ++ db = certs.CertDB(api.env.realm, paths.PKI_TOMCAT_ALIAS_DIR) ++ dn = DN('uid=pkidbuser,ou=people,o=ipaca') ++ subsystem_nick = 'subsystemCert cert-pki-ca' ++ subsystem_ok = yield from match_ldap_nss_cert(self, self.conn, ++ db, dn, ++ 'userCertificate', ++ subsystem_nick) ++ dn = DN('cn=%s IPA CA' % api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ api.env.basedn) ++ casigning_nick = 'caSigningCert cert-pki-ca' ++ casigning_ok = yield from match_ldap_nss_cert(self, self.conn, ++ db, dn, 'CACertificate', ++ casigning_nick) ++ ++ expected_nicks_subjects = { ++ 'ocspSigningCert cert-pki-ca': ++ 'CN=OCSP Subsystem,O=%s' % api.env.realm, ++ 'subsystemCert cert-pki-ca': ++ 'CN=CA Subsystem,O=%s' % api.env.realm, ++ 'auditSigningCert cert-pki-ca': ++ 'CN=CA Audit,O=%s' % api.env.realm, ++ 'Server-Cert cert-pki-ca': ++ 'CN=%s,O=%s' % (api.env.host, api.env.realm), ++ } ++ ++ kra = krainstance.KRAInstance(api.env.realm) ++ if kra.is_installed(): ++ kra_expected_nicks_subjects = { ++ 'transportCert cert-pki-kra': ++ 'CN=KRA Transport Certificate,O=%s' % api.env.realm, ++ 'storageCert cert-pki-kra': ++ 'CN=KRA Storage Certificate,O=%s' % api.env.realm, ++ 'auditSigningCert cert-pki-kra': ++ 'CN=KRA Audit,O=%s' % api.env.realm, ++ } ++ expected_nicks_subjects.update(kra_expected_nicks_subjects) ++ ++ ipaca_basedn = DN('ou=certificateRepository,ou=ca,o=ipaca') ++ ipaca_certs_ok = yield from match_ldap_nss_certs_by_subject( ++ self, self.conn, db, ++ ipaca_basedn, ++ expected_nicks_subjects ++ ) ++ ++ if subsystem_ok: ++ yield Result(self, constants.SUCCESS, ++ key=subsystem_nick) ++ if casigning_ok: ++ yield Result(self, constants.SUCCESS, ++ key=casigning_nick) ++ if ipaca_certs_ok: ++ yield Result(self, constants.SUCCESS, ++ key=str(ipaca_basedn)) ++ ++ + @registry + class IPANSSChainValidation(IPAPlugin): + """Validate the certificate chain of the certs.""" +-- +2.26.3 + diff --git a/SOURCES/0010-Add-tests-for-certificate-mismatch-detection.patch b/SOURCES/0010-Add-tests-for-certificate-mismatch-detection.patch new file mode 100644 index 0000000..2ded66c --- /dev/null +++ b/SOURCES/0010-Add-tests-for-certificate-mismatch-detection.patch @@ -0,0 +1,305 @@ +From 94ca49d991b3f5bb404533f84970a1b2485d9cc8 Mon Sep 17 00:00:00 2001 +From: Antonio Torres +Date: Fri, 23 Apr 2021 17:48:14 +0200 +Subject: [PATCH] Add tests for certificate mismatch detection + +Add tests for the IPACertMatchCheck and IPADogtagCertsMatchCheck plugins. + +Related: https://bugzilla.redhat.com/show_bug.cgi?id=1886770 +Signed-off-by: Antonio Torres +--- + tests/test_ipa_cert_match.py | 282 +++++++++++++++++++++++++++++++++++ + 1 file changed, 282 insertions(+) + create mode 100644 tests/test_ipa_cert_match.py + +diff --git a/tests/test_ipa_cert_match.py b/tests/test_ipa_cert_match.py +new file mode 100644 +index 0000000..460e61a +--- /dev/null ++++ b/tests/test_ipa_cert_match.py +@@ -0,0 +1,282 @@ ++# ++# Copyright (C) 2021 FreeIPA Contributors see COPYING for license ++# ++ ++from util import capture_results, m_api, CAInstance, KRAInstance ++from base import BaseTest ++from ipahealthcheck.core import config, constants ++from ipahealthcheck.ipa.plugin import registry ++from ipahealthcheck.ipa.certs import IPACertMatchCheck ++from ipahealthcheck.ipa.certs import IPADogtagCertsMatchCheck ++from unittest.mock import Mock, patch ++ ++from ipalib import errors ++from ipapython.dn import DN ++from ipapython.ipaldap import LDAPClient, LDAPEntry ++ ++ ++class IPACertificate: ++ def __init__(self, serial_number=1): ++ self.serial_number = serial_number ++ ++ def __eq__(self, other): ++ return self.serial_number == other.serial_number ++ ++ def __hash__(self): ++ return hash(self.serial_number) ++ ++ ++class mock_ldap: ++ SCOPE_BASE = 1 ++ SCOPE_ONELEVEL = 2 ++ SCOPE_SUBTREE = 4 ++ ++ def __init__(self, entries): ++ """Initialize the results that we will return from get_entry""" ++ self.results = {entry.dn: entry for entry in entries} ++ ++ def get_entry(self, dn, attrs_list=None, time_limit=None, ++ size_limit=None, get_effective_rights=False): ++ if self.results is None: ++ raise errors.NotFound(reason='test') ++ return self.results[dn] ++ ++ def get_entries(self, base_dn, scope=SCOPE_SUBTREE, filter=None, ++ attrs_list=None, get_effective_rights=False, **kwargs): ++ if self.results is None: ++ raise errors.NotFound(reason='test') ++ return self.results.values() ++ ++ ++class mock_ldap_conn: ++ def set_option(self, option, invalue): ++ pass ++ ++ def search_s(self, base, scope, filterstr=None, ++ attrlist=None, attrsonly=0): ++ return tuple() ++ ++ ++class mock_CertDB: ++ def __init__(self, trust): ++ """A dict of nickname + NSSdb trust flags""" ++ self.trust = trust ++ self.secdir = '/foo/bar/testdir' ++ ++ def get_cert_from_db(self, nickname): ++ if nickname not in self.trust.keys(): ++ raise errors.NotFound(reason='test') ++ return IPACertificate() ++ ++ def run_certutil(self, args, capture_output): ++ class RunResult: ++ def __init__(self, output): ++ self.raw_output = output ++ ++ return RunResult(b'test output') ++ ++ ++class TestIPACertMatch(BaseTest): ++ patches = { ++ 'ldap.initialize': ++ Mock(return_value=mock_ldap_conn()) ++ } ++ ++ @patch('ipalib.x509.load_certificate_list_from_file') ++ @patch('ipaserver.install.certs.CertDB') ++ def test_certs_match_ok(self, mock_certdb, mock_load_cert): ++ """ Ensure match check is ok""" ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ cacertentry = LDAPEntry(fake_conn, ++ DN('cn=%s IPA CA' % m_api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ m_api.env.basedn), ++ CACertificate=[IPACertificate()]) ++ trust = { ++ ('%s IPA CA' % m_api.env.realm): 'u,u,u' ++ } ++ ++ mock_certdb.return_value = mock_CertDB(trust) ++ mock_load_cert.return_value = [IPACertificate()] ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPACertMatchCheck(registry) ++ f.conn = mock_ldap([cacertentry]) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ for result in self.results.results: ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPACertMatchCheck' ++ ++ @patch('ipalib.x509.load_certificate_list_from_file') ++ @patch('ipaserver.install.certs.CertDB') ++ def test_etc_cacert_mismatch(self, mock_certdb, mock_load_cert): ++ """ Test mismatch with /etc/ipa/ca.crt """ ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ cacertentry = LDAPEntry(fake_conn, ++ DN('cn=%s IPA CA' % m_api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ m_api.env.basedn), ++ CACertificate=[IPACertificate()]) ++ trust = { ++ ('%s IPA CA' % m_api.env.realm): 'u,u,u' ++ } ++ ++ mock_certdb.return_value = mock_CertDB(trust) ++ mock_load_cert.return_value = [IPACertificate(serial_number=2)] ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPACertMatchCheck(registry) ++ f.conn = mock_ldap([cacertentry]) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ result = self.results.results[0] ++ assert result.result == constants.ERROR ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPACertMatchCheck' ++ ++ @patch('ipaserver.install.cainstance.CAInstance') ++ def test_cacert_caless(self, mock_cainstance): ++ """Nothing to check if the master is CALess""" ++ ++ mock_cainstance.return_value = CAInstance(False) ++ ++ framework = object() ++ registry.initialize(framework, config) ++ f = IPACertMatchCheck(registry) ++ ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 0 ++ ++ ++class TestIPADogtagCertMatch(BaseTest): ++ patches = { ++ 'ipaserver.install.krainstance.KRAInstance': ++ Mock(return_value=KRAInstance()), ++ } ++ ++ @patch('ipaserver.install.certs.CertDB') ++ def test_certs_match_ok(self, mock_certdb): ++ """ Ensure match check is ok""" ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ pkidbentry = LDAPEntry(fake_conn, ++ DN('uid=pkidbuser,ou=people,o=ipaca'), ++ userCertificate=[IPACertificate()], ++ subjectName=['test']) ++ casignentry = LDAPEntry(fake_conn, ++ DN('cn=%s IPA CA' % m_api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ m_api.env.basedn), ++ CACertificate=[IPACertificate()], ++ userCertificate=[IPACertificate()], ++ subjectName=['test']) ++ ldap_entries = [pkidbentry, casignentry] ++ trust = { ++ 'ocspSigningCert cert-pki-ca': 'u,u,u', ++ 'caSigningCert cert-pki-ca': 'u,u,u', ++ 'subsystemCert cert-pki-ca': 'u,u,u', ++ 'auditSigningCert cert-pki-ca': 'u,u,Pu', ++ 'Server-Cert cert-pki-ca': 'u,u,u', ++ 'transportCert cert-pki-kra': 'u,u,u', ++ 'storageCert cert-pki-kra': 'u,u,u', ++ 'auditSigningCert cert-pki-kra': 'u,u,Pu', ++ } ++ ++ dogtag_entries_subjects = ( ++ 'CN=OCSP Subsystem,O=%s' % m_api.env.realm, ++ 'CN=CA Subsystem,O=%s' % m_api.env.realm, ++ 'CN=CA Audit,O=%s' % m_api.env.realm, ++ 'CN=%s,O=%s' % (m_api.env.host, m_api.env.realm), ++ 'CN=KRA Transport Certificate,O=%s' % m_api.env.realm, ++ 'CN=KRA Storage Certificate,O=%s' % m_api.env.realm, ++ 'CN=KRA Audit,O=%s' % m_api.env.realm, ++ ) ++ ++ for i, subject in enumerate(dogtag_entries_subjects): ++ entry = LDAPEntry(fake_conn, ++ DN('cn=%i,ou=certificateRepository' % i, ++ 'ou=ca,o=ipaca'), ++ userCertificate=[IPACertificate()], ++ subjectName=[subject]) ++ ldap_entries.append(entry) ++ ++ mock_certdb.return_value = mock_CertDB(trust) ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPADogtagCertsMatchCheck(registry) ++ f.conn = mock_ldap(ldap_entries) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ for result in self.results.results: ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPADogtagCertsMatchCheck' ++ ++ @patch('ipaserver.install.certs.CertDB') ++ def test_certs_mismatch(self, mock_certdb): ++ """ Ensure mismatches are detected""" ++ fake_conn = LDAPClient('ldap://localhost', no_schema=True) ++ pkidbentry = LDAPEntry(fake_conn, ++ DN('uid=pkidbuser,ou=people,o=ipaca'), ++ userCertificate=[IPACertificate( ++ serial_number=2 ++ )], ++ subjectName=['test']) ++ casignentry = LDAPEntry(fake_conn, ++ DN('cn=%s IPA CA' % m_api.env.realm, ++ 'cn=certificates,cn=ipa,cn=etc', ++ m_api.env.basedn), ++ CACertificate=[IPACertificate()], ++ userCertificate=[IPACertificate()], ++ subjectName=['test']) ++ ldap_entries = [pkidbentry, casignentry] ++ trust = { ++ 'ocspSigningCert cert-pki-ca': 'u,u,u', ++ 'caSigningCert cert-pki-ca': 'u,u,u', ++ 'subsystemCert cert-pki-ca': 'u,u,u', ++ 'auditSigningCert cert-pki-ca': 'u,u,Pu', ++ 'Server-Cert cert-pki-ca': 'u,u,u', ++ 'transportCert cert-pki-kra': 'u,u,u', ++ 'storageCert cert-pki-kra': 'u,u,u', ++ 'auditSigningCert cert-pki-kra': 'u,u,Pu', ++ } ++ ++ dogtag_entries_subjects = ( ++ 'CN=OCSP Subsystem,O=%s' % m_api.env.realm, ++ 'CN=CA Subsystem,O=%s' % m_api.env.realm, ++ 'CN=CA Audit,O=%s' % m_api.env.realm, ++ 'CN=%s,O=%s' % (m_api.env.host, m_api.env.realm), ++ 'CN=KRA Transport Certificate,O=%s' % m_api.env.realm, ++ 'CN=KRA Storage Certificate,O=%s' % m_api.env.realm, ++ 'CN=KRA Audit,O=%s' % m_api.env.realm, ++ ) ++ ++ for i, subject in enumerate(dogtag_entries_subjects): ++ entry = LDAPEntry(fake_conn, ++ DN('cn=%i,ou=certificateRepository' % i, ++ 'ou=ca,o=ipaca'), ++ userCertificate=[IPACertificate()], ++ subjectName=[subject]) ++ ldap_entries.append(entry) ++ ++ mock_certdb.return_value = mock_CertDB(trust) ++ ++ framework = object() ++ registry.initialize(framework, config.Config()) ++ f = IPADogtagCertsMatchCheck(registry) ++ f.conn = mock_ldap(ldap_entries) ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ result = self.results.results[0] ++ assert result.result == constants.ERROR ++ assert result.source == 'ipahealthcheck.ipa.certs' ++ assert result.check == 'IPADogtagCertsMatchCheck' +-- +2.26.3 + diff --git a/SOURCES/0011-Add-log-files-to-the-set-of-files-checked-for-owner-.patch b/SOURCES/0011-Add-log-files-to-the-set-of-files-checked-for-owner-.patch new file mode 100644 index 0000000..9c2988d --- /dev/null +++ b/SOURCES/0011-Add-log-files-to-the-set-of-files-checked-for-owner-.patch @@ -0,0 +1,99 @@ +From 7b8acecd7393deba2411192d3f04778a2a4325c5 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 14 Jun 2021 11:38:21 -0400 +Subject: [PATCH] Add log files to the set of files checked for + owner/group/mode + +Extend the list of files to be checked to include most IPA service +log files. + +https://bugzilla.redhat.com/show_bug.cgi?id=1780020 + +Signed-off-by: Rob Crittenden +--- + src/ipahealthcheck/ipa/files.py | 62 +++++++++++++++++++++++++++++++++ + 1 file changed, 62 insertions(+) + +diff --git a/src/ipahealthcheck/ipa/files.py b/src/ipahealthcheck/ipa/files.py +index ae74c38..abfa52f 100644 +--- a/src/ipahealthcheck/ipa/files.py ++++ b/src/ipahealthcheck/ipa/files.py +@@ -2,6 +2,7 @@ + # Copyright (C) 2019 FreeIPA Contributors see COPYING for license + # + ++import glob + import logging + import os + +@@ -95,6 +96,67 @@ class IPAFileCheck(IPAPlugin, FileCheck): + self.files.append((paths.RESOLV_CONF, 'root', 'root', '0644')) + self.files.append((paths.HOSTS, 'root', 'root', '0644')) + ++ # IPA log files that may vary by installation. Only verify ++ # those that exist ++ for filename in ( ++ paths.IPABACKUP_LOG, ++ paths.IPARESTORE_LOG, ++ paths.IPACLIENT_INSTALL_LOG, ++ paths.IPACLIENT_UNINSTALL_LOG, ++ paths.IPAREPLICA_CA_INSTALL_LOG, ++ paths.IPAREPLICA_CONNCHECK_LOG, ++ paths.IPAREPLICA_INSTALL_LOG, ++ paths.IPASERVER_INSTALL_LOG, ++ paths.IPASERVER_KRA_INSTALL_LOG, ++ paths.IPASERVER_UNINSTALL_LOG, ++ paths.IPAUPGRADE_LOG, ++ paths.IPATRUSTENABLEAGENT_LOG, ++ ): ++ if os.path.exists(filename): ++ self.files.append((filename, 'root', 'root', '0600')) ++ ++ self.files.append((paths.IPA_CUSTODIA_AUDIT_LOG, ++ 'root', 'root', '0644')) ++ ++ self.files.append((paths.KADMIND_LOG, 'root', 'root', '0600')) ++ self.files.append((paths.KRB5KDC_LOG, 'root', 'root', '0640')) ++ ++ inst = api.env.realm.replace('.', '-') ++ self.files.append((paths.SLAPD_INSTANCE_ACCESS_LOG_TEMPLATE % inst, ++ 'dirsrv', 'dirsrv', '0600')) ++ self.files.append((paths.SLAPD_INSTANCE_ERROR_LOG_TEMPLATE % inst, ++ 'dirsrv', 'dirsrv', '0600')) ++ ++ self.files.append((paths.VAR_LOG_HTTPD_ERROR, 'root', 'root', '0644')) ++ ++ for globpath in glob.glob("%s/debug*.log" % paths.TOMCAT_CA_DIR): ++ self.files.append((globpath, "pkiuser", "pkiuser", "0644")) ++ ++ for globpath in glob.glob( ++ "%s/ca_audit*" % paths.TOMCAT_SIGNEDAUDIT_DIR ++ ): ++ self.files.append((globpath, 'pkiuser', 'pkiuser', '0640')) ++ ++ for filename in ('selftests.log', 'system', 'transactions'): ++ self.files.append(( ++ os.path.join(paths.TOMCAT_CA_DIR, filename), ++ 'pkiuser', 'pkiuser', '0640' ++ )) ++ ++ for globpath in glob.glob("%s/debug*.log" % paths.TOMCAT_KRA_DIR): ++ self.files.append((globpath, "pkiuser", "pkiuser", "0644")) ++ ++ for globpath in glob.glob( ++ "%s/ca_audit*" % paths.TOMCAT_KRA_SIGNEDAUDIT_DIR ++ ): ++ self.files.append((globpath, 'pkiuser', 'pkiuser', '0640')) ++ ++ for filename in ('selftests.log', 'system', 'transactions'): ++ self.files.append(( ++ os.path.join(paths.TOMCAT_KRA_DIR, filename), ++ 'pkiuser', 'pkiuser', '0640' ++ )) ++ + return FileCheck.check(self) + + +-- +2.26.3 + diff --git a/SOURCES/0012-Handle-files-that-don-t-exist-in-FileCheck.patch b/SOURCES/0012-Handle-files-that-don-t-exist-in-FileCheck.patch new file mode 100644 index 0000000..cfd8d48 --- /dev/null +++ b/SOURCES/0012-Handle-files-that-don-t-exist-in-FileCheck.patch @@ -0,0 +1,63 @@ +From 89bca9803d92cd6977c01462e9031fa1e00c950b Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 14 Jun 2021 11:40:06 -0400 +Subject: [PATCH] Handle files that don't exist in FileCheck + +A raw os.stat() was called which could raise an exception if the file +doesn't exist. Instead call os.path.exists() and if the result is False +then raise a SUCCESS with a message that the file doesn't exist. + +https://github.com/freeipa/freeipa-healthcheck/issues/213 + +Signed-off-by: Rob Crittenden +--- + src/ipahealthcheck/core/files.py | 7 +++++++ + tests/test_core_files.py | 17 +++++++++++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/src/ipahealthcheck/core/files.py b/src/ipahealthcheck/core/files.py +index 9441eb2..1b208d1 100644 +--- a/src/ipahealthcheck/core/files.py ++++ b/src/ipahealthcheck/core/files.py +@@ -23,6 +23,13 @@ class FileCheck: + @duration + def check(self): + for (path, owner, group, mode) in self.files: ++ if not os.path.exists(path): ++ for type in ('mode', 'owner', 'group'): ++ key = '%s_%s' % (path.replace('/', '_'), type) ++ yield Result(self, constants.SUCCESS, key=key, ++ type=type, path=path, ++ msg='File does not exist') ++ continue + stat = os.stat(path) + fmode = str(oct(stat.st_mode)[-4:]) + key = '%s_mode' % path.replace('/', '_') +diff --git a/tests/test_core_files.py b/tests/test_core_files.py +index e6cf354..a4f25ac 100644 +--- a/tests/test_core_files.py ++++ b/tests/test_core_files.py +@@ -105,3 +105,20 @@ def test_files_mode(mock_stat): + my_results = get_results(results, 'mode') + assert my_results.results[0].result == constants.WARNING + assert my_results.results[1].result == constants.WARNING ++ ++ ++@patch('os.path.exists') ++def test_files_not_found(mock_exists): ++ mock_exists.return_value = False ++ ++ f = FileCheck() ++ f.files = files ++ ++ results = capture_results(f) ++ ++ for type in ('mode', 'group', 'owner'): ++ my_results = get_results(results, type) ++ assert len(my_results.results) == 4 ++ for result in my_results.results: ++ assert result.result == constants.SUCCESS ++ assert result.kw.get('msg') == 'File does not exist' +-- +2.26.3 + diff --git a/SPECS/ipa-healthcheck.spec b/SPECS/ipa-healthcheck.spec index 6fb3b64..61b0817 100644 --- a/SPECS/ipa-healthcheck.spec +++ b/SPECS/ipa-healthcheck.spec @@ -8,7 +8,7 @@ Name: ipa-healthcheck Version: 0.7 -Release: 3%{?dist} +Release: 6%{?dist} Summary: Health check tool for IdM BuildArch: noarch License: GPLv3 @@ -20,6 +20,13 @@ Patch0001: 0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch Patch0002: 0002-Remove-ipaclustercheck.patch Patch0003: 0003-Use-trust-find-and-trustdomain-find-to-identify-all-.patch Patch0004: 0004-result-names-are-not-translated-when-reading-input-f.patch +Patch0005: 0005-Add-check-for-IPA-KRA-Agent.patch +Patch0006: 0006-Add-tests-for-KRA-Agent-validation.patch +Patch0007: 0007-Return-user-friendly-message-when-no-issues-found.patch +Patch0009: 0009-Add-checks-to-detect-mismatch-of-certificates.patch +Patch0010: 0010-Add-tests-for-certificate-mismatch-detection.patch +Patch0011: 0011-Add-log-files-to-the-set-of-files-checked-for-owner-.patch +Patch0012: 0012-Handle-files-that-don-t-exist-in-FileCheck.patch Requires: %{name}-core = %{version}-%{release} Requires: ipa-server @@ -123,6 +130,18 @@ install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{long %changelog +* Mon Jun 14 2021 Rob Crittenden - 0.7-6 +- Fix patch fuzz issues, apply add'l upstream for log files (#1780020) + +* Wed Jun 2 2021 Rob Crittenden - 0.7-5 +- Return a user-friendly message when no issues are found (#1780062) +- Report on FIPS status (#1781107) +- Detect mismatches beteween certificates in LDAP and filesystem (#1886770) +- Verify owner/perms for important log files (#1780020) + +* Tue Apr 6 2021 Rob Crittenden - 0.7-4 +- Add check to validate the KRA Agent is correct (#1894781) + * Fri Dec 4 2020 Rob Crittenden - 0.7-3 - Translate result names when reading input from a json file (#1866558)