From 717a8751a41506a36c7483bb427f0f630bad4abc Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: May 16 2023 06:06:54 +0000 Subject: import ipa-healthcheck-0.12-1.module+el8.8.0+17583+8c783c60 --- diff --git a/.gitignore b/.gitignore index 7f7a491..6bddb01 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/freeipa-healthcheck-0.7.tar.gz +SOURCES/0.12.tar.gz diff --git a/.ipa-healthcheck.metadata b/.ipa-healthcheck.metadata index 6b83e2a..ee30a78 100644 --- a/.ipa-healthcheck.metadata +++ b/.ipa-healthcheck.metadata @@ -1 +1 @@ -93e4d1554a11ba94947a816b008fb1092abbf591 SOURCES/freeipa-healthcheck-0.7.tar.gz +dc05dc0ca441dcb1a87e3b3bd7d440d79c17ac0a SOURCES/0.12.tar.gz diff --git a/SOURCES/0001-Remove-ipaclustercheck.patch b/SOURCES/0001-Remove-ipaclustercheck.patch new file mode 100644 index 0000000..040236d --- /dev/null +++ b/SOURCES/0001-Remove-ipaclustercheck.patch @@ -0,0 +1,639 @@ +From 9d5f9d21442ee483044fc55a5c02039af23869d7 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 1 Dec 2022 14:22:46 -0500 +Subject: [PATCH] Remove ipaclustercheck + +--- + setup.py | 12 +- + src/ipaclustercheck/__init__.py | 5 - + src/ipaclustercheck/core/__init__.py | 0 + src/ipaclustercheck/core/main.py | 32 ------ + src/ipaclustercheck/core/output.py | 68 ----------- + src/ipaclustercheck/ipa/__init__.py | 0 + src/ipaclustercheck/ipa/crlmanager.py | 36 ------ + src/ipaclustercheck/ipa/plugin.py | 117 ------------------- + src/ipaclustercheck/ipa/ruv.py | 155 -------------------------- + tests/test_cluster_ruv.py | 106 ------------------ + 10 files changed, 1 insertion(+), 530 deletions(-) + delete mode 100644 src/ipaclustercheck/__init__.py + delete mode 100644 src/ipaclustercheck/core/__init__.py + delete mode 100644 src/ipaclustercheck/core/main.py + delete mode 100644 src/ipaclustercheck/core/output.py + delete mode 100644 src/ipaclustercheck/ipa/__init__.py + delete mode 100644 src/ipaclustercheck/ipa/crlmanager.py + delete mode 100644 src/ipaclustercheck/ipa/plugin.py + delete mode 100644 src/ipaclustercheck/ipa/ruv.py + delete mode 100644 tests/test_cluster_ruv.py + +diff --git a/setup.py b/setup.py +index 0cfa486..b9e1ca1 100644 +--- a/setup.py ++++ b/setup.py +@@ -4,7 +4,7 @@ from setuptools import find_packages, setup + setup( + name='ipahealthcheck', + version='0.12', +- namespace_packages=['ipahealthcheck', 'ipaclustercheck'], ++ namespace_packages=['ipahealthcheck'], + package_dir={'': 'src'}, + # packages=find_packages(where='src'), + packages=[ +@@ -14,14 +14,11 @@ setup( + 'ipahealthcheck.ipa', + 'ipahealthcheck.meta', + 'ipahealthcheck.system', +- 'ipaclustercheck.core', +- 'ipaclustercheck.ipa', + ], + entry_points={ + # creates bin/ipahealthcheck + 'console_scripts': [ + 'ipa-healthcheck = ipahealthcheck.core.main:main', +- 'ipa-clustercheck = ipaclustercheck.core.main:main', + ], + # subsystem registries + 'ipahealthcheck.registry': [ +@@ -72,13 +69,6 @@ setup( + 'ipahealthcheck.system': [ + 'filesystemspace = ipahealthcheck.system.filesystemspace', + ], +- 'ipaclustercheck.registry': [ +- 'ipaclustercheck.ipa = ipaclustercheck.ipa.plugin:registry', +- ], +- 'ipaclustercheck.ipa': [ +- 'crl = ipaclustercheck.ipa.crlmanager', +- 'ruv = ipaclustercheck.ipa.ruv', +- ], + }, + classifiers=[ + 'Programming Language :: Python :: 3.6', +diff --git a/src/ipaclustercheck/__init__.py b/src/ipaclustercheck/__init__.py +deleted file mode 100644 +index 6c91ef7..0000000 +--- a/src/ipaclustercheck/__init__.py ++++ /dev/null +@@ -1,5 +0,0 @@ +-# +-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license +-# +- +-__import__('pkg_resources').declare_namespace(__name__) +diff --git a/src/ipaclustercheck/core/__init__.py b/src/ipaclustercheck/core/__init__.py +deleted file mode 100644 +index e69de29..0000000 +diff --git a/src/ipaclustercheck/core/main.py b/src/ipaclustercheck/core/main.py +deleted file mode 100644 +index f475832..0000000 +--- a/src/ipaclustercheck/core/main.py ++++ /dev/null +@@ -1,32 +0,0 @@ +-# +-# Copyright (C) 2020 FreeIPA Contributors see COPYING for license +-# +- +-import sys +- +-from ipaclustercheck.core.output import output_registry +-from ipahealthcheck.core.core import RunChecks +- +- +-class ClusterChecks(RunChecks): +- +- def add_options(self): +- parser = self.parser +- parser.add_argument('--directory', dest='dir', +- help='Directory holding healthcheck logs') +- +- def validate_options(self): +- super().validate_options() +- +- if self.options.dir is None: +- print("--directory containing logs to check is required") +- return 1 +- +- return None +- +- +-def main(): +- clusterchecks = ClusterChecks(['ipaclustercheck.registry'], +- '/etc/ipa/clustercheck.conf', +- output_registry, 'ansible') +- sys.exit(clusterchecks.run_healthcheck()) +diff --git a/src/ipaclustercheck/core/output.py b/src/ipaclustercheck/core/output.py +deleted file mode 100644 +index 909eac4..0000000 +--- a/src/ipaclustercheck/core/output.py ++++ /dev/null +@@ -1,68 +0,0 @@ +-# +-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license +-# +- +-import json +-from ipahealthcheck.core.output import OutputRegistry, Output +- +- +-output_registry = OutputRegistry() +- +-class ClusterOutput(Output): +- """Base class for writing/display output of cluster results +- +- severity doesn't apply in this case so exclude those. +- """ +- def __init__(self, options): +- self.filename = options.output_file +- +- def strip_output(self, results): +- """Nothing to strip out""" +- return list(results.output()) +- +- def generate(self, data): +- raise NotImplementedError +- +- +-@output_registry +-class Ansible(ClusterOutput): +- """Output information JSON format for consumption by Ansible +- +- Required keywords in a Result: +- name - unique identifier for the return value +- +- One of these is required: +- value - the return value. Type? I dunno yet +- error - if an error was returned +- """ +- +- options = ( +- ('--indent', dict(dest='indent', type=int, default=2, +- help='Indention level of JSON output')), +- ) +- +- def __init__(self, options): +- super().__init__(options) +- self.indent = options.indent +- +- def generate(self, data): +- output = [] +- for line in data: +- kw = line.get('kw') +- name = kw.get('name') +- value = kw.get('value') +- error = kw.get('error') +- +- if value and error: +- value = '%s: %s' % (error, value) +- elif error: +- value = error +- +- rval = {'%s' % name: value} +- output.append(rval) +- +- output = json.dumps(output, indent=self.indent) +- if self.filename is None: +- output += '\n' +- +- return output +diff --git a/src/ipaclustercheck/ipa/__init__.py b/src/ipaclustercheck/ipa/__init__.py +deleted file mode 100644 +index e69de29..0000000 +diff --git a/src/ipaclustercheck/ipa/crlmanager.py b/src/ipaclustercheck/ipa/crlmanager.py +deleted file mode 100644 +index 6806d74..0000000 +--- a/src/ipaclustercheck/ipa/crlmanager.py ++++ /dev/null +@@ -1,36 +0,0 @@ +-# +-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license +-# +- +-from ipaclustercheck.ipa.plugin import ClusterPlugin, registry, find_checks +-from ipahealthcheck.core.plugin import Result, duration +-from ipahealthcheck.core import constants +- +- +-@registry +-class ClusterCRLManagerCheck(ClusterPlugin): +- +- @duration +- def check(self): +- data = self.registry.json +- crlmanagers = [] +- +- for fqdn in data.keys(): +- output = find_checks(data[fqdn], 'ipahealthcheck.ipa.roles', +- 'IPACRLManagerCheck') +- enabled = output[0].get('kw').get('crlgen_enabled') +- if enabled: +- crlmanagers.append(fqdn) +- if len(crlmanagers) == 0: +- yield Result(self, constants.ERROR, +- name='crlmanager', +- error='No CRL Manager defined') +- elif len(crlmanagers) == 1: +- yield Result(self, constants.SUCCESS, +- name='crlmanager', +- value=crlmanagers[0]) +- else: +- yield Result(self, constants.ERROR, +- name='crlmanager', +- value=','.join(crlmanagers), +- error='Multiple CRL Managers defined') +diff --git a/src/ipaclustercheck/ipa/plugin.py b/src/ipaclustercheck/ipa/plugin.py +deleted file mode 100644 +index a111988..0000000 +--- a/src/ipaclustercheck/ipa/plugin.py ++++ /dev/null +@@ -1,117 +0,0 @@ +-# +-# Copyright (C) 2020 FreeIPA Contributors see COPYING for license +-# +- +-from copy import deepcopy +-import json +-import logging +-from os import listdir +-from os.path import isfile, join +- +-from ipahealthcheck.core.plugin import Plugin, Registry +-from ipalib import api +- +- +-logger = logging.getLogger() +- +-def find_checks(data, source, check): +- """Look through the dict for a matching source and check. +- +- data: dict of source and check output +- source: name of source to find +- check: name of check to find +- +- Returns list of contents of source + check or empty list +- """ +- rval = [] +- for d in data: +- if d.get('source') == source and d.get('check') == check: +- rval.append(d) +- +- return rval +- +- +-def get_masters(data): +- """ +- Return the list of known masters +- +- This is determined from the list of loaded healthcheck logs. It +- is possible that mixed versions are used so some may not be +- reporting the full list of masters, so check them all, and raise +- an exception if the list cannot be determined. +- """ +- test_masters = list(data) +- masters = None +- for master in test_masters: +- output = find_checks(data[master], 'ipahealthcheck.ipa.meta', +- 'IPAMetaCheck') +- if len(output) == 0: +- raise ValueError('Unable to determine full list of masters. ' +- 'ipahealthcheck.ipa.meta:IPAMetaCheck not ' +- 'found.') +- +- masters = output[0].get('kw').get('masters') +- if masters: +- return masters +- +- raise ValueError('Unable to determine full list of masters. ' +- 'None of ipahealthcheck.ipa.meta:IPAMetaCheck ' +- 'contain masters.') +- +- +-class ClusterPlugin(Plugin): +- pass +- +- +-class ClusterRegistry(Registry): +- def __init__(self): +- super().__init__() +- self.json = None +- +- def initialize(self, framework, config, options=None): +- super().initialize(framework, config, options) +- +- self.json = {} +- +- self.load_files(options.dir) +- +- if not api.isdone('finalize'): +- if not api.isdone('bootstrap'): +- api.bootstrap(in_server=True, +- context='ipahealthcheck', +- log=None) +- if not api.isdone('finalize'): +- api.finalize() +- +- def load_files(self, dir): +- if self.json: +- return +- +- files = [f for f in listdir(dir) if isfile(join(dir, f))] +- for file in files: +- fname = join(dir, file) +- logger.debug("Reading %s", fname) +- try: +- with open(fname, 'r') as fd: +- data = fd.read() +- except Exception as e: +- logger.error("Unable to read %s: %s", fname, e) +- continue +- +- try: +- data = json.loads(data) +- except Exception as e: +- logger.error("Unable to parse JSON in %s: %s", fname, e) +- continue +- +- meta = find_checks(data, 'ipahealthcheck.meta.core', +- 'MetaCheck') +- if meta: +- fqdn = meta[0].get('kw').get('fqdn') +- self.json[fqdn] = deepcopy(data) +- else: +- logger.error("No fqdn defined in JSON in %s", fname) +- continue +- +- +-registry = ClusterRegistry() +diff --git a/src/ipaclustercheck/ipa/ruv.py b/src/ipaclustercheck/ipa/ruv.py +deleted file mode 100644 +index 6477738..0000000 +--- a/src/ipaclustercheck/ipa/ruv.py ++++ /dev/null +@@ -1,155 +0,0 @@ +-# +-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license +-# +- +-import logging +- +-from ipaclustercheck.ipa.plugin import ( +- ClusterPlugin, +- registry, +- find_checks, +- get_masters +-) +-from ipahealthcheck.core.plugin import Result, duration +-from ipahealthcheck.core import constants +-from ipalib import api +-from ipapython.dn import DN +- +- +-logger = logging.getLogger() +- +- +-@registry +-class ClusterRUVCheck(ClusterPlugin): +- +- # TODO: confirm that all masters are represented, otherwise the +- # trustworthiness of dangling RUV is mixed. +- # +- # gah, need to provide full list of all masters in a check. +- +- @duration +- def check(self): +- data = self.registry.json +- +- # Start with the list of masters from the file(s) collected +- # and find a MetaCheck with a full list of masters. For +- # backwards compatibility. +- try: +- masters = get_masters(data) +- except ValueError as e: +- yield Result(self, constants.ERROR, +- name='dangling_ruv', +- error=str(e)) +- return +- +- if len(data.keys()) < len(masters): +- yield Result(self, constants.ERROR, +- name='dangling_ruv', +- error='Unable to determine list of RUVs, missing ' +- 'some masters: %s' % +- ''.join(set(masters) - set(data.keys()))) +- return +- +- # collect the full set of known RUVs for each master +- info = {} +- for master in masters: +- info[master] = { +- 'ca': False, # does the host have ca configured? +- 'ruvs': set(), # ruvs on the host +- 'csruvs': set(), # csruvs on the host +- 'clean_ruv': set(), # ruvs to be cleaned from the host +- 'clean_csruv': set() # csruvs to be cleaned from the host +- } +- +- for fqdn in data.keys(): +- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv', +- 'KnownRUVCheck') +- for output in outputs: +- if not 'suffix' in output.get('kw'): +- continue +- basedn = DN(output.get('kw').get('suffix')) +- +- ruvset = set() +- ruvtmp = output.get('kw').get('ruvs') +- for ruv in ruvtmp: +- ruvset.add(tuple(ruv)) +- +- if basedn == DN('o=ipaca'): +- info[fqdn]['ca'] = True +- info[fqdn]['csruvs'] = ruvset +- elif basedn == api.env.basedn: +- info[fqdn]['ruvs'] = ruvset +- else: +- yield Result(self, constants.WARNING, +- name='dangling_ruv', +- error='Unknown suffix found %s expected %s' +- % (basedn, api.env.basedn)) +- +- # Collect the nsDS5ReplicaID for each master +- ruvs = set() +- csruvs = set() +- for fqdn in data.keys(): +- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv', +- 'RUVCheck') +- for output in outputs: +- if not 'key' in output.get('kw'): +- continue +- basedn = DN(output.get('kw').get('key')) +- ruv = (fqdn, (output.get('kw').get('ruv'))) +- if basedn == DN('o=ipaca'): +- csruvs.add(ruv) +- elif basedn == api.env.basedn: +- ruvs.add(ruv) +- else: +- yield Result(self, constants.WARNING, +- name='dangling_ruv', +- error='Unknown suffix found %s expected %s' +- % (basedn, api.env.basedn)) +- +- dangles = False +- # get the dangling RUVs +- for master_info in info.values(): +- for ruv in master_info['ruvs']: +- if ruv not in ruvs: +- master_info['clean_ruv'].add(ruv) +- dangles = True +- +- # if ca is not configured, there will be no csruvs in master_info +- for csruv in master_info['csruvs']: +- if csruv not in csruvs: +- master_info['clean_csruv'].add(csruv) +- dangles = True +- +- clean_csruvs = set() +- clean_ruvs = set() +- if dangles: +- for _unused, master_info in info.items(): +- for ruv in master_info['clean_ruv']: +- logger.debug( +- "Dangling RUV id: %s, hostname: %s", ruv[1], ruv[0] +- ) +- clean_ruvs.add(ruv[1]) +- for csruv in master_info['clean_csruv']: +- logger.debug( +- "Dangling CS RUV id: %s, hostname: %s", +- csruv[1], +- csruv[0] +- ) +- clean_csruvs.add(csruv[1]) +- +- if clean_ruvs: +- yield Result(self, constants.ERROR, +- name='dangling_ruv', +- value=', '.join(clean_ruvs)) +- else: +- yield Result(self, constants.SUCCESS, +- name='dangling_ruv', +- value='No dangling RUVs found') +- if clean_csruvs: +- yield Result(self, constants.ERROR, +- name='dangling_csruv', +- value=', '.join(clean_csruvs)) +- else: +- yield Result(self, constants.SUCCESS, +- name='dangling_csruv', +- value='No dangling CS RUVs found') +diff --git a/tests/test_cluster_ruv.py b/tests/test_cluster_ruv.py +deleted file mode 100644 +index 7583c84..0000000 +--- a/tests/test_cluster_ruv.py ++++ /dev/null +@@ -1,106 +0,0 @@ +-# +-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license +-# +- +-from base import BaseTest +-from util import capture_results +- +-from ipahealthcheck.core import config +-from ipaclustercheck.ipa.plugin import ClusterRegistry +-from ipaclustercheck.ipa.ruv import ClusterRUVCheck +- +-import clusterdata +- +- +-class RUVRegistry(ClusterRegistry): +- def load_files(self, dir): +- self.json = dir +- +- +-class Options: +- def __init__(self, data): +- self.data = data +- +- @property +- def dir(self): +- return self.data +- +- +-registry = RUVRegistry() +- +- +-class TestClusterRUV(BaseTest): +- +- def test_no_ruvs(self): +- """Single master test that has never created a replica +- +- This type of master will have no RUVs created at all. +- """ +- framework = object() +- registry.initialize(framework, config.Config, +- Options(clusterdata.ONE_MASTER)) +- f = ClusterRUVCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 2 +- result = self.results.results[0] +- assert result.kw.get('name') == 'dangling_ruv' +- assert result.kw.get('value') == 'No dangling RUVs found' +- result = self.results.results[1] +- assert result.kw.get('name') == 'dangling_csruv' +- assert result.kw.get('value') == 'No dangling CS RUVs found' +- +- def test_six_ruvs_ok(self): +- """Three master test with each having a CA, no dangling +- """ +- framework = object() +- registry.initialize(framework, config.Config, +- Options(clusterdata.THREE_MASTERS_OK)) +- f = ClusterRUVCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 2 +- result = self.results.results[0] +- assert result.kw.get('name') == 'dangling_ruv' +- assert result.kw.get('value') == 'No dangling RUVs found' +- result = self.results.results[1] +- assert result.kw.get('name') == 'dangling_csruv' +- assert result.kw.get('value') == 'No dangling CS RUVs found' +- +- def test_six_ruvs_ipa_bad(self): +- """Three master test with each having a CA, dangling IPA RUV +- """ +- framework = object() +- registry.initialize(framework, config.Config, +- Options(clusterdata.THREE_MASTERS_BAD_IPA_RUV)) +- f = ClusterRUVCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 2 +- result = self.results.results[0] +- assert result.kw.get('name') == 'dangling_ruv' +- assert result.kw.get('value') == '9' +- result = self.results.results[1] +- assert result.kw.get('name') == 'dangling_csruv' +- assert result.kw.get('value') == 'No dangling CS RUVs found' +- +- def test_six_ruvs_cs_bad(self): +- """Three master test with each having a CA, dangling CA RUV +- """ +- framework = object() +- registry.initialize(framework, config.Config, +- Options(clusterdata.THREE_MASTERS_BAD_CS_RUV)) +- f = ClusterRUVCheck(registry) +- +- self.results = capture_results(f) +- +- assert len(self.results) == 2 +- result = self.results.results[0] +- assert result.kw.get('name') == 'dangling_ruv' +- assert result.kw.get('value') == 'No dangling RUVs found' +- result = self.results.results[1] +- assert result.kw.get('name') == 'dangling_csruv' +- assert result.kw.get('value') == '9' +-- +2.38.1 + diff --git a/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch b/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch deleted file mode 100644 index ed363bc..0000000 --- a/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 611a7d51ac6b49770cdc0da02d101023a4a49536 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Fri, 3 May 2019 10:10:57 -0400 -Subject: [PATCH] Remove requirement for pytest-runner since PyPI isn't - available - -We won't be executing make check because the dependencies aren't -available due to IDM being in a module. ---- - setup.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/setup.py b/setup.py -index 801323f..c3cd215 100644 ---- a/setup.py -+++ b/setup.py -@@ -60,6 +60,5 @@ setup( - 'Programming Language :: Python :: 3.6', - ], - python_requires='!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', -- setup_requires=['pytest-runner',], - tests_require=['pytest',], - ) --- -2.17.2 - diff --git a/SOURCES/0002-Disable-two-failing-tests.patch b/SOURCES/0002-Disable-two-failing-tests.patch new file mode 100644 index 0000000..6abba0f --- /dev/null +++ b/SOURCES/0002-Disable-two-failing-tests.patch @@ -0,0 +1,64 @@ +From d2cd8292d8a1d7c2fd2a5f978f8ed76c0769e5e9 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Tue, 8 Feb 2022 14:16:06 -0500 +Subject: [PATCH] Disable two failing tests + +These test that healthcheck can properly detect when IPA +is not installed or configured. Its not ideal to remove them +from the check process but they aren't critical. +--- + tests/test_commands.py | 41 ----------------------------------------- + 1 file changed, 41 deletions(-) + +diff --git a/tests/test_commands.py b/tests/test_commands.py +index 988d7fc..e14114b 100644 +--- a/tests/test_commands.py ++++ b/tests/test_commands.py +@@ -14,44 +14,3 @@ def test_version(): + """ + output = run(['ipa-healthcheck', '--version'], env=os.environ) + assert 'ipahealthcheck' in output.raw_output.decode('utf-8') +- +- +-@pytest.fixture +-def python_ipalib_dir(tmpdir): +- ipalib_dir = tmpdir.mkdir("ipalib") +- ipalib_dir.join("__init__.py").write("") +- +- def _make_facts(configured=None): +- if configured is None: +- module_text = "" +- elif isinstance(configured, bool): +- module_text = f"def is_ipa_configured(): return {configured}" +- else: +- raise TypeError( +- f"'configured' must be None or bool, got '{configured!r}'" +- ) +- +- ipalib_dir.join("facts.py").write(module_text) +- return str(tmpdir) +- +- return _make_facts +- +- +-def test_ipa_notinstalled(python_ipalib_dir, monkeypatch): +- """ +- Test ipa-healthcheck handles the missing IPA stuff +- """ +- monkeypatch.setenv("PYTHONPATH", python_ipalib_dir(configured=None)) +- output = run(["ipa-healthcheck"], raiseonerr=False, env=os.environ) +- assert output.returncode == 1 +- assert "IPA server is not installed" in output.raw_output.decode("utf-8") +- +- +-def test_ipa_unconfigured(python_ipalib_dir, monkeypatch): +- """ +- Test ipa-healthcheck handles the unconfigured IPA server +- """ +- monkeypatch.setenv("PYTHONPATH", python_ipalib_dir(configured=False)) +- output = run(["ipa-healthcheck"], raiseonerr=False, env=os.environ) +- assert output.returncode == 1 +- assert "IPA server is not configured" in output.raw_output.decode("utf-8") +-- +2.31.1 + diff --git a/SOURCES/0002-Remove-ipaclustercheck.patch b/SOURCES/0002-Remove-ipaclustercheck.patch deleted file mode 100644 index 21d08ff..0000000 --- a/SOURCES/0002-Remove-ipaclustercheck.patch +++ /dev/null @@ -1,630 +0,0 @@ -From ea9e00e47307b4ab81cc31c37796dd7b6a4c8785 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu, 29 Oct 2020 11:49:22 -0400 -Subject: [PATCH] Remove ipaclustercheck - -Not shipping it from upstream into Fedora just yet ---- - setup.py | 12 +- - src/ipaclustercheck/__init__.py | 5 - - src/ipaclustercheck/core/__init__.py | 0 - src/ipaclustercheck/core/main.py | 30 ----- - src/ipaclustercheck/core/output.py | 67 ------------ - src/ipaclustercheck/ipa/__init__.py | 0 - src/ipaclustercheck/ipa/crlmanager.py | 36 ------ - src/ipaclustercheck/ipa/plugin.py | 114 ------------------- - src/ipaclustercheck/ipa/ruv.py | 151 -------------------------- - tests/test_cluster_ruv.py | 106 ------------------ - 10 files changed, 1 insertion(+), 520 deletions(-) - delete mode 100644 src/ipaclustercheck/__init__.py - delete mode 100644 src/ipaclustercheck/core/__init__.py - delete mode 100644 src/ipaclustercheck/core/main.py - delete mode 100644 src/ipaclustercheck/core/output.py - delete mode 100644 src/ipaclustercheck/ipa/__init__.py - delete mode 100644 src/ipaclustercheck/ipa/crlmanager.py - delete mode 100644 src/ipaclustercheck/ipa/plugin.py - delete mode 100644 src/ipaclustercheck/ipa/ruv.py - delete mode 100644 tests/test_cluster_ruv.py - -diff --git a/setup.py b/setup.py -index 2b519fc..c25b2d7 100644 ---- a/setup.py -+++ b/setup.py -@@ -4,7 +4,7 @@ from setuptools import find_packages, setup - setup( - name='ipahealthcheck', - version='0.7', -- namespace_packages=['ipahealthcheck', 'ipaclustercheck'], -+ namespace_packages=['ipahealthcheck'], - package_dir={'': 'src'}, - # packages=find_packages(where='src'), - packages=[ -@@ -14,14 +14,11 @@ setup( - 'ipahealthcheck.ipa', - 'ipahealthcheck.meta', - 'ipahealthcheck.system', -- 'ipaclustercheck.core', -- 'ipaclustercheck.ipa', - ], - entry_points={ - # creates bin/ipahealthcheck - 'console_scripts': [ - 'ipa-healthcheck = ipahealthcheck.core.main:main', -- 'ipa-clustercheck = ipaclustercheck.core.main:main', - ], - # subsystem registries - 'ipahealthcheck.registry': [ -@@ -69,13 +66,6 @@ setup( - 'ipahealthcheck.system': [ - 'filesystemspace = ipahealthcheck.system.filesystemspace', - ], -- 'ipaclustercheck.registry': [ -- 'ipaclustercheck.ipa = ipaclustercheck.ipa.plugin:registry', -- ], -- 'ipaclustercheck.ipa': [ -- 'crl = ipaclustercheck.ipa.crlmanager', -- 'ruv = ipaclustercheck.ipa.ruv', -- ], - }, - classifiers=[ - 'Programming Language :: Python :: 3.6', -diff --git a/src/ipaclustercheck/__init__.py b/src/ipaclustercheck/__init__.py -deleted file mode 100644 -index 6c91ef7..0000000 ---- a/src/ipaclustercheck/__init__.py -+++ /dev/null -@@ -1,5 +0,0 @@ --# --# Copyright (C) 2019 FreeIPA Contributors see COPYING for license --# -- --__import__('pkg_resources').declare_namespace(__name__) -diff --git a/src/ipaclustercheck/core/__init__.py b/src/ipaclustercheck/core/__init__.py -deleted file mode 100644 -index e69de29..0000000 -diff --git a/src/ipaclustercheck/core/main.py b/src/ipaclustercheck/core/main.py -deleted file mode 100644 -index 45fda9f..0000000 ---- a/src/ipaclustercheck/core/main.py -+++ /dev/null -@@ -1,30 +0,0 @@ --# --# Copyright (C) 2020 FreeIPA Contributors see COPYING for license --# -- --import sys -- --from ipaclustercheck.core.output import output_registry --from ipahealthcheck.core.core import RunChecks -- -- --class ClusterChecks(RunChecks): -- -- def add_options(self): -- parser = self.parser -- parser.add_argument('--directory', dest='dir', -- help='Directory holding healthcheck logs') -- -- def validate_options(self): -- super(ClusterChecks, self).validate_options() -- -- if self.options.dir is None: -- print("--directory containing logs to check is required") -- return 1 -- -- --def main(): -- clusterchecks = ClusterChecks(['ipaclustercheck.registry'], -- '/etc/ipa/clustercheck.conf', -- output_registry, 'ansible') -- sys.exit(clusterchecks.run_healthcheck()) -diff --git a/src/ipaclustercheck/core/output.py b/src/ipaclustercheck/core/output.py -deleted file mode 100644 -index 2f02b52..0000000 ---- a/src/ipaclustercheck/core/output.py -+++ /dev/null -@@ -1,67 +0,0 @@ --# --# Copyright (C) 2019 FreeIPA Contributors see COPYING for license --# -- --import json --import sys --from ipahealthcheck.core.output import OutputRegistry, Output -- -- --output_registry = OutputRegistry() -- --class ClusterOutput(Output): -- """Base class for writing/display output of cluster results -- -- severity doesn't apply in this case so exclude those. -- """ -- def __init__(self, options): -- self.filename = options.outfile -- -- def strip_output(self, results): -- """Nothing to strip out""" -- return [result for result in results.output()] -- -- --@output_registry --class Ansible(ClusterOutput): -- """Output information JSON format for consumption by Ansible -- -- Required keywords in a Result: -- name - unique identifier for the return value -- -- One of these is required: -- value - the return value. Type? I dunno yet -- error - if an error was returned -- """ -- -- options = ( -- ('--indent', dict(dest='indent', type=int, default=2, -- help='Indention level of JSON output')), -- ) -- -- def __init__(self, options): -- super(Ansible, self).__init__(options) -- self.indent = options.indent -- -- def generate(self, data): -- output = [] -- for line in data: -- kw = line.get('kw') -- result = line.get('result') -- name = kw.get('name') -- value = kw.get('value') -- error = kw.get('error') -- -- if value and error: -- value = '%s: %s' % (error, value) -- elif error: -- value = error -- -- rval = {'%s' % name: value} -- output.append(rval) -- -- output = json.dumps(output, indent=self.indent) -- if self.filename is None: -- output += '\n' -- -- return output -diff --git a/src/ipaclustercheck/ipa/__init__.py b/src/ipaclustercheck/ipa/__init__.py -deleted file mode 100644 -index e69de29..0000000 -diff --git a/src/ipaclustercheck/ipa/crlmanager.py b/src/ipaclustercheck/ipa/crlmanager.py -deleted file mode 100644 -index 6806d74..0000000 ---- a/src/ipaclustercheck/ipa/crlmanager.py -+++ /dev/null -@@ -1,36 +0,0 @@ --# --# Copyright (C) 2019 FreeIPA Contributors see COPYING for license --# -- --from ipaclustercheck.ipa.plugin import ClusterPlugin, registry, find_checks --from ipahealthcheck.core.plugin import Result, duration --from ipahealthcheck.core import constants -- -- --@registry --class ClusterCRLManagerCheck(ClusterPlugin): -- -- @duration -- def check(self): -- data = self.registry.json -- crlmanagers = [] -- -- for fqdn in data.keys(): -- output = find_checks(data[fqdn], 'ipahealthcheck.ipa.roles', -- 'IPACRLManagerCheck') -- enabled = output[0].get('kw').get('crlgen_enabled') -- if enabled: -- crlmanagers.append(fqdn) -- if len(crlmanagers) == 0: -- yield Result(self, constants.ERROR, -- name='crlmanager', -- error='No CRL Manager defined') -- elif len(crlmanagers) == 1: -- yield Result(self, constants.SUCCESS, -- name='crlmanager', -- value=crlmanagers[0]) -- else: -- yield Result(self, constants.ERROR, -- name='crlmanager', -- value=','.join(crlmanagers), -- error='Multiple CRL Managers defined') -diff --git a/src/ipaclustercheck/ipa/plugin.py b/src/ipaclustercheck/ipa/plugin.py -deleted file mode 100644 -index d9b6063..0000000 ---- a/src/ipaclustercheck/ipa/plugin.py -+++ /dev/null -@@ -1,114 +0,0 @@ --# --# Copyright (C) 2020 FreeIPA Contributors see COPYING for license --# -- --from copy import deepcopy --import json --import logging --from os import listdir --from os.path import isfile, join -- --from ipahealthcheck.core.plugin import Plugin, Registry --from ipalib import api -- -- --logger = logging.getLogger() -- --def find_checks(data, source, check): -- """Look through the dict for a matching source and check. -- -- data: dict of source and check output -- source: name of source to find -- check: name of check to find -- -- Returns list of contents of source + check or empty list -- """ -- rval = [] -- for d in data: -- if d.get('source') == source and d.get('check') == check: -- rval.append(d) -- -- return rval -- -- --def get_masters(data): -- """ -- Return the list of known masters -- -- This is determined from the list of loaded healthcheck logs. It -- is possible that mixed versions are used so some may not be -- reporting the full list of masters, so check them all, and raise -- an exception if the list cannot be determined. -- """ -- test_masters = list(data) -- masters = None -- for master in test_masters: -- output = find_checks(data[master], 'ipahealthcheck.ipa.meta', -- 'IPAMetaCheck') -- if len(output) == 0: -- raise ValueError('Unable to determine full list of masters. ' -- 'ipahealthcheck.ipa.meta:IPAMetaCheck not ' -- 'found.') -- -- masters = output[0].get('kw').get('masters') -- if masters: -- return masters -- -- raise ValueError('Unable to determine full list of masters. ' -- 'None of ipahealthcheck.ipa.meta:IPAMetaCheck ' -- 'contain masters.') -- -- --class ClusterPlugin(Plugin): -- def __init__(self, registry): -- super(ClusterPlugin, self).__init__(registry) -- -- --class ClusterRegistry(Registry): -- def initialize(self, framework, config, options): -- super(ClusterRegistry, self).initialize(framework, config, options) -- -- self.json = {} -- -- self.load_files(options.dir) -- -- if not api.isdone('finalize'): -- if not api.isdone('bootstrap'): -- api.bootstrap(in_server=True, -- context='ipahealthcheck', -- log=None) -- if not api.isdone('finalize'): -- api.finalize() -- -- def load_files(self, dir): -- if self.json: -- return -- -- files = [f for f in listdir(dir) if isfile(join(dir, f))] -- for file in files: -- fname = join(dir, file) -- logger.debug("Reading %s", fname) -- try: -- with open(fname, 'r') as fd: -- data = fd.read() -- except Exception as e: -- logger.error("Unable to read %s: %s", fname, e) -- continue -- -- try: -- data = json.loads(data) -- except Exception as e: -- logger.error("Unable to parse JSON in %s: %s", fname, e) -- continue -- -- meta = find_checks(data, 'ipahealthcheck.meta.core', -- 'MetaCheck') -- if meta: -- fqdn = meta[0].get('kw').get('fqdn') -- self.json[fqdn] = deepcopy(data) -- else: -- logger.error("No fqdn defined in JSON in %s", fname) -- continue -- -- --registry = ClusterRegistry() -diff --git a/src/ipaclustercheck/ipa/ruv.py b/src/ipaclustercheck/ipa/ruv.py -deleted file mode 100644 -index 0e51da9..0000000 ---- a/src/ipaclustercheck/ipa/ruv.py -+++ /dev/null -@@ -1,151 +0,0 @@ --# --# Copyright (C) 2019 FreeIPA Contributors see COPYING for license --# -- --import logging -- --from ipaclustercheck.ipa.plugin import ( -- ClusterPlugin, -- registry, -- find_checks, -- get_masters --) --from ipahealthcheck.core.plugin import Result, duration --from ipahealthcheck.core import constants --from ipalib import api --from ipapython.dn import DN -- -- --logger = logging.getLogger() -- -- --@registry --class ClusterRUVCheck(ClusterPlugin): -- -- # TODO: confirm that all masters are represented, otherwise the -- # trustworthiness of dangling RUV is mixed. -- # -- # gah, need to provide full list of all masters in a check. -- -- @duration -- def check(self): -- data = self.registry.json -- -- # Start with the list of masters from the file(s) collected -- # and find a MetaCheck with a full list of masters. For -- # backwards compatibility. -- try: -- masters = get_masters(data) -- except ValueError as e: -- yield Result(self, constants.ERROR, -- name='dangling_ruv', -- error=str(e)) -- return -- -- if len(data.keys()) < len(masters): -- yield Result(self, constants.ERROR, -- name='dangling_ruv', -- error='Unable to determine list of RUVs, missing ' -- 'some masters: %s' % -- ''.join(set(masters) - set(data.keys()))) -- return -- -- # collect the full set of known RUVs for each master -- info = {} -- for master in masters: -- info[master] = { -- 'ca': False, # does the host have ca configured? -- 'ruvs': set(), # ruvs on the host -- 'csruvs': set(), # csruvs on the host -- 'clean_ruv': set(), # ruvs to be cleaned from the host -- 'clean_csruv': set() # csruvs to be cleaned from the host -- } -- -- for fqdn in data.keys(): -- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv', -- 'KnownRUVCheck') -- for output in outputs: -- if not 'suffix' in output.get('kw'): -- continue -- basedn = DN(output.get('kw').get('suffix')) -- -- ruvset = set() -- ruvtmp = output.get('kw').get('ruvs') -- for ruv in ruvtmp: -- ruvset.add(tuple(ruv)) -- -- if basedn == DN('o=ipaca'): -- info[fqdn]['ca'] = True -- info[fqdn]['csruvs'] = ruvset -- elif basedn == api.env.basedn: -- info[fqdn]['ruvs'] = ruvset -- else: -- yield Result(self, constants.WARNING, -- name='dangling_ruv', -- error='Unknown suffix found %s expected %s' -- % (basedn, api.env.basedn)) -- -- # Collect the nsDS5ReplicaID for each master -- ruvs = set() -- csruvs = set() -- for fqdn in data.keys(): -- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv', -- 'RUVCheck') -- for output in outputs: -- if not 'key' in output.get('kw'): -- continue -- basedn = DN(output.get('kw').get('key')) -- ruv = (fqdn, (output.get('kw').get('ruv'))) -- if basedn == DN('o=ipaca'): -- csruvs.add(ruv) -- elif basedn == api.env.basedn: -- ruvs.add(ruv) -- else: -- yield Result(self, constants.WARNING, -- name='dangling_ruv', -- error='Unknown suffix found %s expected %s' -- % (basedn, api.env.basedn)) -- -- dangles = False -- # get the dangling RUVs -- for master_info in info.values(): -- for ruv in master_info['ruvs']: -- if ruv not in ruvs: -- master_info['clean_ruv'].add(ruv) -- dangles = True -- -- # if ca is not configured, there will be no csruvs in master_info -- for csruv in master_info['csruvs']: -- if csruv not in csruvs: -- master_info['clean_csruv'].add(csruv) -- dangles = True -- -- clean_csruvs = set() -- clean_ruvs = set() -- if dangles: -- for master_cn, master_info in info.items(): -- for ruv in master_info['clean_ruv']: -- logger.debug('Dangling RUV id: {id}, hostname: {host}' -- .format(id=ruv[1], host=ruv[0])) -- clean_ruvs.add(ruv[1]) -- for csruv in master_info['clean_csruv']: -- logger.debug('Dangling CS RUV id: {id}, hostname: {host}' -- .format(id=csruv[1], host=csruv[0])) -- clean_csruvs.add(csruv[1]) -- -- if clean_ruvs: -- yield Result(self, constants.ERROR, -- name='dangling_ruv', -- value=', '.join(clean_ruvs)) -- else: -- yield Result(self, constants.SUCCESS, -- name='dangling_ruv', -- value='No dangling RUVs found') -- if clean_csruvs: -- yield Result(self, constants.ERROR, -- name='dangling_csruv', -- value=', '.join(clean_csruvs)) -- else: -- yield Result(self, constants.SUCCESS, -- name='dangling_csruv', -- value='No dangling CS RUVs found') -diff --git a/tests/test_cluster_ruv.py b/tests/test_cluster_ruv.py -deleted file mode 100644 -index 7583c84..0000000 ---- a/tests/test_cluster_ruv.py -+++ /dev/null -@@ -1,106 +0,0 @@ --# --# Copyright (C) 2019 FreeIPA Contributors see COPYING for license --# -- --from base import BaseTest --from util import capture_results -- --from ipahealthcheck.core import config --from ipaclustercheck.ipa.plugin import ClusterRegistry --from ipaclustercheck.ipa.ruv import ClusterRUVCheck -- --import clusterdata -- -- --class RUVRegistry(ClusterRegistry): -- def load_files(self, dir): -- self.json = dir -- -- --class Options: -- def __init__(self, data): -- self.data = data -- -- @property -- def dir(self): -- return self.data -- -- --registry = RUVRegistry() -- -- --class TestClusterRUV(BaseTest): -- -- def test_no_ruvs(self): -- """Single master test that has never created a replica -- -- This type of master will have no RUVs created at all. -- """ -- framework = object() -- registry.initialize(framework, config.Config, -- Options(clusterdata.ONE_MASTER)) -- f = ClusterRUVCheck(registry) -- -- self.results = capture_results(f) -- -- assert len(self.results) == 2 -- result = self.results.results[0] -- assert result.kw.get('name') == 'dangling_ruv' -- assert result.kw.get('value') == 'No dangling RUVs found' -- result = self.results.results[1] -- assert result.kw.get('name') == 'dangling_csruv' -- assert result.kw.get('value') == 'No dangling CS RUVs found' -- -- def test_six_ruvs_ok(self): -- """Three master test with each having a CA, no dangling -- """ -- framework = object() -- registry.initialize(framework, config.Config, -- Options(clusterdata.THREE_MASTERS_OK)) -- f = ClusterRUVCheck(registry) -- -- self.results = capture_results(f) -- -- assert len(self.results) == 2 -- result = self.results.results[0] -- assert result.kw.get('name') == 'dangling_ruv' -- assert result.kw.get('value') == 'No dangling RUVs found' -- result = self.results.results[1] -- assert result.kw.get('name') == 'dangling_csruv' -- assert result.kw.get('value') == 'No dangling CS RUVs found' -- -- def test_six_ruvs_ipa_bad(self): -- """Three master test with each having a CA, dangling IPA RUV -- """ -- framework = object() -- registry.initialize(framework, config.Config, -- Options(clusterdata.THREE_MASTERS_BAD_IPA_RUV)) -- f = ClusterRUVCheck(registry) -- -- self.results = capture_results(f) -- -- assert len(self.results) == 2 -- result = self.results.results[0] -- assert result.kw.get('name') == 'dangling_ruv' -- assert result.kw.get('value') == '9' -- result = self.results.results[1] -- assert result.kw.get('name') == 'dangling_csruv' -- assert result.kw.get('value') == 'No dangling CS RUVs found' -- -- def test_six_ruvs_cs_bad(self): -- """Three master test with each having a CA, dangling CA RUV -- """ -- framework = object() -- registry.initialize(framework, config.Config, -- Options(clusterdata.THREE_MASTERS_BAD_CS_RUV)) -- f = ClusterRUVCheck(registry) -- -- self.results = capture_results(f) -- -- assert len(self.results) == 2 -- result = self.results.results[0] -- assert result.kw.get('name') == 'dangling_ruv' -- assert result.kw.get('value') == 'No dangling RUVs found' -- result = self.results.results[1] -- assert result.kw.get('name') == 'dangling_csruv' -- assert result.kw.get('value') == '9' --- -2.25.4 - diff --git a/SOURCES/0003-Fix-logging-issue-related-to-dtype.patch b/SOURCES/0003-Fix-logging-issue-related-to-dtype.patch new file mode 100644 index 0000000..6ff821d --- /dev/null +++ b/SOURCES/0003-Fix-logging-issue-related-to-dtype.patch @@ -0,0 +1,29 @@ +From 0f485a0921a39c08e7259f9b38f0b10e425384a5 Mon Sep 17 00:00:00 2001 +From: root +Date: Mon, 5 Dec 2022 16:17:17 -0500 +Subject: [PATCH] Fix logging issue related to dtype + +It is an integer in earlier versions of python3-dns and a class +in later versions. Log the integer value. + +Related: #2099484 +--- + src/ipahealthcheck/ipa/idns.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipa/idns.py b/ipa/idns.py +index e294db2..1adb69d 100644 +--- a/src/ipahealthcheck/ipa/idns.py ++++ b/src/ipahealthcheck/ipa/idns.py +@@ -176,7 +176,7 @@ class IPADNSSystemRecordsCheck(IPAPlugin): + qname = "ipa-ca." + api.env.domain + "." + ipa_ca_records = [] + for dtype in (rdatatype.A, rdatatype.AAAA): +- logger.debug("Search DNS for %s records of %s", dtype.name, qname) ++ logger.debug("Search DNS for %s records of %s", dtype, qname) + try: + answers = resolve(qname, dtype) + except DNSException as e: +-- +2.31.1 + diff --git a/SOURCES/0003-Use-trust-find-and-trustdomain-find-to-identify-all-.patch b/SOURCES/0003-Use-trust-find-and-trustdomain-find-to-identify-all-.patch deleted file mode 100644 index f643aa0..0000000 --- a/SOURCES/0003-Use-trust-find-and-trustdomain-find-to-identify-all-.patch +++ /dev/null @@ -1,303 +0,0 @@ -From 886153da7dd1ca1f5d37dd9c1e2141850b7177b2 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 17 Nov 2020 20:37:52 -0500 -Subject: [PATCH] Use trust-find and trustdomain-find to identify all AD trusts - -Not all AD domains are visible to trust-find. For each trust -iterate over trustdomain-find to find the complete -list of domains. - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/ipa/trust.py | 20 +++-- - tests/test_ipa_trust.py | 155 ++++++++++++++++++++------------ - 2 files changed, 108 insertions(+), 67 deletions(-) - -diff --git a/src/ipahealthcheck/ipa/trust.py b/src/ipahealthcheck/ipa/trust.py -index 0abe5cd..00971c4 100644 ---- a/src/ipahealthcheck/ipa/trust.py -+++ b/src/ipahealthcheck/ipa/trust.py -@@ -42,16 +42,18 @@ def get_trust_domains(): - - Each entry is a dictionary representating an AD domain. - """ -- result = api.Command.trust_find() -- results = result['result'] - trust_domains = [] -- for result in results: -- if result.get('trusttype')[0] == 'Active Directory domain': -- domain = dict() -- domain['domain'] = result.get('cn')[0] -- domain['domainsid'] = result.get('ipanttrusteddomainsid')[0] -- domain['netbios'] = result.get('ipantflatname')[0] -- trust_domains.append(domain) -+ trusts = api.Command.trust_find(pkey_only=True, raw=True) -+ for trust in trusts['result']: -+ for cn in trust.get('cn'): -+ trustdomains = api.Command.trustdomain_find(cn, raw=True) -+ for trustdomain in trustdomains['result']: -+ domain = dict() -+ domain['domain'] = trustdomain.get('cn')[0] -+ domain['domainsid'] = trustdomain.get( -+ 'ipanttrusteddomainsid')[0] -+ domain['netbios'] = trustdomain.get('ipantflatname')[0] -+ trust_domains.append(domain) - return trust_domains - - -diff --git a/tests/test_ipa_trust.py b/tests/test_ipa_trust.py -index 3c4b947..f3a9f27 100644 ---- a/tests/test_ipa_trust.py -+++ b/tests/test_ipa_trust.py -@@ -72,6 +72,56 @@ class mock_ldap_conn: - return tuple() - - -+# -+# Construct a setup with two direct trusts and one sub domain -+# -+def trust_find(): -+ return [{ -+ 'result': [ -+ { -+ 'cn': ['ad.example'], -+ }, -+ { -+ 'cn': ['child.example'], -+ }, -+ ] -+ }] -+ -+ -+def trustdomain_find(): -+ return [ -+ { -+ "result": [ -+ { -+ "cn": ["ad.example"], -+ "ipantflatname": ["ADROOT"], -+ "ipanttrusteddomainsid": ["S-1-5-21-abc"], -+ "ipanttrusttype": ["2"], -+ "ipanttrustattributes": ["8"], -+ }, -+ { -+ "cn": ["child.ad.example"], -+ "ipantflatname": ["CHILD.ADROOT"], -+ "ipanttrusteddomainsid": ["S-1-5-22-def"], -+ "ipanttrusttype": ["2"], -+ "ipanttrustattributes": ["1"], -+ }, -+ ], -+ }, -+ { -+ "result": [ -+ { -+ "cn": ["child.example"], -+ "ipantflatname": ["CHILD"], -+ "ipanttrusteddomainsid": ["S-1-5-21-ghi"], -+ "ipanttrusttype": ["2"], -+ "ipanttrustattributes": ["8"], -+ }, -+ ], -+ }, -+ ] -+ -+ - class SSSDDomain: - def __init__(self, return_ipa_server_mode=True, provider='ipa'): - self.return_ipa_server_mode = return_ipa_server_mode -@@ -246,31 +296,17 @@ class TestTrustDomains(BaseTest): - dlresult.returncode = 0 - dlresult.error_log = '' - dlresult.output = 'implicit_files\nipa.example\nad.example\n' \ -- 'child.example\n' -+ 'child.ad.example\nchild.example\n' - olresult = namedtuple('run', ['returncode', 'error_log']) - olresult.returncode = 0 - olresult.error_log = '' - olresult.output = 'Online status: Online\n\n' - -- mock_run.side_effect = [dlresult, olresult, olresult] -+ mock_run.side_effect = [dlresult, olresult, olresult, olresult] - - # get_trust_domains() -- m_api.Command.trust_find.side_effect = [{ -- 'result': [ -- { -- 'cn': ['ad.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-abc'], -- 'trusttype': ['Active Directory domain'], -- }, -- { -- 'cn': ['child.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-def'], -- 'trusttype': ['Active Directory domain'], -- }, -- ] -- }] -+ m_api.Command.trust_find.side_effect = trust_find() -+ m_api.Command.trustdomain_find.side_effect = trustdomain_find() - - framework = object() - registry.initialize(framework, config.Config) -@@ -279,15 +315,17 @@ class TestTrustDomains(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 3 -+ assert len(self.results) == 4 - - result = self.results.results[0] - assert result.result == constants.SUCCESS - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustDomainsCheck' - assert result.kw.get('key') == 'domain-list' -- assert result.kw.get('trust_domains') == 'ad.example, child.example' -- assert result.kw.get('sssd_domains') == 'ad.example, child.example' -+ assert result.kw.get('trust_domains') == \ -+ 'ad.example, child.ad.example, child.example' -+ assert result.kw.get('sssd_domains') == \ -+ 'ad.example, child.ad.example, child.example' - - result = self.results.results[1] - assert result.result == constants.SUCCESS -@@ -301,6 +339,13 @@ class TestTrustDomains(BaseTest): - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustDomainsCheck' - assert result.kw.get('key') == 'domain-status' -+ assert result.kw.get('domain') == 'child.ad.example' -+ -+ result = self.results.results[3] -+ assert result.result == constants.SUCCESS -+ assert result.source == 'ipahealthcheck.ipa.trust' -+ assert result.check == 'IPATrustDomainsCheck' -+ assert result.kw.get('key') == 'domain-status' - assert result.kw.get('domain') == 'child.example' - - @patch('ipapython.ipautil.run') -@@ -319,22 +364,8 @@ class TestTrustDomains(BaseTest): - mock_run.side_effect = [dlresult, olresult, olresult] - - # get_trust_domains() -- m_api.Command.trust_find.side_effect = [{ -- 'result': [ -- { -- 'cn': ['ad.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-abc'], -- 'trusttype': ['Active Directory domain'], -- }, -- { -- 'cn': ['child.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-def'], -- 'trusttype': ['Active Directory domain'], -- }, -- ] -- }] -+ m_api.Command.trust_find.side_effect = trust_find() -+ m_api.Command.trustdomain_find.side_effect = trustdomain_find() - - framework = object() - registry.initialize(framework, config.Config) -@@ -350,7 +381,8 @@ class TestTrustDomains(BaseTest): - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustDomainsCheck' - assert result.kw.get('key') == 'domain-list' -- assert result.kw.get('trust_domains') == 'ad.example, child.example' -+ assert result.kw.get('trust_domains') == \ -+ 'ad.example, child.ad.example, child.example' - assert result.kw.get('sssd_domains') == 'child.example' - - result = self.results.results[1] -@@ -428,29 +460,16 @@ class TestTrustCatalog(BaseTest): - ds2result.output = 'Active servers:\nAD Global Catalog: ' \ - 'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \ - -- mock_run.side_effect = [dsresult, ds2result] -+ mock_run.side_effect = [dsresult, ds2result, 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 = [{ -- 'result': [ -- { -- 'cn': ['ad.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-abc'], -- 'trusttype': ['Active Directory domain'], -- }, -- { -- 'cn': ['child.example'], -- 'ipantflatname': ['ADROOT'], -- 'ipanttrusteddomainsid': ['S-1-5-21-def'], -- 'trusttype': ['Active Directory domain'], -- }, -- ] -- }] -+ m_api.Command.trust_find.side_effect = trust_find() -+ m_api.Command.trustdomain_find.side_effect = trustdomain_find() - - framework = object() - registry.initialize(framework, config.Config) -@@ -459,7 +478,7 @@ class TestTrustCatalog(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 6 -+ assert len(self.results) == 9 - - result = self.results.results[0] - assert result.result == constants.SUCCESS -@@ -487,20 +506,40 @@ class TestTrustCatalog(BaseTest): - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustCatalogCheck' - assert result.kw.get('key') == 'Domain Security Identifier' -- assert result.kw.get('sid') == 'S-1-5-21-def' -+ assert result.kw.get('sid') == 'S-1-5-22-def' - - result = self.results.results[4] - assert result.result == constants.SUCCESS - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustCatalogCheck' - assert result.kw.get('key') == 'AD Global Catalog' -- assert result.kw.get('domain') == 'child.example' -+ assert result.kw.get('domain') == 'child.ad.example' - - result = self.results.results[5] - assert result.result == constants.SUCCESS - assert result.source == 'ipahealthcheck.ipa.trust' - assert result.check == 'IPATrustCatalogCheck' - assert result.kw.get('key') == 'AD Domain Controller' -+ -+ result = self.results.results[6] -+ assert result.result == constants.SUCCESS -+ assert result.source == 'ipahealthcheck.ipa.trust' -+ assert result.check == 'IPATrustCatalogCheck' -+ assert result.kw.get('key') == 'Domain Security Identifier' -+ assert result.kw.get('sid') == 'S-1-5-21-ghi' -+ -+ result = self.results.results[7] -+ assert result.result == constants.SUCCESS -+ assert result.source == 'ipahealthcheck.ipa.trust' -+ assert result.check == 'IPATrustCatalogCheck' -+ assert result.kw.get('key') == 'AD Global Catalog' -+ assert result.kw.get('domain') == 'child.example' -+ -+ result = self.results.results[8] -+ assert result.result == constants.SUCCESS -+ assert result.source == 'ipahealthcheck.ipa.trust' -+ assert result.check == 'IPATrustCatalogCheck' -+ assert result.kw.get('key') == 'AD Domain Controller' - assert result.kw.get('domain') == 'child.example' - - --- -2.25.4 - diff --git a/SOURCES/0004-result-names-are-not-translated-when-reading-input-f.patch b/SOURCES/0004-result-names-are-not-translated-when-reading-input-f.patch deleted file mode 100644 index 7dcef19..0000000 --- a/SOURCES/0004-result-names-are-not-translated-when-reading-input-f.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 5efeafa16a893cb6277ece4d573184bb64ee2744 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Mon, 30 Nov 2020 15:06:03 -0500 -Subject: [PATCH] result names are not translated when reading input from json - file - -The strings were being retained so when processing the results to -determine the return code it was always a 1 because none of -the values were being translated. It was always comparing -the string like 'SUCCESS' to constants.SUCCESS which is 0. - -https://bugzilla.redhat.com/show_bug.cgi?id=1866558 ---- - src/ipahealthcheck/core/constants.py | 20 +++++++++++++++++++- - src/ipahealthcheck/core/plugin.py | 4 ++-- - tests/test_results.py | 8 ++++++++ - 3 files changed, 29 insertions(+), 3 deletions(-) - -diff --git a/src/ipahealthcheck/core/constants.py b/src/ipahealthcheck/core/constants.py -index a55469c..b6ee029 100644 ---- a/src/ipahealthcheck/core/constants.py -+++ b/src/ipahealthcheck/core/constants.py -@@ -36,7 +36,25 @@ def getLevelName(level): - is passed in instead the corresponding string representation is - returned. - """ -- return _levelToName.get(level) or _nameToLevel.get(level) or level -+ name = _levelToName.get(level) or _nameToLevel.get(level) -+ if name is not None: -+ return name -+ -+ return level -+ -+ -+def getLevel(name): -+ """ -+ Translate between level text and their numeric constants -+ -+ If the level is one of the predefined levels then returns the -+ corresponding number. -+ """ -+ level = _nameToLevel.get(name) -+ if level is not None: -+ return level -+ -+ return name - - - CONFIG_FILE = '/etc/ipahealthcheck/ipahealthcheck.conf' -diff --git a/src/ipahealthcheck/core/plugin.py b/src/ipahealthcheck/core/plugin.py -index 7ac923a..26dddd4 100644 ---- a/src/ipahealthcheck/core/plugin.py -+++ b/src/ipahealthcheck/core/plugin.py -@@ -6,7 +6,7 @@ import uuid - from datetime import datetime - from functools import wraps - --from ipahealthcheck.core.constants import getLevelName -+from ipahealthcheck.core.constants import getLevelName, getLevel - - - def duration(f): -@@ -204,7 +204,7 @@ def json_to_results(data): - results = Results() - - for line in data: -- result = line.pop('result') -+ result = getLevel(line.pop('result')) - source = line.pop('source') - check = line.pop('check') - duration = line.pop('duration') -diff --git a/tests/test_results.py b/tests/test_results.py -index dd6e8fd..99c18d7 100644 ---- a/tests/test_results.py -+++ b/tests/test_results.py -@@ -69,3 +69,11 @@ def test_Result(): - assert x['result'] in (constants.getLevelName(constants.SUCCESS), - constants.getLevelName(constants.CRITICAL)) - assert len(x['kw']) == 0 -+ -+ -+def test_getLevel(): -+ assert constants.getLevel('SUCCESS') == constants.SUCCESS -+ assert constants.getLevel('WARNING') == constants.WARNING -+ assert constants.getLevel('ERROR') == constants.ERROR -+ assert constants.getLevel('CRITICAL') == constants.CRITICAL -+ assert constants.getLevel('FOO') == 'FOO' --- -2.25.4 - diff --git a/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch b/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch deleted file mode 100644 index a2528fd..0000000 --- a/SOURCES/0005-Add-check-for-IPA-KRA-Agent.patch +++ /dev/null @@ -1,236 +0,0 @@ -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 deleted file mode 100644 index ccd5d82..0000000 --- a/SOURCES/0006-Add-tests-for-KRA-Agent-validation.patch +++ /dev/null @@ -1,207 +0,0 @@ -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 deleted file mode 100644 index 2df3c72..0000000 --- a/SOURCES/0007-Return-user-friendly-message-when-no-issues-found.patch +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index ae8d15a..0000000 --- a/SOURCES/0009-Add-checks-to-detect-mismatch-of-certificates.patch +++ /dev/null @@ -1,291 +0,0 @@ -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 deleted file mode 100644 index 2ded66c..0000000 --- a/SOURCES/0010-Add-tests-for-certificate-mismatch-detection.patch +++ /dev/null @@ -1,305 +0,0 @@ -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 deleted file mode 100644 index 9c2988d..0000000 --- a/SOURCES/0011-Add-log-files-to-the-set-of-files-checked-for-owner-.patch +++ /dev/null @@ -1,99 +0,0 @@ -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 deleted file mode 100644 index cfd8d48..0000000 --- a/SOURCES/0012-Handle-files-that-don-t-exist-in-FileCheck.patch +++ /dev/null @@ -1,63 +0,0 @@ -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/SOURCES/0013-Add-service-check-dependencies.patch b/SOURCES/0013-Add-service-check-dependencies.patch deleted file mode 100644 index f79ec4a..0000000 --- a/SOURCES/0013-Add-service-check-dependencies.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 235198b41a0932a7a190124fff5f8c1afa5d6679 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Wed, 5 May 2021 15:35:19 -0400 -Subject: [PATCH] Add service check dependencies - -Since 389-ds is the heart of IPA there may not be a point in checking -all dependent services. ipa-dnskeysyncd in particular doesn't like -when it can't connect and tries to restart itself multiple times. - -Note that this currently works because the services are sorted -alphabetically and dirsrv appears near the top. Re-ordering may be -necessary in the future. - -I'm choosing not to add dirsrv to the other services because they -return cleanly if it is not available. - -https://bugzilla.redhat.com/show_bug.cgi?id=1776687 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/core.py | 20 ++++++++++++++++++++ - src/ipahealthcheck/meta/services.py | 2 ++ - 2 files changed, 22 insertions(+) - -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index a4af690..eaa2d9c 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -81,6 +81,26 @@ def run_service_plugins(plugins, source, check): - if not isinstance(plugin, ServiceCheck): - continue - -+ # Try to save some time to not check dependent services if the -+ # parent is down. -+ if not set(plugin.requires).issubset(available): -+ # A required service is not available. Either it hasn't been -+ # checked yet or it isn't running. If not running break. -+ running = True -+ for result in results.results: -+ if result.check in plugin.requires: -+ # if not in available but in results the service failed -+ running = False -+ break -+ if not running: -+ logger.debug( -+ 'Skipping %s:%s because %s service(s) not running', -+ plugin.__class__.__module__, -+ plugin.__class__.__name__, -+ ', '.join(set(plugin.requires) - set(available)) -+ ) -+ continue -+ - logger.debug('Calling check %s', plugin) - for result in plugin.check(): - # always run the service checks so dependencies work -diff --git a/src/ipahealthcheck/meta/services.py b/src/ipahealthcheck/meta/services.py -index a987108..5d80728 100644 ---- a/src/ipahealthcheck/meta/services.py -+++ b/src/ipahealthcheck/meta/services.py -@@ -92,6 +92,8 @@ class ipa_dnskeysyncd(IPAServiceCheck): - def check(self): - self.service_name = 'ipa-dnskeysyncd' - -+ requires = ('dirsrv',) -+ - if not bindinstance.named_conf_exists(): - return () - --- -2.31.1 - diff --git a/SOURCES/0014-Filter-out-the-pki-healthcheck-sources-if-IPA-CA-is-.patch b/SOURCES/0014-Filter-out-the-pki-healthcheck-sources-if-IPA-CA-is-.patch deleted file mode 100644 index 3bc4afe..0000000 --- a/SOURCES/0014-Filter-out-the-pki-healthcheck-sources-if-IPA-CA-is-.patch +++ /dev/null @@ -1,82 +0,0 @@ -From eb377fed539e44194fb1ad822c0d4c6e9ea38d03 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 11 May 2021 13:26:00 -0400 -Subject: [PATCH] Filter out the pki healthcheck sources if IPA CA is not - installed - -The pki checks spew the error "Invalid PKI instance: pki-tomcat" so -we need to suppress them in the IPA CA-less installation case. - -So if the IPA CA is not configured then don't register the -pki sources. - -A side-effect is that to user the sources will not be listed at -all in this case. - -This should not affect pki-healthcheck and it will continue to -return errors in the unconfigured case. - -https://github.com/freeipa/freeipa-healthcheck/issues/201 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/core.py | 13 +++++++++++++ - src/ipahealthcheck/ipa/plugin.py | 4 ++++ - 2 files changed, 17 insertions(+) - -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index eaa2d9c..a6b4fe8 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -281,6 +281,13 @@ class RunChecks: - if rval is not None: - return rval - -+ # If we have IPA configured without a CA then we want to skip -+ # the pkihealthcheck plugins otherwise they will generated a -+ # lot of false positives. The IPA plugins are loaded first so -+ # which should set ca_configured in its registry to True or -+ # False. We will skip the pkihealthcheck plugins only if -+ # ca_configured is False which means that it was set by IPA. -+ ca_configured = False - for name, registry in find_registries(self.entry_points).items(): - try: - registry.initialize(framework, config, options) -@@ -292,6 +299,12 @@ class RunChecks: - except Exception as e: - logger.error("Unable to initialize %s: %s" % (name, e)) - continue -+ if hasattr(registry, 'ca_configured'): -+ ca_configured = registry.ca_configured -+ for name, registry in find_registries(self.entry_points).items(): -+ if 'pkihealthcheck' in name and ca_configured is False: -+ logger.debug('IPA CA is not configured, skipping %s', name) -+ continue - for plugin in find_plugins(name, registry): - plugins.append(plugin) - -diff --git a/src/ipahealthcheck/ipa/plugin.py b/src/ipahealthcheck/ipa/plugin.py -index 67d93e5..debb1bb 100644 ---- a/src/ipahealthcheck/ipa/plugin.py -+++ b/src/ipahealthcheck/ipa/plugin.py -@@ -35,6 +35,7 @@ class IPARegistry(Registry): - super(IPARegistry, self).__init__() - self.trust_agent = False - self.trust_controller = False -+ self.ca_configured = False - - def initialize(self, framework, config, options=None): - super(IPARegistry, self).initialize(framework, config) -@@ -58,6 +59,9 @@ class IPARegistry(Registry): - logging.debug('Failed to connect to LDAP: %s', e) - return - -+ ca = cainstance.CAInstance(api.env.realm, host_name=api.env.host) -+ self.ca_configured = ca.is_configured() -+ - # This package is pulled in when the trust package is installed - # and is required to lookup trust users. If this is not installed - # then it can be inferred that trust is not enabled. --- -2.31.1 - diff --git a/SOURCES/0015-Work-with-existing-resolve_rrsets-and-newer-resolve_.patch b/SOURCES/0015-Work-with-existing-resolve_rrsets-and-newer-resolve_.patch deleted file mode 100644 index 3bec151..0000000 --- a/SOURCES/0015-Work-with-existing-resolve_rrsets-and-newer-resolve_.patch +++ /dev/null @@ -1,117 +0,0 @@ -From d59a031264c5b30ce2686d2c2bd6d756b05ebcc8 Mon Sep 17 00:00:00 2001 -From: root -Date: Thu, 7 Oct 2021 18:02:30 -0400 -Subject: [PATCH] Work with existing resolve_rrsets and newer - resolve_rrsets_nss - -Up to freeipa 4.8.9 resolve_rrsets is used to look up the -ipa-ca values. After that, and in master, resovle_rrsets_nss -is used instead. Handle both in the DNS mock testing. ---- - tests/test_ipa_dns.py | 27 ++++++++++++++++++--------- - 1 file changed, 18 insertions(+), 9 deletions(-) - -diff --git a/tests/test_ipa_dns.py b/tests/test_ipa_dns.py -index b6a9610..91b15c2 100644 ---- a/tests/test_ipa_dns.py -+++ b/tests/test_ipa_dns.py -@@ -27,6 +27,15 @@ from ipaserver.dns_data_management import ( - IPA_DEFAULT_ADTRUST_SRV_REC - ) - -+try: -+ # pylint: disable=unused-import -+ from ipaserver.install.installutils import resolve_rrsets_nss # noqa: F401 -+ # pylint: enable=unused-import -+except ImportError: -+ resolve_rrsets_import = 'ipaserver.dns_data_management.resolve_rrsets' -+else: -+ resolve_rrsets_import = 'ipaserver.install.installutils.resolve_rrsets_nss' -+ - - def add_srv_records(qname, port_map, priority=0, weight=100): - rdlist = [] -@@ -182,7 +191,7 @@ class TestDNSSystemRecords(BaseTest): - 2. fake_query() overrides dns.resolver.query to simulate - A, AAAA and TXT record lookups. - """ -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_single(self, mock_query, mock_query_srv, mock_rrset): -@@ -217,7 +226,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.source == 'ipahealthcheck.ipa.idns' - assert result.check == 'IPADNSSystemRecordsCheck' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_two(self, mock_query, mock_query_srv, mock_rrset): -@@ -265,7 +274,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.source == 'ipahealthcheck.ipa.idns' - assert result.check == 'IPADNSSystemRecordsCheck' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_three(self, mock_query, mock_query_srv, mock_rrset): -@@ -323,7 +332,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.source == 'ipahealthcheck.ipa.idns' - assert result.check == 'IPADNSSystemRecordsCheck' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_three_mixed(self, mock_query, mock_query_srv, -@@ -379,7 +388,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.result == constants.SUCCESS - assert result.source == 'ipahealthcheck.ipa.idns' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_missing_server(self, mock_query, mock_query_srv, -@@ -445,7 +454,7 @@ class TestDNSSystemRecords(BaseTest): - for result in warn: - assert result.kw.get('msg') == 'Expected SRV record missing' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_missing_ipa_ca(self, mock_query, mock_query_srv, -@@ -516,7 +525,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.kw.get('count') == 2 - assert result.kw.get('expected') == 3 - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_extra_srv(self, mock_query, mock_query_srv, -@@ -586,7 +595,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.kw.get('msg') == \ - 'Unexpected SRV entry in DNS' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_bad_realm(self, mock_query, mock_query_srv, -@@ -626,7 +635,7 @@ class TestDNSSystemRecords(BaseTest): - assert result.kw.get('msg') == 'expected realm missing' - assert result.kw.get('key') == '\"FAKE_REALM\"' - -- @patch('ipaserver.dns_data_management.resolve_rrsets') -+ @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') - @patch('dns.resolver.query') - def test_dnsrecords_one_with_ad(self, mock_query, mock_query_srv, --- -2.27.0 - diff --git a/SOURCES/0016-tests-Generate-a-proper-not-valid-after-field.patch b/SOURCES/0016-tests-Generate-a-proper-not-valid-after-field.patch deleted file mode 100644 index 8d00073..0000000 --- a/SOURCES/0016-tests-Generate-a-proper-not-valid-after-field.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 90f0b7c16c68d1dd876fc88b56b58c04bc565230 Mon Sep 17 00:00:00 2001 -From: Stanislav Levin -Date: Fri, 6 Nov 2020 15:18:33 +0300 -Subject: [PATCH] tests: Generate a proper `not-valid-after` field - -Some tests assume that the mocked certificate will be valid in N -days from now(). There was a hardcoded `not-valid-after` value -which pointed to 20201205214850Z. So, from Nov 06 2020 the assertion -20201205214850Z - now() < cert_expiration_days(30days) fails. - -Fixes: https://github.com/freeipa/freeipa-healthcheck/issues/159 -Signed-off-by: Stanislav Levin ---- - tests/mock_certmonger.py | 18 ++++++++++++++++-- - tests/test_ipa_expiration.py | 8 ++++++-- - 3 files changed, 30 insertions(+), 8 deletions(-) - -diff --git a/tests/mock_certmonger.py b/tests/mock_certmonger.py -index ab53620..8fa4d36 100644 ---- a/tests/mock_certmonger.py -+++ b/tests/mock_certmonger.py -@@ -3,6 +3,7 @@ - # - - import copy -+from datetime import datetime, timedelta, timezone - - from ipaplatform.paths import paths - -@@ -10,6 +11,8 @@ from ipaplatform.paths import paths - # distinct from the value from the overrident get_defaults() method. - template = paths.CERTMONGER_COMMAND_TEMPLATE - -+CERT_EXPIRATION_DAYS = 30 -+ - pristine_cm_requests = [ - { - 'nickname': '1234', -@@ -20,7 +23,11 @@ pristine_cm_requests = [ - 'cert-storage': 'FILE', - 'cert-presave-command': template % 'renew_ra_cert_pre', - 'cert-postsave-command': template % 'renew_ra_cert', -- 'not-valid-after': 1024, -+ 'not-valid-after': ( -+ int( -+ datetime(1970, 1, 1, 0, 17, 4, tzinfo=timezone.utc).timestamp() -+ ) -+ ), - }, - { - 'nickname': '5678', -@@ -30,7 +37,14 @@ pristine_cm_requests = [ - 'template_profile': 'caIPAserviceCert', - 'cert-storage': 'FILE', - 'cert-postsave-command': template % 'restart_httpd', -- 'not-valid-after': 1607204930, -+ 'not-valid-after': ( -+ int( -+ ( -+ datetime.now(timezone.utc) + -+ timedelta(days=CERT_EXPIRATION_DAYS + 1) -+ ).timestamp() -+ ) -+ ), - }, - ] - -diff --git a/tests/test_ipa_expiration.py b/tests/test_ipa_expiration.py -index ff3564b..fb7105b 100644 ---- a/tests/test_ipa_expiration.py -+++ b/tests/test_ipa_expiration.py -@@ -11,7 +11,11 @@ from ipahealthcheck.ipa.certs import IPACertmongerExpirationCheck - from ipahealthcheck.ipa.certs import IPACAChainExpirationCheck - from unittest.mock import Mock, patch - from mock_certmonger import create_mock_dbus, _certmonger --from mock_certmonger import get_expected_requests, set_requests -+from mock_certmonger import ( -+ get_expected_requests, -+ set_requests, -+ CERT_EXPIRATION_DAYS, -+) - - from datetime import datetime, timedelta, timezone - -@@ -67,7 +71,7 @@ class TestExpiration(BaseTest): - registry.initialize(framework, config.Config) - f = IPACertmongerExpirationCheck(registry) - -- f.config.cert_expiration_days = '30' -+ f.config.cert_expiration_days = str(CERT_EXPIRATION_DAYS) - self.results = capture_results(f) - - assert len(self.results) == 2 --- -2.31.1 - diff --git a/SOURCES/0017-Fix-the-number-of-expected-results-in-the-fix-file-t.patch b/SOURCES/0017-Fix-the-number-of-expected-results-in-the-fix-file-t.patch deleted file mode 100644 index 3ada438..0000000 --- a/SOURCES/0017-Fix-the-number-of-expected-results-in-the-fix-file-t.patch +++ /dev/null @@ -1,25 +0,0 @@ -From bfcf6c0ebe7522cdc7e0c4e3aee695752ea3f489 Mon Sep 17 00:00:00 2001 -From: root -Date: Thu, 7 Oct 2021 18:14:03 -0400 -Subject: [PATCH] Fix the number of expected results in the fix file test - ---- - tests/test_core_files.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tests/test_core_files.py b/tests/test_core_files.py -index a4f25ac..8257f40 100644 ---- a/tests/test_core_files.py -+++ b/tests/test_core_files.py -@@ -118,7 +118,7 @@ def test_files_not_found(mock_exists): - - for type in ('mode', 'group', 'owner'): - my_results = get_results(results, type) -- assert len(my_results.results) == 4 -+ assert len(my_results.results) == 2 - for result in my_results.results: - assert result.result == constants.SUCCESS - assert result.kw.get('msg') == 'File does not exist' --- -2.27.0 - diff --git a/SOURCES/0018-Don-t-collect-the-CRLManager-role-if-the-CA-is-not-c.patch b/SOURCES/0018-Don-t-collect-the-CRLManager-role-if-the-CA-is-not-c.patch deleted file mode 100644 index 8947d73..0000000 --- a/SOURCES/0018-Don-t-collect-the-CRLManager-role-if-the-CA-is-not-c.patch +++ /dev/null @@ -1,54 +0,0 @@ -From a63d5ac05157e689e99494661240d43d131c0e91 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 11 May 2021 13:19:41 -0400 -Subject: [PATCH] Don't collect the CRLManager role if the CA is not configured - -This was raising a false positive in the IPA CA-less case. - -https://github.com/freeipa/freeipa-healthcheck/issues/201 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/ipa/roles.py | 2 ++ - tests/test_ipa_roles.py | 12 ++++++++++++ - 2 files changed, 14 insertions(+) - -diff --git a/src/ipahealthcheck/ipa/roles.py b/src/ipahealthcheck/ipa/roles.py -index 0ff2269..aac7b80 100644 ---- a/src/ipahealthcheck/ipa/roles.py -+++ b/src/ipahealthcheck/ipa/roles.py -@@ -25,6 +25,8 @@ class IPACRLManagerCheck(IPAPlugin): - """ - @duration - def check(self): -+ if not self.ca.is_configured(): -+ return - try: - enabled = self.ca.is_crlgen_enabled() - except AttributeError: -diff --git a/tests/test_ipa_roles.py b/tests/test_ipa_roles.py -index 21c0069..7c4a2d1 100644 ---- a/tests/test_ipa_roles.py -+++ b/tests/test_ipa_roles.py -@@ -48,6 +48,18 @@ class TestCRLManagerRole(BaseTest): - assert result.check == 'IPACRLManagerCheck' - assert result.kw.get('crlgen_enabled') is True - -+ @patch('ipaserver.install.cainstance.CAInstance') -+ def test_crlmanager_no_ca(self, mock_ca): -+ """There should be no CRLManagerCheck without a CA""" -+ mock_ca.return_value = CAInstance(False) -+ framework = object() -+ registry.initialize(framework, config.Config) -+ f = IPACRLManagerCheck(registry) -+ -+ self.results = capture_results(f) -+ -+ assert len(self.results) == 0 -+ - - class TestRenewalMaster(BaseTest): - def test_renewal_master_not_set(self): --- -2.31.1 - diff --git a/SOURCES/0019-Don-t-depend-on-IPA-status-when-suppressing-pki-chec.patch b/SOURCES/0019-Don-t-depend-on-IPA-status-when-suppressing-pki-chec.patch deleted file mode 100644 index df29ffb..0000000 --- a/SOURCES/0019-Don-t-depend-on-IPA-status-when-suppressing-pki-chec.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 62c14dbff5a947b50194df197de9f7052597ffb4 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu, 17 Feb 2022 08:56:38 -0500 -Subject: [PATCH] Don't depend on IPA status when suppressing pki checks - -The pki healthchecks are noisy if a CA is not configured. We -want to suppresse these in IPA so don't make the checks visible -if a CA is not configured. - -So this means we need to be able to run in these conditions: - -1. IPA is configured with a CA: the pki checks are run -2. IPA is configured without a CA: the pki checks are not run -3. IPA is not configured: the pki checks are run - -Which basically equates to three states: True, False, None - -This was done originally with the ca_configured variable set to -None. Using some inside knowledge the registries are loaded which -will set ca_configured to True or False in the IPA registry. -Using that we can determine if the pki checks should be available. -Unfortunately I changed the initialization to False so it always -assumes that IPA is installed. ca_configured will be False for the -case of IPA not installed instead of None so we can't handle that -last state. - -So initialize ca_configured to None so we can satisfy all three -states. - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/core.py | 22 ++++++++++++++++------ - 1 file changed, 16 insertions(+), 6 deletions(-) - -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index a6b4fe8..19f7818 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -281,13 +281,23 @@ class RunChecks: - if rval is not None: - return rval - -+ # The pki checks are noisy if a CA is not configured so we -+ # want to suppress that for IPA. -+ # -+ # There are 3 possible states: -+ # 1. IPA is configured with a CA -+ # 2. IPA is configured without a CA -+ # 3. IPA is not configured -+ # - # If we have IPA configured without a CA then we want to skip -- # the pkihealthcheck plugins otherwise they will generated a -- # lot of false positives. The IPA plugins are loaded first so -- # which should set ca_configured in its registry to True or -- # False. We will skip the pkihealthcheck plugins only if -- # ca_configured is False which means that it was set by IPA. -- ca_configured = False -+ # the pkihealthcheck plugins -+ # -+ # The IPA registry will set ca_configured in its registry to True -+ # or False. We will skip the pkihealthcheck plugins only if -+ # ca_configured is False which means that it was set by IPA. So -+ # we initialize ca_configured to None so that the pki checks -+ # will always be executed with pki-healthcheck. -+ ca_configured = None - for name, registry in find_registries(self.entry_points).items(): - try: - registry.initialize(framework, config, options) --- -2.31.1 - diff --git a/SOURCES/0020-Add-support-for-the-DNS-URI-type.patch b/SOURCES/0020-Add-support-for-the-DNS-URI-type.patch deleted file mode 100644 index b5ed6e7..0000000 --- a/SOURCES/0020-Add-support-for-the-DNS-URI-type.patch +++ /dev/null @@ -1,573 +0,0 @@ -From 3ae8b103974d7e6aca4e2c5f093e63b67f25a6dd Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Fri, 18 Mar 2022 16:53:20 -0400 -Subject: [PATCH] Add support for the DNS URI type - -URI records are not required but if they exist they are -validated. - -https://github.com/freeipa/freeipa-healthcheck/issues/222 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/ipa/idns.py | 60 ++++++++- - tests/test_ipa_dns.py | 228 +++++++++++++++++++++++++++------ - 2 files changed, 242 insertions(+), 46 deletions(-) - -diff --git a/src/ipahealthcheck/ipa/idns.py b/src/ipahealthcheck/ipa/idns.py -index 4aa7008..64d5ddd 100644 ---- a/src/ipahealthcheck/ipa/idns.py -+++ b/src/ipahealthcheck/ipa/idns.py -@@ -11,12 +11,21 @@ from ipahealthcheck.core.plugin import Result, duration - from ipahealthcheck.core import constants - - from ipalib import api --from dns import resolver -+from dns.resolver import query as resolve - - - logger = logging.getLogger() - - -+def query_uri(uri): -+ try: -+ answers = resolve(uri, rdatatype.URI) -+ except DNSException as e: -+ logger.debug("DNS record not found: %s", e.__class__.__name__) -+ answers = [] -+ return answers -+ -+ - @registry - class IPADNSSystemRecordsCheck(IPAPlugin): - """ -@@ -32,6 +41,10 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - """Combine the SRV record and target into a unique name.""" - return srv + ":" + target - -+ def uri_to_name(self, uri, target): -+ """Combine the SRV record and target into a unique name.""" -+ return uri + ":" + target -+ - @duration - def check(self): - from ipapython.dnsutil import query_srv -@@ -43,6 +56,7 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - # collect the list of expected values - txt_rec = dict() - srv_rec = dict() -+ uri_rec = dict() - a_rec = list() - aaaa_rec = list() - -@@ -63,6 +77,15 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - a_rec.append(rd.to_text()) - elif rd.rdtype == rdatatype.AAAA: - aaaa_rec.append(rd.to_text()) -+ elif rd.rdtype == rdatatype.URI: -+ if name.ToASCII() in uri_rec: -+ uri_rec[name.ToASCII()].append( -+ rd.target.decode('utf-8') -+ ) -+ else: -+ uri_rec[name.ToASCII()] = [ -+ rd.target.decode('utf-8') -+ ] - else: - logger.error("Unhandler rdtype %d", rd.rdtype) - -@@ -95,10 +118,39 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - msg='Expected SRV record missing', - key=self.srv_to_name(srv, host)) - -+ for uri in uri_rec: -+ logger.debug("Search DNS for URI record of %s", uri) -+ answers = query_uri(uri) -+ hosts = uri_rec[uri] -+ for answer in answers: -+ logger.debug("DNS record found: %s", answer) -+ try: -+ hosts.remove(answer.target.decode('utf-8')) -+ yield Result( -+ self, constants.SUCCESS, -+ key=self.uri_to_name( -+ uri, answer.target.decode('utf-8') -+ ) -+ ) -+ except ValueError: -+ yield Result( -+ self, constants.WARNING, -+ msg='Unexpected URI entry in DNS', -+ key=self.uri_to_name( -+ uri, answer.target.decode('utf-8') -+ ) -+ ) -+ for host in hosts: -+ yield Result( -+ self, constants.WARNING, -+ msg='Expected URI record missing', -+ key=self.uri_to_name(uri, host) -+ ) -+ - for txt in txt_rec: - logger.debug("Search DNS for TXT record of %s", txt) - try: -- answers = resolver.query(txt, rdatatype.TXT) -+ answers = resolve(txt, rdatatype.TXT) - except DNSException as e: - logger.debug("DNS record not found: %s", e.__class__.__name__) - answers = [] -@@ -121,7 +173,7 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - qname = "ipa-ca." + api.env.domain + "." - logger.debug("Search DNS for A record of %s", qname) - try: -- answers = resolver.query(qname, rdatatype.A) -+ answers = resolve(qname, rdatatype.A) - except DNSException as e: - logger.debug("DNS record not found: %s", e.__class__.__name__) - answers = [] -@@ -155,7 +207,7 @@ class IPADNSSystemRecordsCheck(IPAPlugin): - qname = "ipa-ca." + api.env.domain + "." - logger.debug("Search DNS for AAAA record of %s", qname) - try: -- answers = resolver.query(qname, rdatatype.AAAA) -+ answers = resolve(qname, rdatatype.AAAA) - except DNSException as e: - logger.debug("DNS record not found: %s", e.__class__.__name__) - answers = [] -diff --git a/tests/test_ipa_dns.py b/tests/test_ipa_dns.py -index 91b15c2..28243b6 100644 ---- a/tests/test_ipa_dns.py -+++ b/tests/test_ipa_dns.py -@@ -27,6 +27,15 @@ from ipaserver.dns_data_management import ( - IPA_DEFAULT_ADTRUST_SRV_REC - ) - -+try: -+ # pylint: disable=unused-import -+ from ipaserver.dns_data_management import IPA_DEFAULT_MASTER_URI_REC # noqa -+except ImportError: -+ has_uri_support = False -+else: -+ has_uri_support = True -+ -+ - try: - # pylint: disable=unused-import - from ipaserver.install.installutils import resolve_rrsets_nss # noqa: F401 -@@ -79,6 +88,45 @@ def query_srv(qname, ad_records=False): - return rdlist - - -+def query_uri(hosts): -+ """ -+ Return a list containing two answers, one for each uri type -+ """ -+ answers = [] -+ if version.MAJOR < 2 or (version.MAJOR == 2 and version.MINOR == 0): -+ m = message.Message() -+ elif version.MAJOR == 2 and version.MINOR > 0: -+ m = message.QueryMessage() # pylint: disable=E1101 -+ m = message.make_response(m) # pylint: disable=E1101 -+ -+ rdtype = rdatatype.URI -+ for name in ('_kerberos.', '_kpasswd.'): -+ qname = DNSName(name + m_api.env.domain) -+ qname = qname.make_absolute() -+ if version.MAJOR < 2: -+ # pylint: disable=unexpected-keyword-arg -+ answer = Answer(qname, rdataclass.IN, rdtype, m, -+ raise_on_no_answer=False) -+ # pylint: enable=unexpected-keyword-arg -+ else: -+ if version.MAJOR == 2 and version.MINOR > 0: -+ question = rrset.RRset(qname, rdataclass.IN, rdtype) -+ m.question = [question] -+ answer = Answer(qname, rdataclass.IN, rdtype, m) -+ -+ rl = [] -+ for host in hosts: -+ rlist = rrset.from_text_list( -+ qname, 86400, rdataclass.IN, -+ rdatatype.URI, ['0 100 "krb5srv:m:tcp:%s."' % host, -+ '0 100 "krb5srv:m:udp:%s."' % host, ] -+ ) -+ rl.extend(rlist) -+ answer.rrset = rl -+ answers.append(answer) -+ return answers -+ -+ - def gen_addrs(rdtype=rdatatype.A, num=1): - """Generate sequential IP addresses for the ipa-ca A record lookup""" - ips = [] -@@ -188,16 +236,19 @@ class TestDNSSystemRecords(BaseTest): - - 1. The query_srv() override returns the set of configured - servers for each type of SRV record. -- 2. fake_query() overrides dns.resolver.query to simulate -+ 2. fake_query() overrides ipahealthcheck.ipa.idns.resolve to simulate - A, AAAA and TXT record lookups. - """ - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_single(self, mock_query, mock_query_srv, mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_single(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Test single CA master, all SRV records""" - mock_query.side_effect = fake_query_one - mock_query_srv.side_effect = query_srv([m_api.env.host]) -+ mock_query_uri.side_effect = query_uri([m_api.env.host]) - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)) - ] -@@ -219,7 +270,11 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 10 -+ if has_uri_support: -+ expected = 14 -+ else: -+ expected = 10 -+ assert len(self.results) == expected - - for result in self.results.results: - assert result.result == constants.SUCCESS -@@ -228,13 +283,19 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_two(self, mock_query, mock_query_srv, mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_two(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Test two CA masters, all SRV records""" - mock_query_srv.side_effect = query_srv([ - m_api.env.host, - 'replica.' + m_api.env.domain - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain -+ ]) - mock_query.side_effect = fake_query_two - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -267,7 +328,11 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 19 -+ if has_uri_support: -+ expected = 27 -+ else: -+ expected = 19 -+ assert len(self.results) == expected - - for result in self.results.results: - assert result.result == constants.SUCCESS -@@ -276,14 +341,21 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_three(self, mock_query, mock_query_srv, mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_three(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Test three CA masters, all SRV records""" - mock_query_srv.side_effect = query_srv([ - m_api.env.host, - 'replica.' + m_api.env.domain, - 'replica2.' + m_api.env.domain - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain, -+ 'replica2.' + m_api.env.domain -+ ]) - mock_query.side_effect = fake_query_three - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -325,7 +397,11 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 28 -+ if has_uri_support: -+ expected = 40 -+ else: -+ expected = 28 -+ assert len(self.results) == expected - - for result in self.results.results: - assert result.result == constants.SUCCESS -@@ -334,15 +410,21 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_three_mixed(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_three_mixed(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Test three masters, only one with a CA, all SRV records""" - mock_query_srv.side_effect = query_srv([ - m_api.env.host, - 'replica.' + m_api.env.domain, - 'replica2.' + m_api.env.domain - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain, -+ 'replica2.' + m_api.env.domain -+ ]) - mock_query.side_effect = fake_query_one - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -382,7 +464,11 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 24 -+ if has_uri_support: -+ expected = 36 -+ else: -+ expected = 24 -+ assert len(self.results) == expected - - for result in self.results.results: - assert result.result == constants.SUCCESS -@@ -390,9 +476,10 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_missing_server(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_missing_server(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Drop one of the masters from query_srv - - This will simulate missing SRV records and cause a number of -@@ -403,6 +490,11 @@ class TestDNSSystemRecords(BaseTest): - 'replica.' + m_api.env.domain - # replica2 is missing - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain, -+ 'replica2.' + m_api.env.domain -+ ]) - mock_query.side_effect = fake_query_three - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -444,21 +536,30 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 28 -+ if has_uri_support: -+ expected = 40 -+ else: -+ expected = 28 -+ assert len(self.results) == expected - - ok = get_results_by_severity(self.results.results, constants.SUCCESS) - warn = get_results_by_severity(self.results.results, constants.WARNING) -- assert len(ok) == 21 -- assert len(warn) == 7 -+ if has_uri_support: -+ assert len(ok) == 33 -+ assert len(warn) == 7 -+ else: -+ assert len(ok) == 21 -+ assert len(warn) == 7 - - for result in warn: - assert result.kw.get('msg') == 'Expected SRV record missing' - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_missing_ipa_ca(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_missing_ipa_ca(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Drop one of the masters from query_srv - - This will simulate missing SRV records and cause a number of -@@ -469,6 +570,11 @@ class TestDNSSystemRecords(BaseTest): - 'replica.' + m_api.env.domain, - 'replica2.' + m_api.env.domain - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain, -+ 'replica2.' + m_api.env.domain -+ ]) - mock_query.side_effect = fake_query_two - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -510,12 +616,20 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 28 -+ if has_uri_support: -+ expected = 40 -+ else: -+ expected = 28 -+ assert len(self.results) == expected - - ok = get_results_by_severity(self.results.results, constants.SUCCESS) - warn = get_results_by_severity(self.results.results, constants.WARNING) -- assert len(ok) == 26 -- assert len(warn) == 2 -+ if has_uri_support: -+ assert len(ok) == 38 -+ assert len(warn) == 2 -+ else: -+ assert len(ok) == 26 -+ assert len(warn) == 2 - - for result in warn: - assert re.match( -@@ -527,9 +641,10 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_extra_srv(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_extra_srv(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """An extra SRV record set exists, report it. - - Add an extra master to the query_srv() which will generate -@@ -541,6 +656,11 @@ class TestDNSSystemRecords(BaseTest): - 'replica2.' + m_api.env.domain, - 'replica3.' + m_api.env.domain - ]) -+ mock_query_uri.side_effect = query_uri([ -+ m_api.env.host, -+ 'replica.' + m_api.env.domain, -+ 'replica2.' + m_api.env.domain, -+ ]) - mock_query.side_effect = fake_query_three - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)), -@@ -584,12 +704,20 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 35 -+ if has_uri_support: -+ expected = 47 -+ else: -+ expected = 35 -+ assert len(self.results) == expected - - ok = get_results_by_severity(self.results.results, constants.SUCCESS) - warn = get_results_by_severity(self.results.results, constants.WARNING) -- assert len(ok) == 28 -- assert len(warn) == 7 -+ if has_uri_support: -+ assert len(ok) == 40 -+ assert len(warn) == 7 -+ else: -+ assert len(ok) == 28 -+ assert len(warn) == 7 - - for result in warn: - assert result.kw.get('msg') == \ -@@ -597,12 +725,14 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_bad_realm(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_bad_realm(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - """Unexpected Kerberos TXT record""" - mock_query.side_effect = fake_query_one_txt - mock_query_srv.side_effect = query_srv([m_api.env.host]) -+ mock_query_uri.side_effect = query_uri([m_api.env.host]) - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)) - ] -@@ -624,12 +754,20 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 10 -+ if has_uri_support: -+ expected = 14 -+ else: -+ expected = 10 -+ assert len(self.results) == expected - - ok = get_results_by_severity(self.results.results, constants.SUCCESS) - warn = get_results_by_severity(self.results.results, constants.WARNING) -- assert len(ok) == 9 -- assert len(warn) == 1 -+ if has_uri_support: -+ assert len(ok) == 13 -+ assert len(warn) == 1 -+ else: -+ assert len(ok) == 9 -+ assert len(warn) == 1 - - result = warn[0] - assert result.kw.get('msg') == 'expected realm missing' -@@ -637,11 +775,13 @@ class TestDNSSystemRecords(BaseTest): - - @patch(resolve_rrsets_import) - @patch('ipapython.dnsutil.query_srv') -- @patch('dns.resolver.query') -- def test_dnsrecords_one_with_ad(self, mock_query, mock_query_srv, -- mock_rrset): -+ @patch('ipahealthcheck.ipa.idns.query_uri') -+ @patch('ipahealthcheck.ipa.idns.resolve') -+ def test_dnsrecords_one_with_ad(self, mock_query, mock_query_uri, -+ mock_query_srv, mock_rrset): - mock_query.side_effect = fake_query_one - mock_query_srv.side_effect = query_srv([m_api.env.host], True) -+ mock_query_uri.side_effect = query_uri([m_api.env.host]) - mock_rrset.side_effect = [ - resolve_rrsets(m_api.env.host, (rdatatype.A, rdatatype.AAAA)) - ] -@@ -664,7 +804,11 @@ class TestDNSSystemRecords(BaseTest): - - self.results = capture_results(f) - -- assert len(self.results) == 16 -+ if has_uri_support: -+ expected = 20 -+ else: -+ expected = 16 -+ assert len(self.results) == expected - - for result in self.results.results: - assert result.result == constants.SUCCESS --- -2.31.1 - diff --git a/SOURCES/0021-Use-the-subject-base-from-the-IPA-configuration-not-.patch b/SOURCES/0021-Use-the-subject-base-from-the-IPA-configuration-not-.patch deleted file mode 100644 index c53c901..0000000 --- a/SOURCES/0021-Use-the-subject-base-from-the-IPA-configuration-not-.patch +++ /dev/null @@ -1,372 +0,0 @@ -From ed5322daad5dc456e4958228835b33a32c7d1608 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 29 Mar 2022 12:58:01 -0400 -Subject: [PATCH] Use the subject base from the IPA configuration, not REALM - -The expected certificates were hardcoded with O={REALM} which -would return false-positives if the customer defined their -own certificate subject base. - -Also add a search filter to only retrieve the certificate(s) we -want to examine rather than the entire contents. - -Fixes: https://github.com/freeipa/freeipa-healthcheck/issues/253 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/ipa/certs.py | 24 ++-- - tests/test_ipa_cert_match.py | 202 ++++++++++++++++++++++++-------- - 2 files changed, 166 insertions(+), 60 deletions(-) - -diff --git a/src/ipahealthcheck/ipa/certs.py b/src/ipahealthcheck/ipa/certs.py -index 2ef33f9..fd5e180 100644 ---- a/src/ipahealthcheck/ipa/certs.py -+++ b/src/ipahealthcheck/ipa/certs.py -@@ -707,12 +707,14 @@ class IPADogtagCertsMatchCheck(IPAPlugin): - - 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(): -+ entries = ldap.get_entries( -+ dn, -+ filter=f'subjectname={subject}' -+ ) - cert = db.get_cert_from_db(nick) -- ok = any([cert in entry['userCertificate'] and -- subject == entry['subjectName'][0] -+ ok = any([cert in entry['userCertificate'] - for entry in entries - if 'userCertificate' in entry]) - if not ok: -@@ -740,26 +742,28 @@ class IPADogtagCertsMatchCheck(IPAPlugin): - db, dn, 'CACertificate', - casigning_nick) - -+ config = api.Command.config_show() -+ subject_base = config['result']['ipacertificatesubjectbase'][0] - expected_nicks_subjects = { - 'ocspSigningCert cert-pki-ca': -- 'CN=OCSP Subsystem,O=%s' % api.env.realm, -+ f'CN=OCSP Subsystem,{subject_base}', - 'subsystemCert cert-pki-ca': -- 'CN=CA Subsystem,O=%s' % api.env.realm, -+ f'CN=CA Subsystem,{subject_base}', - 'auditSigningCert cert-pki-ca': -- 'CN=CA Audit,O=%s' % api.env.realm, -+ f'CN=CA Audit,{subject_base}', - 'Server-Cert cert-pki-ca': -- 'CN=%s,O=%s' % (api.env.host, api.env.realm), -+ f'CN={api.env.host},{subject_base}', - } - - 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, -+ f'CN=KRA Transport Certificate,{subject_base}', - 'storageCert cert-pki-kra': -- 'CN=KRA Storage Certificate,O=%s' % api.env.realm, -+ f'CN=KRA Storage Certificate,{subject_base}', - 'auditSigningCert cert-pki-kra': -- 'CN=KRA Audit,O=%s' % api.env.realm, -+ f'CN=KRA Audit,{subject_base}', - } - expected_nicks_subjects.update(kra_expected_nicks_subjects) - -diff --git a/tests/test_ipa_cert_match.py b/tests/test_ipa_cert_match.py -index 460e61a..70ef59e 100644 ---- a/tests/test_ipa_cert_match.py -+++ b/tests/test_ipa_cert_match.py -@@ -44,8 +44,15 @@ class mock_ldap: - 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() -+ raise errors.NotFound(reason='None') -+ if filter: -+ (attr, value) = filter.split('=', maxsplit=1) -+ for result in self.results.values(): -+ if result.get(attr)[0] == value: -+ return [result] -+ raise errors.NotFound(reason='Not found %s' % filter) -+ -+ return self.results - - - class mock_ldap_conn: -@@ -82,6 +89,10 @@ class TestIPACertMatch(BaseTest): - Mock(return_value=mock_ldap_conn()) - } - -+ trust = { -+ ('%s IPA CA' % m_api.env.realm): 'u,u,u' -+ } -+ - @patch('ipalib.x509.load_certificate_list_from_file') - @patch('ipaserver.install.certs.CertDB') - def test_certs_match_ok(self, mock_certdb, mock_load_cert): -@@ -92,11 +103,8 @@ class TestIPACertMatch(BaseTest): - '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_certdb.return_value = mock_CertDB(self.trust) - mock_load_cert.return_value = [IPACertificate()] - - framework = object() -@@ -121,11 +129,8 @@ class TestIPACertMatch(BaseTest): - '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_certdb.return_value = mock_CertDB(self.trust) - mock_load_cert.return_value = [IPACertificate(serial_number=2)] - - framework = object() -@@ -155,15 +160,54 @@ class TestIPACertMatch(BaseTest): - assert len(self.results) == 0 - - -+default_subject_base = [{ -+ 'result': -+ { -+ 'ipacertificatesubjectbase': [f'O={m_api.env.realm}'], -+ }, -+}] -+ -+custom_subject_base = [{ -+ 'result': -+ { -+ 'ipacertificatesubjectbase': ['OU=Eng,O=ACME'], -+ }, -+}] -+ -+ - class TestIPADogtagCertMatch(BaseTest): - patches = { - 'ipaserver.install.krainstance.KRAInstance': - Mock(return_value=KRAInstance()), - } -+ 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', -+ } -+ -+ def get_dogtag_subjects(self, hostname, base): -+ subject_base = base[0]['result']['ipacertificatesubjectbase'][0] -+ return ( -+ f'CN=OCSP Subsystem,{subject_base}', -+ f'CN=CA Subsystem,{subject_base}', -+ f'CN=CA Audit,{subject_base}', -+ f'CN=%s,{subject_base}', -+ f'CN=KRA Transport Certificate,{subject_base}', -+ f'CN=KRA Storage Certificate,{subject_base}', -+ f'CN=KRA Audit,{subject_base}', -+ f'CN={hostname},{subject_base}', -+ ) - - @patch('ipaserver.install.certs.CertDB') - def test_certs_match_ok(self, mock_certdb): - """ Ensure match check is ok""" -+ m_api.Command.config_show.side_effect = default_subject_base - fake_conn = LDAPClient('ldap://localhost', no_schema=True) - pkidbentry = LDAPEntry(fake_conn, - DN('uid=pkidbuser,ou=people,o=ipaca'), -@@ -177,25 +221,9 @@ class TestIPADogtagCertMatch(BaseTest): - 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, -+ -+ dogtag_entries_subjects = self.get_dogtag_subjects( -+ m_api.env.host, default_subject_base - ) - - for i, subject in enumerate(dogtag_entries_subjects): -@@ -206,7 +234,7 @@ class TestIPADogtagCertMatch(BaseTest): - subjectName=[subject]) - ldap_entries.append(entry) - -- mock_certdb.return_value = mock_CertDB(trust) -+ mock_certdb.return_value = mock_CertDB(self.trust) - - framework = object() - registry.initialize(framework, config.Config()) -@@ -223,6 +251,7 @@ class TestIPADogtagCertMatch(BaseTest): - @patch('ipaserver.install.certs.CertDB') - def test_certs_mismatch(self, mock_certdb): - """ Ensure mismatches are detected""" -+ m_api.Command.config_show.side_effect = default_subject_base - fake_conn = LDAPClient('ldap://localhost', no_schema=True) - pkidbentry = LDAPEntry(fake_conn, - DN('uid=pkidbuser,ou=people,o=ipaca'), -@@ -238,25 +267,9 @@ class TestIPADogtagCertMatch(BaseTest): - 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, -+ -+ dogtag_entries_subjects = self.get_dogtag_subjects( -+ m_api.env.host, default_subject_base - ) - - for i, subject in enumerate(dogtag_entries_subjects): -@@ -267,7 +280,7 @@ class TestIPADogtagCertMatch(BaseTest): - subjectName=[subject]) - ldap_entries.append(entry) - -- mock_certdb.return_value = mock_CertDB(trust) -+ mock_certdb.return_value = mock_CertDB(self.trust) - - framework = object() - registry.initialize(framework, config.Config()) -@@ -280,3 +293,92 @@ class TestIPADogtagCertMatch(BaseTest): - assert result.result == constants.ERROR - assert result.source == 'ipahealthcheck.ipa.certs' - assert result.check == 'IPADogtagCertsMatchCheck' -+ -+ @patch('ipaserver.install.certs.CertDB') -+ def test_certs_match_ok_subject(self, mock_certdb): -+ """ Ensure match check is ok""" -+ m_api.Command.config_show.side_effect = custom_subject_base -+ 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] -+ -+ dogtag_entries_subjects = self.get_dogtag_subjects( -+ m_api.env.host, custom_subject_base -+ ) -+ -+ 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(self.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_subject(self, mock_certdb): -+ """ Ensure mismatches are detected""" -+ m_api.Command.config_show.side_effect = custom_subject_base -+ 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] -+ -+ dogtag_entries_subjects = self.get_dogtag_subjects( -+ m_api.env.host, custom_subject_base -+ ) -+ -+ 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(self.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' --- -2.31.1 - diff --git a/SOURCES/0022-Allow-multiple-file-modes-in-the-FileChecker.patch b/SOURCES/0022-Allow-multiple-file-modes-in-the-FileChecker.patch deleted file mode 100644 index a300f3e..0000000 --- a/SOURCES/0022-Allow-multiple-file-modes-in-the-FileChecker.patch +++ /dev/null @@ -1,178 +0,0 @@ -From 96a780d75274031fd97167c1109fcb63efa7d5cd Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 5 Apr 2022 09:25:45 -0400 -Subject: [PATCH] Allow multiple file modes in the FileChecker - -There are some cases where a strict file mode is not -necessary. The kadmind.log is one. - -It is owned root:root so there is no real difference -between 0600 and 0640. So allow both. - -https://bugzilla.redhat.com/show_bug.cgi?id=2058239 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/files.py | 31 +++++++++++++++++++++++-------- - src/ipahealthcheck/ipa/files.py | 3 ++- - tests/test_core_files.py | 32 ++++++++++++++++++++++++++++---- - 3 files changed, 53 insertions(+), 13 deletions(-) - -diff --git a/src/ipahealthcheck/core/files.py b/src/ipahealthcheck/core/files.py -index 1b208d1..af03243 100644 ---- a/src/ipahealthcheck/core/files.py -+++ b/src/ipahealthcheck/core/files.py -@@ -16,13 +16,15 @@ class FileCheck: - files is a tuple of tuples. Each tuple consists of: - (path, expected_perm, expected_owner, expected_group) - -- perm is in the form of a POSIX ACL: e.g. 0440, 0770. -+ perm is a POSIX ACL as either a string or tuple: e.g. 0440, (0770,). - Owner and group are names, not uid/gid. - """ - - @duration - def check(self): - for (path, owner, group, mode) in self.files: -+ if not isinstance(mode, tuple): -+ mode = tuple((mode,)) - if not os.path.exists(path): - for type in ('mode', 'owner', 'group'): - key = '%s_%s' % (path.replace('/', '_'), type) -@@ -33,19 +35,32 @@ class FileCheck: - stat = os.stat(path) - fmode = str(oct(stat.st_mode)[-4:]) - key = '%s_mode' % path.replace('/', '_') -- if mode != fmode: -- if mode < fmode: -+ if fmode not in mode: -+ if len(mode) == 1: -+ modes = mode[0] -+ else: -+ modes = 'one of {}'.format(','.join(mode)) -+ if all(m < fmode for m in mode): - yield Result(self, constants.WARNING, key=key, -- path=path, type='mode', expected=mode, -+ path=path, type='mode', expected=modes, - got=fmode, - msg='Permissions of %s are too permissive: ' -- '%s and should be %s' % (path, fmode, mode)) -- if mode > fmode: -+ '%s and should be %s' % -+ (path, fmode, modes)) -+ elif all(m > fmode for m in mode): - yield Result(self, constants.ERROR, key=key, -- path=path, type='mode', expected=mode, -+ path=path, type='mode', expected=modes, - got=fmode, - msg='Permissions of %s are too restrictive: ' -- '%s and should be %s' % (path, fmode, mode)) -+ '%s and should be %s' % -+ (path, fmode, modes)) -+ else: -+ yield Result(self, constants.ERROR, key=key, -+ path=path, type='mode', expected=modes, -+ got=fmode, -+ msg='Permissions of %s are unexpected: ' -+ '%s and should be %s' % -+ (path, fmode, modes)) - else: - yield Result(self, constants.SUCCESS, key=key, - type='mode', path=path) -diff --git a/src/ipahealthcheck/ipa/files.py b/src/ipahealthcheck/ipa/files.py -index abfa52f..a372992 100644 ---- a/src/ipahealthcheck/ipa/files.py -+++ b/src/ipahealthcheck/ipa/files.py -@@ -118,7 +118,8 @@ class IPAFileCheck(IPAPlugin, FileCheck): - self.files.append((paths.IPA_CUSTODIA_AUDIT_LOG, - 'root', 'root', '0644')) - -- self.files.append((paths.KADMIND_LOG, 'root', 'root', '0600')) -+ self.files.append((paths.KADMIND_LOG, 'root', 'root', -+ ('0600', '0640'))) - self.files.append((paths.KRB5KDC_LOG, 'root', 'root', '0640')) - - inst = api.env.realm.replace('.', '-') -diff --git a/tests/test_core_files.py b/tests/test_core_files.py -index 8257f40..509e28e 100644 ---- a/tests/test_core_files.py -+++ b/tests/test_core_files.py -@@ -15,7 +15,8 @@ nobody = pwd.getpwnam('nobody') - - # Mock files to test - files = (('foo', 'root', 'root', '0660'), -- ('bar', 'nobody', 'nobody', '0664'),) -+ ('bar', 'nobody', 'nobody', '0664'), -+ ('zap', 'root', 'root', ('0664', '0640'),)) - - - def make_stat(mode=33200, uid=0, gid=0): -@@ -25,6 +26,13 @@ def make_stat(mode=33200, uid=0, gid=0): - mode = 0660 - owner = root - group = root -+ -+ Cheat sheet equivalents: -+ 0600 = 33152 -+ 0640 = 33184 -+ 0644 = 33188 -+ 0660 = 33200 -+ 0666 = 33206 - """ - return posix.stat_result((mode, 1, 42, 1, uid, gid, 0, 1, 1, 1,)) - -@@ -80,6 +88,11 @@ def test_files_group(mock_stat): - assert my_results.results[0].result == constants.WARNING - assert my_results.results[1].result == constants.SUCCESS - -+ assert my_results.results[2].result == constants.WARNING -+ assert my_results.results[2].kw.get('got') == 'nobody' -+ assert my_results.results[2].kw.get('expected') == 'root' -+ assert my_results.results[2].kw.get('type') == 'group' -+ - - @patch('os.stat') - def test_files_mode(mock_stat): -@@ -94,17 +107,28 @@ def test_files_mode(mock_stat): - assert my_results.results[0].result == constants.SUCCESS - assert my_results.results[1].result == constants.ERROR - -- mock_stat.return_value = make_stat(mode=33152) -+ mock_stat.return_value = make_stat(mode=33152) # 0600 - results = capture_results(f) - my_results = get_results(results, 'mode') - assert my_results.results[0].result == constants.ERROR - assert my_results.results[1].result == constants.ERROR -+ assert my_results.results[2].result == constants.ERROR - -- mock_stat.return_value = make_stat(mode=33206) -+ # Too restrictive -+ mock_stat.return_value = make_stat(mode=33206) # 0666 - results = capture_results(f) - my_results = get_results(results, 'mode') - assert my_results.results[0].result == constants.WARNING - assert my_results.results[1].result == constants.WARNING -+ assert my_results.results[2].result == constants.WARNING -+ -+ # Too restrictive with allowed multi-mode value -+ mock_stat.return_value = make_stat(mode=33184) # 0640 -+ results = capture_results(f) -+ my_results = get_results(results, 'mode') -+ assert my_results.results[0].result == constants.ERROR -+ assert my_results.results[1].result == constants.ERROR -+ assert my_results.results[2].result == constants.SUCCESS - - - @patch('os.path.exists') -@@ -118,7 +142,7 @@ def test_files_not_found(mock_exists): - - for type in ('mode', 'group', 'owner'): - my_results = get_results(results, type) -- assert len(my_results.results) == 2 -+ assert len(my_results.results) == len(f.files) - for result in my_results.results: - assert result.result == constants.SUCCESS - assert result.kw.get('msg') == 'File does not exist' --- -2.31.1 - diff --git a/SOURCES/0023-Unify-command-line-options-and-configuration.patch b/SOURCES/0023-Unify-command-line-options-and-configuration.patch deleted file mode 100644 index 1388a72..0000000 --- a/SOURCES/0023-Unify-command-line-options-and-configuration.patch +++ /dev/null @@ -1,245 +0,0 @@ -From 71221179de4c5694a0032cc667891322d5016ee3 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue Aug 31 15:55:04 2021 -0400 -Subject: Unify command-line options and configuration - -This makes it possible to add command-line options to the -configuration file. - -The config file is loaded then the command-line options are -merged in. The first one option set takes precedence. So if -an option, say output_type, is in the configuration file then -passing output-type on the command-line will not override it. -The workaround is to pass --config= to ipa-healthcheck in order -to not load the configuration file. - -This will allow for greater flexibility when running this automatically -without having to manually change test timer scripting directly. - -https://bugzilla.redhat.com/show_bug.cgi?id=1872467 - -Signed-off-by: Rob Crittenden ---- - man/man5/ipahealthcheck.conf.5 | 10 ++++++- - man/man8/ipa-healthcheck.8 | 3 +++ - src/ipahealthcheck/core/config.py | 2 +- - src/ipahealthcheck/core/core.py | 22 +++++++++++---- - src/ipahealthcheck/core/output.py | 4 +-- - tests/test_init.py | 6 ++--- - tests/test_options.py | 45 +++++++++++++++++++++++++++++++ - 7 files changed, 80 insertions(+), 12 deletions(-) - create mode 100644 tests/test_options.py - -diff --git a/man/man5/ipahealthcheck.conf.5 b/man/man5/ipahealthcheck.conf.5 -index 50d5506..8ff83c9 100644 ---- a/man/man5/ipahealthcheck.conf.5 -+++ b/man/man5/ipahealthcheck.conf.5 -@@ -36,6 +36,14 @@ The following options are relevant for the server: - .TP - .B cert_expiration_days\fR - The number of days left before a certificate expires to start displaying a warning. The default is 28. -+.TP -+All command\-line options may be included in the configuration file. Dashes must be converted to underscore for the configuration file, e.g. \-\-output\-type becomes output_type. All options, including those that don't make sense in a config file, like \-\-list\-sources, are allowed. Let the buyer beware. -+.TP -+The purpose of allowing command\-line options to be in the configuration file is for automation without having to tweak the automation script. For example, if you want the default output type to be human for the systemd timer automated runs, settting output_type=human in the configuration file will do this. When loading configuration the first option wins, so if any option is in the configuration file then it cannot be overridden by the command-line unless a different configuration file is specified (see \-\-config). -+.TP -+There may be conflicting exceptions. For example, if all=True is set in the configuration file, and the command\-line contains \-\-failures\-only, then only failures will be displayed because of the way the option evaluation is done. -+.TP -+Options that don't make sense for the configuration file include \-\-list\-sources and \-\-input\-file. - .SH "FILES" - .TP - .I /etc/ipahealthcheck/ipahealthcheck.conf -@@ -49,4 +57,4 @@ configuration file - cert_expiration_days=7 - - .SH "SEE ALSO" --.BR ipa-healthcheck (8) -+.BR ipa\-healthcheck (8) -diff --git a/man/man8/ipa-healthcheck.8 b/man/man8/ipa-healthcheck.8 -index 9c97cfe..148a08b 100644 ---- a/man/man8/ipa-healthcheck.8 -+++ b/man/man8/ipa-healthcheck.8 -@@ -30,6 +30,9 @@ Display a list of the available sources and the checks associated with those sou - - .SS "OPTIONAL ARGUMENTS" - .TP -+\fB\-\-config\fR=\fIFILE\fR -+The configuration file to use. If an empty string is passed in then no configuration file is loaded. The default is /etc/ipahealthcheck/ipahealthcheck.conf. -+.TP - \fB\-\-source\fR=\fISOURCE\fR - Execute checks within the named source, or all sources in the given namespace. - .TP -diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py -index 5b41cc3..14ae27f 100644 ---- a/src/ipahealthcheck/core/config.py -+++ b/src/ipahealthcheck/core/config.py -@@ -63,7 +63,7 @@ class Config: - """ - Merge variables from dict ``d`` into the configuration - -- The last one wins. -+ The first one wins. - - :param d: dict containing configuration - """ -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index 19f7818..98ca1d2 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -160,6 +160,8 @@ def list_sources(plugins): - def add_default_options(parser, output_registry, default_output): - output_names = [plugin.__name__.lower() for - plugin in output_registry.plugins] -+ parser.add_argument('--config', dest='config', -+ default=None, help='Config file to load') - parser.add_argument('--verbose', dest='verbose', action='store_true', - default=False, help='Run in verbose mode') - parser.add_argument('--debug', dest='debug', action='store_true', -@@ -173,9 +175,10 @@ def add_default_options(parser, output_registry, default_output): - parser.add_argument('--check', dest='check', - default=None, - help='Check to execute, e.g. BazCheck') -- parser.add_argument('--output-type', dest='output', choices=output_names, -+ parser.add_argument('--output-type', dest='output_type', -+ choices=output_names, - default=default_output, help='Output method') -- parser.add_argument('--output-file', dest='outfile', default=None, -+ parser.add_argument('--output-file', dest='output_file', default=None, - help='File to store output') - - -@@ -262,7 +265,6 @@ class RunChecks: - add_output_options(self.parser, self.output_registry) - self.add_options() - options = parse_options(self.parser) -- self.options = options - rval = self.validate_options() - if rval: - return rval -@@ -273,10 +275,20 @@ class RunChecks: - if options.debug: - logger.setLevel(logging.DEBUG) - -- config = read_config(self.configfile) -+ if options.config is not None: -+ config = read_config(options.config) -+ else: -+ config = read_config(self.configfile) - if config is None: - return 1 - -+ # Unify config and options. One of these variables will be -+ # eventually deprecated in the future. This way all cli -+ # options can be set in config instead. -+ config.merge(vars(options)) -+ self.options = config -+ options = config -+ - rval = self.pre_check() - if rval is not None: - return rval -@@ -319,7 +331,7 @@ class RunChecks: - plugins.append(plugin) - - for out in self.output_registry.plugins: -- if out.__name__.lower() == options.output: -+ if out.__name__.lower() == options.output_type: - output = out(options) - break - -diff --git a/src/ipahealthcheck/core/output.py b/src/ipahealthcheck/core/output.py -index 784263d..61dffbe 100644 ---- a/src/ipahealthcheck/core/output.py -+++ b/src/ipahealthcheck/core/output.py -@@ -36,7 +36,7 @@ class Output: - which will render the results into a string for writing. - """ - def __init__(self, options): -- self.filename = options.outfile -+ self.filename = options.output_file - - # Non-required options in the framework, set logical defaults to - # pre 0.6 behavior with everything reported. -@@ -110,7 +110,7 @@ class JSON(Output): - - def __init__(self, options): - super(JSON, self).__init__(options) -- self.indent = options.indent -+ self.indent = int(options.indent) - - def generate(self, data): - output = json.dumps(data, indent=self.indent) -diff --git a/tests/test_init.py b/tests/test_init.py -index e18e03c..5f9e3e2 100644 ---- a/tests/test_init.py -+++ b/tests/test_init.py -@@ -10,12 +10,12 @@ from ipahealthcheck.core.output import output_registry - class RunChecks: - def run_healthcheck(self): - options = argparse.Namespace(check=None, debug=False, indent=2, -- list_sources=False, outfile=None, -- output='json', source=None, -+ list_sources=False, output_file=None, -+ output_type='json', source=None, - verbose=False) - - for out in output_registry.plugins: -- if out.__name__.lower() == options.output: -+ if out.__name__.lower() == options.output_type: - out(options) - break - -diff --git a/tests/test_options.py b/tests/test_options.py -new file mode 100644 -index 0000000..da1866f ---- /dev/null -+++ b/tests/test_options.py -@@ -0,0 +1,45 @@ -+# -+# Copyright (C) 2022 FreeIPA Contributors see COPYING for license -+# -+ -+import argparse -+import os -+import tempfile -+from unittest.mock import patch -+ -+from ipahealthcheck.core.core import RunChecks -+from ipahealthcheck.core.plugin import Results -+ -+options = argparse.Namespace(check=None, source=None, debug=False, -+ indent=2, list_sources=False, -+ output_type='json', output_file=None, -+ verbose=False, version=False, config=None) -+ -+ -+@patch('ipahealthcheck.core.core.run_service_plugins') -+@patch('ipahealthcheck.core.core.run_plugins') -+@patch('ipahealthcheck.core.core.parse_options') -+def test_options_merge(mock_parse, mock_run, mock_service): -+ """ -+ Test merging file-based and CLI options -+ """ -+ mock_service.return_value = (Results(), []) -+ mock_run.return_value = Results() -+ mock_parse.return_value = options -+ fd, config_path = tempfile.mkstemp() -+ os.close(fd) -+ with open(config_path, "w") as fd: -+ fd.write('[default]\n') -+ fd.write('output_type=human\n') -+ fd.write('indent=5\n') -+ -+ try: -+ run = RunChecks(['ipahealthcheck.registry'], config_path) -+ -+ run.run_healthcheck() -+ -+ # verify two valus that have defaults with our overriden values -+ assert run.options.output_type == 'human' -+ assert run.options.indent == '5' -+ finally: -+ os.remove(config_path) --- -2.31.1 - diff --git a/SOURCES/0024-Convert-configuration-option-strings-into-native-dat.patch b/SOURCES/0024-Convert-configuration-option-strings-into-native-dat.patch deleted file mode 100644 index 64e88d0..0000000 --- a/SOURCES/0024-Convert-configuration-option-strings-into-native-dat.patch +++ /dev/null @@ -1,148 +0,0 @@ -From 883ef5b965bb7b02a701037748436f8daa9d0643 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Fri Apr 29 11:42:53 2022 -0400 -Subject: Convert configuration option strings into native data - types - -When loading options from the configuration file they will all -be strings. This breaks existing boolean checks (if ) -and some assumptions about integer types (e.g. timeout, indent). - -So try to detect the data type, defaulting to remain as a string. - -Also hardcode some type validation for known keys to prevent -things like debug=foo (which would evaluate to True). - -https://bugzilla.redhat.com/show_bug.cgi?id=2079861 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/config.py | 47 ++++++++++++++++++++++++++++++- - src/ipahealthcheck/core/output.py | 2 +- - tests/test_config.py | 16 ++++++++++- - tests/test_options.py | 2 +- - 4 files changed, 63 insertions(+), 4 deletions(-) - -diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py -index 14ae27f..3cd4e8e 100644 ---- a/src/ipahealthcheck/core/config.py -+++ b/src/ipahealthcheck/core/config.py -@@ -71,6 +71,27 @@ class Config: - self.__d[key] = d[key] - - -+def convert_string(value): -+ """ -+ Reading options from the configuration file will leave them as -+ strings. This breaks boolean values so attempt to convert them. -+ """ -+ if not isinstance(value, str): -+ return value -+ -+ if value.lower() in ( -+ "true", -+ "false", -+ ): -+ return value.lower() == 'true' -+ else: -+ try: -+ value = int(value) -+ except ValueError: -+ pass -+ return value -+ -+ - def read_config(config_file): - """ - Simple configuration file reader -@@ -100,6 +121,30 @@ def read_config(config_file): - items = parser.items(CONFIG_SECTION) - - for (key, value) in items: -- config[key] = value -+ if len(value) == 0 or value is None: -+ logging.error( -+ "Empty value for %s in %s [%s]", -+ key, config_file, CONFIG_SECTION -+ ) -+ return None -+ else: -+ # Try to do some basic validation. This is unfortunately -+ # hardcoded. -+ if key in ('all', 'debug', 'failures_only', 'verbose'): -+ if value.lower() not in ('true', 'false'): -+ logging.error( -+ "%s is not a valid boolean in %s [%s]", -+ key, config_file, CONFIG_SECTION -+ ) -+ return None -+ elif key in ('indent',): -+ if not isinstance(convert_string(value), int): -+ logging.error( -+ "%s is not a valid integer in %s [%s]", -+ key, config_file, CONFIG_SECTION -+ ) -+ return None -+ # Some rough type translation from strings -+ config[key] = convert_string(value) - - return config -diff --git a/src/ipahealthcheck/core/output.py b/src/ipahealthcheck/core/output.py -index 61dffbe..945969f 100644 ---- a/src/ipahealthcheck/core/output.py -+++ b/src/ipahealthcheck/core/output.py -@@ -110,7 +110,7 @@ class JSON(Output): - - def __init__(self, options): - super(JSON, self).__init__(options) -- self.indent = int(options.indent) -+ self.indent = options.indent - - def generate(self, data): - output = json.dumps(data, indent=self.indent) -diff --git a/tests/test_config.py b/tests/test_config.py -index 09c2188..655233c 100644 ---- a/tests/test_config.py -+++ b/tests/test_config.py -@@ -2,7 +2,7 @@ - # Copyright (C) 2019 FreeIPA Contributors see COPYING for license - # - --from ipahealthcheck.core.config import read_config -+from ipahealthcheck.core.config import read_config, convert_string - import tempfile - - -@@ -60,3 +60,17 @@ def test_config_recursion(): - config._Config__d['_Config__d'] - except KeyError: - pass -+ -+ -+def test_convert_string(): -+ for value in ("s", "string", "BiggerString"): -+ assert convert_string(value) == value -+ -+ for value in ("True", "true", True): -+ assert convert_string(value) is True -+ -+ for value in ("False", "false", False): -+ assert convert_string(value) is False -+ -+ for value in ("10", "99999", 807): -+ assert convert_string(value) == int(value) -diff --git a/tests/test_options.py b/tests/test_options.py -index da1866f..00cdb7c 100644 ---- a/tests/test_options.py -+++ b/tests/test_options.py -@@ -40,6 +40,6 @@ def test_options_merge(mock_parse, mock_run, mock_service): - - # verify two valus that have defaults with our overriden values - assert run.options.output_type == 'human' -- assert run.options.indent == '5' -+ assert run.options.indent == 5 - finally: - os.remove(config_path) --- -2.31.1 - diff --git a/SOURCES/0025-Limit-config-file-delimiters-to-catch-empty-values.patch b/SOURCES/0025-Limit-config-file-delimiters-to-catch-empty-values.patch deleted file mode 100644 index ee21394..0000000 --- a/SOURCES/0025-Limit-config-file-delimiters-to-catch-empty-values.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 6acb134da07140618a03651756f490074ced3cbc Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu Apr 28 08:46:02 2022 -0400 -Subject: Limit config file delimiters to =, catch empty values - -ConfigParser allows both = and : as a delimiter. Limit to -just = to match the configuration file man page. - -Don't allow empty values in options in the config file. - -https://bugzilla.redhat.com/show_bug.cgi?id=2079739 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/config.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py -index 3cd4e8e..e795a2d 100644 ---- a/src/ipahealthcheck/core/config.py -+++ b/src/ipahealthcheck/core/config.py -@@ -107,7 +107,7 @@ def read_config(config_file): - format(config_file)) - return config - -- parser = ConfigParser() -+ parser = ConfigParser(delimiters='=') - try: - parser.read(config_file) - except ParsingError as e: --- -2.31.1 - diff --git a/SOURCES/0026-Relocate-eval-of-debug-verbose-in-case-they-are-set-.patch b/SOURCES/0026-Relocate-eval-of-debug-verbose-in-case-they-are-set-.patch deleted file mode 100644 index 9f8ed06..0000000 --- a/SOURCES/0026-Relocate-eval-of-debug-verbose-in-case-they-are-set-.patch +++ /dev/null @@ -1,52 +0,0 @@ -From f28a5fb06a72eca6dc107f11dfd0673ecb92daac Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu Apr 28 08:57:38 2022 -0400 -Subject: Relocate eval of debug/verbose in case they are set in - config file - -Since the configuration file allows options to be set we need -to evaluate them after the merge. - -Leaving version handling pre-config load since it makes no sense -within the config file. - -https://bugzilla.redhat.com/show_bug.cgi?id=2079861 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/core.py | 12 ++++++------ - 1 file changed, 6 insertions(+), 6 deletions(-) - -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index 98ca1d2..092bab9 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -269,12 +269,6 @@ class RunChecks: - if rval: - return rval - -- if options.verbose: -- logger.setLevel(logging.INFO) -- -- if options.debug: -- logger.setLevel(logging.DEBUG) -- - if options.config is not None: - config = read_config(options.config) - else: -@@ -289,6 +283,12 @@ class RunChecks: - self.options = config - options = config - -+ if options.verbose: -+ logger.setLevel(logging.INFO) -+ -+ if options.debug: -+ logger.setLevel(logging.DEBUG) -+ - rval = self.pre_check() - if rval is not None: - return rval --- -2.31.1 - diff --git a/SOURCES/0027-Validate-that-a-known-output-type-has-been-selected.patch b/SOURCES/0027-Validate-that-a-known-output-type-has-been-selected.patch deleted file mode 100644 index 6a9ff1f..0000000 --- a/SOURCES/0027-Validate-that-a-known-output-type-has-been-selected.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 663ae08a2374df76e93d007551fa5e143425d064 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu, 28 Apr 2022 08:33:35 -0400 -Subject: Validate that a known output-type has been selected - -A user may pass an unknown value in via the configuration file. - -https://bugzilla.redhat.com/show_bug.cgi?id=2079698 - -Signed-off-by: Rob Crittenden ---- - src/ipahealthcheck/core/constants.py | 2 -- - src/ipahealthcheck/core/core.py | 5 ++++- - 2 files changed, 4 insertions(+), 3 deletions(-) - -diff --git a/src/ipahealthcheck/core/constants.py b/src/ipahealthcheck/core/constants.py -index b6ee029..806bd46 100644 ---- a/src/ipahealthcheck/core/constants.py -+++ b/src/ipahealthcheck/core/constants.py -@@ -2,8 +2,6 @@ - # Copyright (C) 2019 FreeIPA Contributors see COPYING for license - # - --DEFAULT_OUTPUT = 'json' -- - # Error reporting result - SUCCESS = 0 - WARNING = 10 -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py -index 092bab9..84aa8a4 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -256,7 +256,7 @@ class RunChecks: - def run_healthcheck(self): - framework = object() - plugins = [] -- output = constants.DEFAULT_OUTPUT -+ output = None - - logger.setLevel(logging.WARNING) - -@@ -334,6 +334,9 @@ class RunChecks: - if out.__name__.lower() == options.output_type: - output = out(options) - break -+ if output is None: -+ print(f"Unknown output-type '{options.output_type}'") -+ return 1 - - if options.list_sources: - return list_sources(plugins) --- -2.31.1 - diff --git a/SOURCES/0028-Restore-the-log-level-after-loading-the-resources.patch b/SOURCES/0028-Restore-the-log-level-after-loading-the-resources.patch deleted file mode 100644 index 2a2871e..0000000 --- a/SOURCES/0028-Restore-the-log-level-after-loading-the-resources.patch +++ /dev/null @@ -1,33 +0,0 @@ -commit b0b782da304babcff3ef64374c79bc879cbdc303 -Author: Rob Crittenden -Date: Wed May 5 16:30:43 2021 -0400 - - Restore the log level after loading the resources - - It seems that loading the resources can affect the log level so - save it off before loading them and then restore it. - - I'm not entirely sure what is happening but this is an easy fix - and we can address it further later if we want. - - Signed-off-by: Rob Crittenden - -diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.p -y -index 4ba94be..2d00586 100644 ---- a/src/ipahealthcheck/core/core.py -+++ b/src/ipahealthcheck/core/core.py -@@ -21,12 +21,15 @@ logger = logging.getLogger() - - - def find_registries(entry_points): -+ # Loading the resources may reset the log level, save it. -+ log_level = logger.level - registries = {} - for entry_point in entry_points: - registries.update({ - ep.name: ep.resolve() - for ep in pkg_resources.iter_entry_points(entry_point) - }) -+ logger.setLevel(log_level) - return registries diff --git a/SPECS/ipa-healthcheck.spec b/SPECS/ipa-healthcheck.spec index 9be5dc3..5e9bd40 100644 --- a/SPECS/ipa-healthcheck.spec +++ b/SPECS/ipa-healthcheck.spec @@ -7,42 +7,18 @@ Name: ipa-healthcheck -Version: 0.7 -Release: 14%{?dist} +Version: 0.12 +Release: 1%{?dist} Summary: Health check tool for IdM BuildArch: noarch License: GPLv3 URL: https://github.com/%{project}/freeipa-healthcheck -Source0: https://github.com/%{project}/%{name}/archive/%{version}.tar.gz#/%{project}-%{shortname}-%{version}.tar.gz +Source0: https://github.com/%{project}/%{name}/archive/%{version}.tar.gz#/%{version}.tar.gz Source1: %{longname}.conf -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 -Patch0013: 0013-Add-service-check-dependencies.patch -Patch0014: 0014-Filter-out-the-pki-healthcheck-sources-if-IPA-CA-is-.patch -Patch0015: 0015-Work-with-existing-resolve_rrsets-and-newer-resolve_.patch -Patch0016: 0016-tests-Generate-a-proper-not-valid-after-field.patch -Patch0017: 0017-Fix-the-number-of-expected-results-in-the-fix-file-t.patch -Patch0018: 0018-Don-t-collect-the-CRLManager-role-if-the-CA-is-not-c.patch -Patch0019: 0019-Don-t-depend-on-IPA-status-when-suppressing-pki-chec.patch -Patch0020: 0020-Add-support-for-the-DNS-URI-type.patch -Patch0021: 0021-Use-the-subject-base-from-the-IPA-configuration-not-.patch -Patch0022: 0022-Allow-multiple-file-modes-in-the-FileChecker.patch -Patch0023: 0023-Unify-command-line-options-and-configuration.patch -Patch0024: 0024-Convert-configuration-option-strings-into-native-dat.patch -Patch0025: 0025-Limit-config-file-delimiters-to-catch-empty-values.patch -Patch0026: 0026-Relocate-eval-of-debug-verbose-in-case-they-are-set-.patch -Patch0027: 0027-Validate-that-a-known-output-type-has-been-selected.patch -Patch0028: 0028-Restore-the-log-level-after-loading-the-resources.patch +Patch0001: 0001-Remove-ipaclustercheck.patch +Patch0002: 0002-Disable-two-failing-tests.patch +Patch0003: 0003-Fix-logging-issue-related-to-dtype.patch Requires: %{name}-core = %{version}-%{release} Requires: ipa-server @@ -146,6 +122,13 @@ install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{long %changelog +* Thu Dec 01 2022 Rob Crittenden - 0.12-1 +- Update to upstream 0.12 (#2139529) +- Verify that the number of krb5kdc worker processes is aligned to the + number of configured CPUs (#2052930) +- IPADNSSystemRecordsCheck displays warning message for 2 expected + ipa-ca AAAA records (#2099484) + * Wed May 25 2022 Rob Crittenden - 0.7-14 - Add CLI options to healthcheck configuration file (#1872467)