diff --git a/SOURCES/0003-Skip-AD-domains-with-posix-ranges-in-the-catalog-che.patch b/SOURCES/0003-Skip-AD-domains-with-posix-ranges-in-the-catalog-che.patch new file mode 100644 index 0000000..0b20f70 --- /dev/null +++ b/SOURCES/0003-Skip-AD-domains-with-posix-ranges-in-the-catalog-che.patch @@ -0,0 +1,340 @@ +From 30471ebdc9fe5871c115ca06f78a415275a320e6 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 16 Jun 2022 20:02:51 +0000 +Subject: [PATCH] Skip AD domains with posix ranges in the catalog check + +The catalog check is intended to ensure that the trust is +working by looking up a user. For a non-posix range we can use +the Administrator user because it has a predicible SID. + +With a posix range the UID/GID may not be set so the lookup +can fail (with an empty return value). + +So skip domain which have a posix range associated with it. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1775199 + +Signed-off-by: Rob Crittenden +--- + src/ipahealthcheck/ipa/trust.py | 34 ++++- + tests/test_ipa_trust.py | 214 +++++++++++++++++++++++++++++++- + 2 files changed, 243 insertions(+), 5 deletions(-) + +diff --git a/src/ipahealthcheck/ipa/trust.py b/src/ipahealthcheck/ipa/trust.py +index 27a2c86..b962807 100644 +--- a/src/ipahealthcheck/ipa/trust.py ++++ b/src/ipahealthcheck/ipa/trust.py +@@ -183,6 +183,7 @@ class IPATrustDomainsCheck(IPAPlugin): + except Exception as e: + yield Result(self, constants.WARNING, + key='domain-status', ++ domain=domain, + error=str(e), + msg='Execution of {key} failed: {error}') + continue +@@ -262,6 +263,10 @@ class IPATrustCatalogCheck(IPAPlugin): + This should populate the 'AD Global catalog' and 'AD Domain Controller' + fields in 'sssctl domain-status' output (means SSSD actually talks to AD + DCs) ++ ++ If the associated idrange type is ipa-ad-trust-posix then the ++ check will be skipped because we can't predict what the UID of the ++ Administrator account will be. + """ + @duration + def check(self): +@@ -280,20 +285,41 @@ class IPATrustCatalogCheck(IPAPlugin): + + for trust_domain in trust_domains: + sid = trust_domain.get('domainsid') ++ domain = trust_domain['domain'] ++ idrange = api.Command.idrange_find(sid) ++ if len(idrange['result']) == 0: ++ yield Result(self, constants.WARNING, ++ key=sid, ++ domain=domain, ++ msg='Domain {domain} does not have an idrange') ++ continue ++ ++ if 'ipa-ad-trust-posix' in idrange['result'][0]['iparangetyperaw']: ++ yield Result(self, constants.SUCCESS, ++ key=sid, ++ domain=domain, ++ type='ipa-ad-trust-posix') ++ logger.debug("Domain %s is a POSIX range, skip the lookup", ++ domain) ++ continue ++ + try: + id = pysss_nss_idmap.getnamebysid(sid + '-500') + except Exception as e: + yield Result(self, constants.ERROR, +- key=sid, ++ key=id, ++ domain=domain, + error=str(e), +- msg='Look up of{key} failed: {error}') ++ msg='Look up of ID {key} for {domain} failed: ' ++ '{error}') + continue + + if not id: + yield Result(self, constants.WARNING, +- key=sid, ++ key=id, ++ domain=trust_domain['domain'], + error='returned nothing', +- msg='Look up of {key} {error}') ++ msg='Look up of ID {key} for {domain} {error}') + else: + yield Result(self, constants.SUCCESS, + key='Domain Security Identifier', +diff --git a/tests/test_ipa_trust.py b/tests/test_ipa_trust.py +index c314b70..6c4754a 100644 +--- a/tests/test_ipa_trust.py ++++ b/tests/test_ipa_trust.py +@@ -129,6 +129,74 @@ def trustdomain_find(): + ] + + ++def idrange_find_adrange_type(): ++ """ ++ Return a set of idranges of type "Active Directory domain range" ++ """ ++ ++ return { ++ "result": [ ++ { ++ "cn": ["AD.EXAMPLE_id_range"], ++ "ipabaseid": ["1664000000"], ++ "ipabaserid": ["0"], ++ "ipaidrangesize": ["200000"], ++ "ipanttrusteddomainsid": ["S-1-5-21-abc"], ++ "iparangetype": ["Active Directory domain range"], ++ "iparangetyperaw": ["ipa-ad-trust"] ++ }, ++ { ++ "cn": ["CHILD.AD.EXAMPLE_id_range"], ++ "ipabaseid": ["538600000"], ++ "ipabaserid": ["0"], ++ "ipaidrangesize": ["200000"], ++ "ipanttrusteddomainsid": [ ++ "S-1-5-21-38045160-610119595-3099869984" ++ ], ++ "iparangetype": ["Active Directory domain range"], ++ "iparangetyperaw": ["ipa-ad-trust"] ++ }, ++ { ++ "cn": ["IPA.EXAMPLE_id_range"], ++ "ipabaseid": ["447400000"], ++ "ipabaserid": ["1000"], ++ "ipaidrangesize": ["200000"], ++ "iparangetype": ["local domain range"], ++ "iparangetyperaw": ["ipa-local"], ++ "ipasecondarybaserid": ["100000000"] ++ }] ++ } ++ ++ ++def idrange_find_adrange_posix(): ++ """ ++ Return a set of idranges of type ++ "Active Directory trust range with POSIX attributes" ++ """ ++ ++ return { ++ "result": [ ++ { ++ "cn": ["AD.EXAMPLE_id_range"], ++ "ipabaseid": ["1664000000"], ++ "ipaidrangesize": ["200000"], ++ "ipanttrusteddomainsid": ["S-1-5-21-abc"], ++ "iparangetype": [ ++ "Active Directory trust range with POSIX attributes"], ++ "iparangetyperaw": ["ipa-ad-trust-posix"] ++ }, ++ { ++ "cn": ["IPA.EXAMPLE_id_range"], ++ "ipabaseid": ["447400000"], ++ "ipabaserid": ["1000"], ++ "ipaidrangesize": ["200000"], ++ "iparangetype": ["local domain range"], ++ "iparangetyperaw": ["ipa-local"], ++ "ipasecondarybaserid": ["100000000"] ++ }] ++ } ++ ++ + class SSSDDomain: + def __init__(self, return_ipa_server_mode=True, provider='ipa'): + self.return_ipa_server_mode = return_ipa_server_mode +@@ -454,7 +522,8 @@ class TestTrustCatalog(BaseTest): + + @patch('pysss_nss_idmap.getnamebysid') + @patch('ipapython.ipautil.run') +- def test_trust_catalog_ok(self, mock_run, mock_getnamebysid): ++ def test_trust_catalog_adrange(self, mock_run, mock_getnamebysid): ++ """The associated ID ranges are Active Directory domain range""" + # id Administrator@ad.example + dsresult = namedtuple('run', ['returncode', 'error_log']) + dsresult.returncode = 0 +@@ -478,6 +547,11 @@ class TestTrustCatalog(BaseTest): + # get_trust_domains() + m_api.Command.trust_find.side_effect = trust_find() + m_api.Command.trustdomain_find.side_effect = trustdomain_find() ++ m_api.Command.idrange_find.side_effect = [ ++ idrange_find_adrange_type(), ++ idrange_find_adrange_type(), ++ idrange_find_adrange_type() ++ ] + + framework = object() + registry.initialize(framework, config.Config) +@@ -550,6 +624,144 @@ class TestTrustCatalog(BaseTest): + assert result.kw.get('key') == 'AD Domain Controller' + assert result.kw.get('domain') == 'child.example' + ++ @patch('pysss_nss_idmap.getnamebysid') ++ @patch('ipapython.ipautil.run') ++ def test_trust_catalog_posix(self, mock_run, mock_getnamebysid): ++ """AD POSIX ranges""" ++ # id Administrator@ad.example ++ dsresult = namedtuple('run', ['returncode', 'error_log']) ++ dsresult.returncode = 0 ++ dsresult.error_log = '' ++ dsresult.output = 'Active servers:\nAD Global Catalog: ' \ ++ 'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \ ++ 'IPA: master.ipa.vm\n\n' ++ ds2result = namedtuple('run', ['returncode', 'error_log']) ++ ds2result.returncode = 0 ++ ds2result.error_log = '' ++ ds2result.output = 'Active servers:\nAD Global Catalog: ' \ ++ 'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \ ++ ++ mock_run.side_effect = [dsresult, dsresult, ds2result] ++ mock_getnamebysid.side_effect = [ ++ {'S-1-5-21-abc-500': {'name': 'admin@ad.example', 'type': 3}}, ++ {'S-1-5-21-ghi-500': {'name': 'admin@child.ad.example', 'type': 3}}, ++ {'S-1-5-21-def-500': {'name': 'admin@child.example', 'type': 3}} ++ ] ++ ++ # get_trust_domains() ++ m_api.Command.trust_find.side_effect = trust_find() ++ m_api.Command.trustdomain_find.side_effect = trustdomain_find() ++ m_api.Command.idrange_find.side_effect = [ ++ idrange_find_adrange_posix(), ++ idrange_find_adrange_posix(), ++ idrange_find_adrange_posix() ++ ] ++ ++ framework = object() ++ registry.initialize(framework, config.Config) ++ registry.trust_agent = True ++ f = IPATrustCatalogCheck(registry) ++ ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ ++ result = self.results.results[0] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-21-abc' ++ assert result.kw.get('domain') == 'ad.example' ++ assert result.kw.get('type') == 'ipa-ad-trust-posix' ++ ++ result = self.results.results[1] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-22-def' ++ assert result.kw.get('domain') == 'child.ad.example' ++ assert result.kw.get('type') == 'ipa-ad-trust-posix' ++ ++ result = self.results.results[2] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-21-ghi' ++ assert result.kw.get('domain') == 'child.example' ++ assert result.kw.get('type') == 'ipa-ad-trust-posix' ++ ++ @patch('pysss_nss_idmap.getnamebysid') ++ @patch('ipapython.ipautil.run') ++ def test_trust_catalog_posix_missing(self, mock_run, mock_getnamebysid): ++ """AD POSIX ranges""" ++ # id Administrator@ad.example ++ dsresult = namedtuple('run', ['returncode', 'error_log']) ++ dsresult.returncode = 0 ++ dsresult.error_log = '' ++ dsresult.output = 'Active servers:\nAD Global Catalog: ' \ ++ 'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \ ++ 'IPA: master.ipa.vm\n\n' ++ ds2result = namedtuple('run', ['returncode', 'error_log']) ++ ds2result.returncode = 0 ++ ds2result.error_log = '' ++ ds2result.output = 'Active servers:\nAD Global Catalog: ' \ ++ 'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \ ++ ++ mock_run.side_effect = [dsresult, dsresult, ds2result] ++ mock_getnamebysid.side_effect = [ ++ {'S-1-5-21-abc-500': {'name': 'admin@ad.example', 'type': 3}}, ++ {'S-1-5-21-ghi-500': {'name': 'admin@child.ad.example', 'type': 3}}, ++ {'S-1-5-21-def-500': {'name': 'admin@child.example', 'type': 3}} ++ ] ++ ++ # get_trust_domains() ++ m_api.Command.trust_find.side_effect = trust_find() ++ m_api.Command.trustdomain_find.side_effect = trustdomain_find() ++ m_api.Command.idrange_find.side_effect = [ ++ idrange_find_adrange_posix(), ++ {'result': []}, ++ {'result': []} ++ ] ++ ++ framework = object() ++ registry.initialize(framework, config.Config) ++ registry.trust_agent = True ++ f = IPATrustCatalogCheck(registry) ++ ++ self.results = capture_results(f) ++ ++ assert len(self.results) == 3 ++ ++ result = self.results.results[0] ++ assert result.result == constants.SUCCESS ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-21-abc' ++ assert result.kw.get('domain') == 'ad.example' ++ assert result.kw.get('type') == 'ipa-ad-trust-posix' ++ ++ result = self.results.results[1] ++ assert result.result == constants.WARNING ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-22-def' ++ assert result.kw.get('domain') == 'child.ad.example' ++ assert ( ++ result.kw.get('msg') ++ == 'Domain {domain} does not have an idrange' ++ ) ++ ++ result = self.results.results[2] ++ assert result.result == constants.WARNING ++ assert result.source == 'ipahealthcheck.ipa.trust' ++ assert result.check == 'IPATrustCatalogCheck' ++ assert result.kw.get('key') == 'S-1-5-21-ghi' ++ assert result.kw.get('domain') == 'child.example' ++ assert ( ++ result.kw.get('msg') ++ == 'Domain {domain} does not have an idrange' ++ ) ++ + + class Testsidgen(BaseTest): + patches = { +-- +2.39.2 + diff --git a/SOURCES/0004-Catch-exceptions-during-user-group-name-lookup-in-Fi.patch b/SOURCES/0004-Catch-exceptions-during-user-group-name-lookup-in-Fi.patch new file mode 100644 index 0000000..4234843 --- /dev/null +++ b/SOURCES/0004-Catch-exceptions-during-user-group-name-lookup-in-Fi.patch @@ -0,0 +1,142 @@ +From 4906c52b629bfce275558d4701c083f4c020ef32 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Fri, 30 Jun 2023 14:35:40 -0400 +Subject: [PATCH] Catch exceptions during user/group name lookup in FileCheck + +It's possible that one or more of the allowed users/groups +in a file check do not exist on the system. Catch this +exception and try to proceed as best as possible. + +https://github.com/freeipa/freeipa-healthcheck/issues/296 + +Signed-off-by: Rob Crittenden +--- + src/ipahealthcheck/core/files.py | 33 +++++++++++++++++++++++---- + tests/test_core_files.py | 38 ++++++++++++++++++++++++++++++++ + 2 files changed, 67 insertions(+), 4 deletions(-) + +diff --git a/src/ipahealthcheck/core/files.py b/src/ipahealthcheck/core/files.py +index 59e8b76..85d42bc 100644 +--- a/src/ipahealthcheck/core/files.py ++++ b/src/ipahealthcheck/core/files.py +@@ -3,12 +3,15 @@ + # + + import grp ++import logging + import os + import pwd + + from ipahealthcheck.core import constants + from ipahealthcheck.core.plugin import Result, duration + ++logger = logging.getLogger() ++ + + class FileCheck: + """Generic check to validate permission and ownership of files +@@ -77,14 +80,25 @@ class FileCheck: + + found = False + for o in owner: +- fowner = pwd.getpwnam(o) ++ try: ++ fowner = pwd.getpwnam(o) ++ except Exception as e: ++ logging.debug('user lookup "%s" for "%s" failed: %s', ++ o, path, e) ++ continue + if fowner.pw_uid == stat.st_uid: + found = True + break + + if not found: +- actual = pwd.getpwuid(stat.st_uid) + key = '%s_owner' % path.replace('/', '_') ++ try: ++ actual = pwd.getpwuid(stat.st_uid) ++ except Exception: ++ yield Result(self, constants.WARNING, key=key, ++ path=path, type='owner', expected=owner, ++ got='Unknown uid %s' % stat.st_uid) ++ continue + if len(owner) == 1: + msg = 'Ownership of %s is %s and should ' \ + 'be %s' % \ +@@ -104,14 +118,25 @@ class FileCheck: + + found = False + for g in group: +- fgroup = grp.getgrnam(g) ++ try: ++ fgroup = grp.getgrnam(g) ++ except Exception as e: ++ logging.debug('group lookup "%s" for "%s" failed: %s', ++ g, path, e) ++ continue + if fgroup.gr_gid == stat.st_gid: + found = True + break + + if not found: + key = '%s_group' % path.replace('/', '_') +- actual = grp.getgrgid(stat.st_gid) ++ try: ++ actual = grp.getgrgid(stat.st_gid) ++ except Exception: ++ yield Result(self, constants.WARNING, key=key, ++ path=path, type='group', expected=group, ++ got='Unknown gid %s' % stat.st_gid) ++ continue + if len(group) == 1: + msg = 'Group of %s is %s and should ' \ + 'be %s' % \ +diff --git a/tests/test_core_files.py b/tests/test_core_files.py +index 6e3ec38..924d7fa 100644 +--- a/tests/test_core_files.py ++++ b/tests/test_core_files.py +@@ -197,3 +197,41 @@ def test_files_not_found(mock_exists): + for result in my_results.results: + assert result.result == constants.SUCCESS + assert result.kw.get('msg') == 'File does not exist' ++ ++ ++@patch('os.stat') ++@patch('pwd.getpwnam') ++@patch('pwd.getpwuid') ++def test_files_owner_not_found(mock_pwuid, mock_pwnam, mock_stat): ++ mock_pwuid.side_effect = KeyError('getpwnam(): name not found') ++ mock_pwnam.side_effect = KeyError('getpwuid(): uid not found') ++ mock_stat.return_value = make_stat() ++ ++ f = FileCheck() ++ f.files = files ++ ++ results = capture_results(f) ++ ++ my_results = get_results(results, 'owner') ++ for result in my_results.results: ++ assert result.result == constants.WARNING ++ assert result.kw.get('got') == 'Unknown uid 0' ++ ++ ++@patch('os.stat') ++@patch('grp.getgrnam') ++@patch('grp.getgrgid') ++def test_files_group_not_found(mock_grgid, mock_grnam, mock_stat): ++ mock_grgid.side_effect = KeyError('getgrnam(): name not found') ++ mock_grnam.side_effect = KeyError('getgruid(): gid not found') ++ mock_stat.return_value = make_stat() ++ ++ f = FileCheck() ++ f.files = files ++ ++ results = capture_results(f) ++ ++ my_results = get_results(results, 'group') ++ for result in my_results.results: ++ assert result.result == constants.WARNING ++ assert result.kw.get('got') == 'Unknown gid 0' +-- +2.41.0 + diff --git a/SOURCES/0005-Don-t-error-in-DogtagCertsConnectivityCheck-with-ext.patch b/SOURCES/0005-Don-t-error-in-DogtagCertsConnectivityCheck-with-ext.patch new file mode 100644 index 0000000..7fa1b4d --- /dev/null +++ b/SOURCES/0005-Don-t-error-in-DogtagCertsConnectivityCheck-with-ext.patch @@ -0,0 +1,372 @@ +From 29855ec76bcb445543e1f2b16b13e5bcfeb67723 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 27 Mar 2023 16:43:11 -0400 +Subject: [PATCH] Don't error in DogtagCertsConnectivityCheck with external CAs + +The purpose of the check is to validate that communication +with the CA works. In the past we looked up serial number 1 +for this check. The problem is that if the server was +installed with RSNv3 so had no predictable CA serial number. + +It also was broken with externally-issued CA certificate which +cannot be looked up in IPA. + +Instead use the IPA RA agent certificate which should definitely +have a serial number in the IPA CA if one is configured. + +Fixes: https://github.com/freeipa/freeipa-healthcheck/issues/285 + +Signed-off-by: Rob Crittenden +--- + src/ipahealthcheck/dogtag/ca.py | 45 +++----- + tests/test_dogtag_connectivity.py | 175 +++++------------------------- + 2 files changed, 39 insertions(+), 181 deletions(-) + +diff --git a/src/ipahealthcheck/dogtag/ca.py b/src/ipahealthcheck/dogtag/ca.py +index 868876f..4afa5d7 100644 +--- a/src/ipahealthcheck/dogtag/ca.py ++++ b/src/ipahealthcheck/dogtag/ca.py +@@ -12,10 +12,8 @@ from ipahealthcheck.core import constants + from ipalib import api, errors, x509 + from ipaplatform.paths import paths + from ipaserver.install import certs +-from ipaserver.install import ca + from ipaserver.install import krainstance + from ipapython.directivesetter import get_directive +-from ipapython.dn import DN + from cryptography.hazmat.primitives.serialization import Encoding + + logger = logging.getLogger() +@@ -95,6 +93,10 @@ class DogtagCertsConfigCheck(DogtagPlugin): + class DogtagCertsConnectivityCheck(DogtagPlugin): + """ + Test basic connectivity by using cert-show to fetch a cert ++ ++ The RA agent certificate is used because if a CA is configured we ++ know this certificate should exist. Use its serial number to do ++ the lookup. + """ + requires = ('dirsrv',) + +@@ -104,59 +106,38 @@ class DogtagCertsConnectivityCheck(DogtagPlugin): + logger.debug('CA is not configured, skipping connectivity check') + return + +- config = api.Command.config_show() +- +- subject_base = config['result']['ipacertificatesubjectbase'][0] +- ipa_subject = ca.lookup_ca_subject(api, subject_base) + try: +- certs = x509.load_certificate_list_from_file(paths.IPA_CA_CRT) ++ cert = x509.load_certificate_from_file(paths.RA_AGENT_PEM) + except Exception as e: + yield Result(self, constants.ERROR, +- key='ipa_ca_crt_file_missing', +- path=paths.IPA_CA_CRT, ++ key='ipa_ra_crt_file_missing', ++ path=paths.RA_AGENT_PEM, + error=str(e), +- msg='The IPA CA cert file {path} could not be ' ++ msg='The IPA RA cert file {path} could not be ' + 'opened: {error}') + return + +- found = False +- for cert in certs: +- if DN(cert.subject) == ipa_subject: +- found = True +- break +- +- if not found: +- yield Result(self, constants.ERROR, +- key='ipa_ca_cert_not_found', +- subject=str(ipa_subject), +- path=paths.IPA_CA_CRT, +- msg='The CA certificate with subject {subject} ' +- 'was not found in {path}') +- return +- # Load the IPA CA certificate to obtain its serial number. This +- # was traditionally 1 prior to random serial number support. +- # There is nothing special about cert 1. Even if there is no cert +- # serial number 1 but the connection is ok it is considered passing. ++ # We used to use serial #1 but with RSNv3 it can be anything. + try: + api.Command.cert_show(cert.serial_number, all=True) + except errors.CertificateOperationError as e: + if 'not found' in str(e): + yield Result(self, constants.ERROR, +- key='cert_show_1', ++ key='cert_show_ra', + error=str(e), + serial=str(cert.serial_number), + msg='Serial number not found: {error}') + else: + yield Result(self, constants.ERROR, +- key='cert_show_1', ++ key='cert_show_ra', + error=str(e), + serial=str(cert.serial_number), + msg='Request for certificate failed: {error}') + except Exception as e: + yield Result(self, constants.ERROR, +- key='cert_show_1', ++ key='cert_show_ra', + error=str(e), + serial=str(cert.serial_number), +- msg='Request for certificate failed: {error') ++ msg='Request for certificate failed: {error}') + else: + yield Result(self, constants.SUCCESS) +diff --git a/tests/test_dogtag_connectivity.py b/tests/test_dogtag_connectivity.py +index d81e598..4413fe1 100644 +--- a/tests/test_dogtag_connectivity.py ++++ b/tests/test_dogtag_connectivity.py +@@ -13,14 +13,23 @@ from ipahealthcheck.dogtag.ca import DogtagCertsConnectivityCheck + + from ipalib.errors import CertificateOperationError + from ipaplatform.paths import paths +-from ipapython.dn import DN ++ ++ ++default_subject_base = [{ ++ 'result': ++ { ++ 'ipacertificatesubjectbase': [f'O={m_api.env.realm}'], ++ }, ++}] + + + class IPACertificate: + def __init__(self, serial_number=1, +- subject='CN=Certificate Authority, O=%s' % m_api.env.realm): ++ subject='CN=Certificate Authority, O=%s' % m_api.env.realm, ++ issuer='CN=Certificate Authority, O=%s' % m_api.env.realm): + self.serial_number = serial_number + self.subject = subject ++ self.issuer = issuer + + def __eq__(self, other): + return self.serial_number == other.serial_number +@@ -50,18 +59,15 @@ class TestCAConnectivity(BaseTest): + Mock(return_value=CAInstance()), + } + +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_ok(self, mock_load_cert, mock_ca_subject): ++ @patch('ipalib.x509.load_certificate_from_file') ++ def test_ca_connection_ok(self, mock_load_cert): + """CA connectivity check when cert_show returns a valid value""" + m_api.Command.cert_show.side_effect = None + m_api.Command.config_show.side_effect = subject_base + m_api.Command.cert_show.return_value = { + u'result': {u'revoked': False} + } +- mock_load_cert.return_value = [IPACertificate(12345)] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') ++ mock_load_cert.return_value = IPACertificate(12345) + + framework = object() + registry.initialize(framework, config.Config) +@@ -76,10 +82,8 @@ class TestCAConnectivity(BaseTest): + assert result.source == 'ipahealthcheck.dogtag.ca' + assert result.check == 'DogtagCertsConnectivityCheck' + +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_cert_not_found(self, mock_load_cert, +- mock_ca_subject): ++ @patch('ipalib.x509.load_certificate_from_file') ++ def test_ca_connection_cert_not_found(self, mock_load_cert): + """CA connectivity check for a cert that doesn't exist""" + m_api.Command.cert_show.reset_mock() + m_api.Command.config_show.side_effect = subject_base +@@ -87,9 +91,7 @@ class TestCAConnectivity(BaseTest): + message='Certificate operation cannot be completed: ' + 'EXCEPTION (Certificate serial number 0x0 not found)' + ) +- mock_load_cert.return_value = [IPACertificate()] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') ++ mock_load_cert.return_value = IPACertificate(serial_number=7) + + framework = object() + registry.initialize(framework, config.Config) +@@ -103,46 +105,16 @@ class TestCAConnectivity(BaseTest): + assert result.result == constants.ERROR + assert result.source == 'ipahealthcheck.dogtag.ca' + assert result.check == 'DogtagCertsConnectivityCheck' +- assert result.kw.get('key') == 'cert_show_1' +- assert result.kw.get('serial') == '1' ++ assert result.kw.get('key') == 'cert_show_ra' ++ assert result.kw.get('serial') == '7' + assert result.kw.get('msg') == 'Serial number not found: {error}' + +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_cert_file_not_found(self, mock_load_cert, +- mock_ca_subject): ++ @patch('ipalib.x509.load_certificate_from_file') ++ def test_ca_connection_cert_file_not_found(self, mock_load_cert): + """CA connectivity check for a cert that doesn't exist""" + m_api.Command.cert_show.reset_mock() + m_api.Command.config_show.side_effect = subject_base + mock_load_cert.side_effect = FileNotFoundError() +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') +- +- framework = object() +- registry.initialize(framework, config.Config) +- f = DogtagCertsConnectivityCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 1 +- +- result = self.results.results[0] +- assert result.result == constants.ERROR +- assert result.source == 'ipahealthcheck.dogtag.ca' +- assert result.check == 'DogtagCertsConnectivityCheck' +- assert result.kw.get('key') == 'ipa_ca_crt_file_missing' +- assert result.kw.get('path') == paths.IPA_CA_CRT +- +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_cert_not_in_file_list(self, mock_load_cert, +- mock_ca_subject): +- """CA connectivity check for a cert that isn't in IPA_CA_CRT""" +- m_api.Command.cert_show.reset_mock() +- m_api.Command.config_show.side_effect = bad_subject_base +- mock_load_cert.return_value = [IPACertificate()] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- 'O=BAD') + + framework = object() + registry.initialize(framework, config.Config) +@@ -156,26 +128,18 @@ class TestCAConnectivity(BaseTest): + assert result.result == constants.ERROR + assert result.source == 'ipahealthcheck.dogtag.ca' + assert result.check == 'DogtagCertsConnectivityCheck' +- bad = bad_subject_base[0]['result']['ipacertificatesubjectbase'][0] +- bad_subject = DN(f'CN=Certificate Authority,{bad}') +- assert DN(result.kw['subject']) == bad_subject +- assert result.kw['path'] == paths.IPA_CA_CRT +- assert result.kw['msg'] == ( +- 'The CA certificate with subject {subject} was not found in {path}' +- ) ++ assert result.kw.get('key') == 'ipa_ra_crt_file_missing' ++ assert result.kw.get('path') == paths.RA_AGENT_PEM + +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_down(self, mock_load_cert, mock_ca_subject): ++ @patch('ipalib.x509.load_certificate_from_file') ++ def test_ca_connection_down(self, mock_load_cert): + """CA connectivity check with the CA down""" + m_api.Command.cert_show.side_effect = CertificateOperationError( + message='Certificate operation cannot be completed: ' + 'Unable to communicate with CMS (503)' + ) + m_api.Command.config_show.side_effect = subject_base +- mock_load_cert.return_value = [IPACertificate()] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') ++ mock_load_cert.return_value = IPACertificate() + + framework = object() + registry.initialize(framework, config.Config) +@@ -192,90 +156,3 @@ class TestCAConnectivity(BaseTest): + assert result.kw.get('msg') == ( + 'Request for certificate failed: {error}' + ) +- +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_multiple_ok(self, mock_load_cert, mock_ca_subject): +- """CA connectivity check when cert_show returns a valid value""" +- m_api.Command.cert_show.side_effect = None +- m_api.Command.config_show.side_effect = subject_base +- m_api.Command.cert_show.return_value = { +- u'result': {u'revoked': False} +- } +- mock_load_cert.return_value = [ +- IPACertificate(1, 'CN=something'), +- IPACertificate(12345), +- ] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') +- +- framework = object() +- registry.initialize(framework, config.Config) +- f = DogtagCertsConnectivityCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 1 +- +- result = self.results.results[0] +- assert result.result == constants.SUCCESS +- assert result.source == 'ipahealthcheck.dogtag.ca' +- +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_multiple_ok_reverse(self, mock_load_cert, +- mock_ca_subject): +- """CA connectivity check when cert_show returns a valid value""" +- m_api.Command.cert_show.side_effect = None +- m_api.Command.config_show.side_effect = subject_base +- m_api.Command.cert_show.return_value = { +- u'result': {u'revoked': False} +- } +- mock_load_cert.return_value = [ +- IPACertificate(12345), +- IPACertificate(1, 'CN=something'), +- ] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') +- +- framework = object() +- registry.initialize(framework, config.Config) +- f = DogtagCertsConnectivityCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 1 +- +- result = self.results.results[0] +- assert result.result == constants.SUCCESS +- assert result.source == 'ipahealthcheck.dogtag.ca' +- +- @patch('ipaserver.install.ca.lookup_ca_subject') +- @patch('ipalib.x509.load_certificate_list_from_file') +- def test_ca_connection_not_found(self, mock_load_cert, mock_ca_subject): +- """CA connectivity check when cert_show returns a valid value""" +- m_api.Command.cert_show.side_effect = None +- m_api.Command.config_show.side_effect = subject_base +- m_api.Command.cert_show.return_value = { +- u'result': {u'revoked': False} +- } +- mock_load_cert.return_value = [ +- IPACertificate(1, 'CN=something'), +- ] +- mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), +- f'O={m_api.env.realm}') +- +- framework = object() +- registry.initialize(framework, config.Config) +- f = DogtagCertsConnectivityCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 1 +- +- result = self.results.results[0] +- assert result.result == constants.ERROR +- assert result.source == 'ipahealthcheck.dogtag.ca' +- assert result.kw['msg'] == ( +- 'The CA certificate with subject {subject} was not found in {path}' +- ) +-- +2.41.0 + diff --git a/SPECS/freeipa-healthcheck.spec b/SPECS/freeipa-healthcheck.spec index bbc526c..e7c6e1e 100644 --- a/SPECS/freeipa-healthcheck.spec +++ b/SPECS/freeipa-healthcheck.spec @@ -17,7 +17,7 @@ Name: %{prefix}-healthcheck Version: 0.12 -Release: 1%{?dist} +Release: 4%{?dist} Summary: Health check tool for %{productname} BuildArch: noarch License: GPLv3 @@ -27,6 +27,9 @@ Source1: ipahealthcheck.conf Patch0001: 0001-Remove-ipaclustercheck.patch Patch0002: 0002-Disable-two-failing-tests.patch +Patch0003: 0003-Skip-AD-domains-with-posix-ranges-in-the-catalog-che.patch +Patch0004: 0004-Catch-exceptions-during-user-group-name-lookup-in-Fi.patch +Patch0005: 0005-Don-t-error-in-DogtagCertsConnectivityCheck-with-ext.patch Requires: %{name}-core = %{version}-%{release} Requires: %{prefix}-server @@ -156,7 +159,16 @@ PYTHONPATH=src PATH=$PATH:$RPM_BUILD_ROOT/usr/bin pytest-3 tests/test_* %changelog -* Wed Mar 15 2023 MSVSphere Packaging Team - 0.9-9 +* Mon Jul 24 2023 Rob Crittenden - 0.12-4 +- Error in DogtagCertsConnectivityCheckCA with external CA (#2224595) + +* Thu Jul 06 2023 Rob Crittenden - 0.12-3 +- Catch exceptions during user/group name lookup in FileCheck (#2218912) + +* Tue Apr 25 2023 Rob Crittenden - 0.12-2 +- Skip AD domains with posix ranges in the catalog check (#2188135) + +* Wed Mar 15 2023 MSVSphere Packaging Team - 0.12-1 - Rebuilt for MSVSphere 9.1. * Thu Dec 01 2022 Rob Crittenden - 0.12-1