commit
1838ded070
@ -0,0 +1 @@
|
||||
SOURCES/0.9.tar.gz
|
@ -0,0 +1 @@
|
||||
01a26143be6b2d3d4500bcd43a282c51d4f50ce8 SOURCES/0.9.tar.gz
|
@ -0,0 +1,640 @@
|
||||
From 3c82636f3b1333333314bbc012d30a7faed00261 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Tue, 8 Jun 2021 13:51:28 -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 | 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 f2bdb7b..2abcf7f 100644
|
||||
--- a/setup.py
|
||||
+++ b/setup.py
|
||||
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
||||
setup(
|
||||
name='ipahealthcheck',
|
||||
version='0.9',
|
||||
- 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 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 6a1963d..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.outfile
|
||||
-
|
||||
- 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 b6ad9ec..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 2a9540c..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 _, 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.26.3
|
||||
|
@ -0,0 +1,63 @@
|
||||
From 7db48931c9b3045406e465abb4d2a21beaadfcc4 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Mon, 14 Jun 2021 08:44:39 -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 <rcritten@redhat.com>
|
||||
---
|
||||
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 a63c06b..0a7641e 100644
|
||||
--- a/src/ipahealthcheck/core/files.py
|
||||
+++ b/src/ipahealthcheck/core/files.py
|
||||
@@ -33,6 +33,13 @@ class FileCheck:
|
||||
owner = tuple((owner,))
|
||||
if not isinstance(group, tuple):
|
||||
group = tuple((group,))
|
||||
+ 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 3b71469..10824ab 100644
|
||||
--- a/tests/test_core_files.py
|
||||
+++ b/tests/test_core_files.py
|
||||
@@ -144,3 +144,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
|
||||
|
@ -0,0 +1,118 @@
|
||||
From de2032487c73151e13812db78866ddd85d0f541c Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Mon, 28 Jun 2021 16:43:11 -0400
|
||||
Subject: [PATCH] Allow for HIDDEN_SERVICE when checking ADTRUST service
|
||||
|
||||
If the host is a trust controller then the ADTRUST service
|
||||
must be enabled. This is defined as both ENABLED_SERVICE and
|
||||
HIDDEN_SERVICE.
|
||||
|
||||
https://github.com/freeipa/freeipa-healthcheck/issues/217
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/ipa/trust.py | 6 ++--
|
||||
tests/test_ipa_trust.py | 54 ++++++++++++++++++---------------
|
||||
2 files changed, 33 insertions(+), 27 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/ipa/trust.py b/src/ipahealthcheck/ipa/trust.py
|
||||
index 162a64c..27a2c86 100644
|
||||
--- a/src/ipahealthcheck/ipa/trust.py
|
||||
+++ b/src/ipahealthcheck/ipa/trust.py
|
||||
@@ -23,9 +23,9 @@ except ImportError:
|
||||
# be skipped
|
||||
pass
|
||||
try:
|
||||
- from ipaserver.masters import ENABLED_SERVICE
|
||||
+ from ipaserver.masters import ENABLED_SERVICE, HIDDEN_SERVICE
|
||||
except ImportError:
|
||||
- from ipaserver.install.service import ENABLED_SERVICE
|
||||
+ from ipaserver.install.service import ENABLED_SERVICE, HIDDEN_SERVICE
|
||||
try:
|
||||
from ipapython.ipaldap import realm_to_serverid
|
||||
except ImportError:
|
||||
@@ -476,7 +476,7 @@ class IPATrustControllerServiceCheck(IPAPlugin):
|
||||
configs = entry.get('ipaconfigstring', [])
|
||||
enabled = False
|
||||
for config in configs:
|
||||
- if config == ENABLED_SERVICE:
|
||||
+ if config in [ENABLED_SERVICE, HIDDEN_SERVICE]:
|
||||
enabled = True
|
||||
break
|
||||
|
||||
diff --git a/tests/test_ipa_trust.py b/tests/test_ipa_trust.py
|
||||
index 5eca9b5..c314b70 100644
|
||||
--- a/tests/test_ipa_trust.py
|
||||
+++ b/tests/test_ipa_trust.py
|
||||
@@ -28,6 +28,11 @@ from ipahealthcheck.ipa.trust import (IPATrustAgentCheck,
|
||||
from ipalib import errors
|
||||
from ipapython.dn import DN
|
||||
from ipapython.ipaldap import LDAPClient, LDAPEntry
|
||||
+try:
|
||||
+ from ipaserver.masters import ENABLED_SERVICE, HIDDEN_SERVICE
|
||||
+except ImportError:
|
||||
+ from ipaserver.install.service import ENABLED_SERVICE, HIDDEN_SERVICE
|
||||
+
|
||||
|
||||
try:
|
||||
from ipapython.ipaldap import realm_to_serverid
|
||||
@@ -795,31 +800,32 @@ class TestControllerService(BaseTest):
|
||||
# Zero because the call was skipped altogether
|
||||
assert len(self.results) == 0
|
||||
|
||||
- def test_principal_ok(self):
|
||||
+ def test_service_enabled(self):
|
||||
service_dn = DN(('cn', 'ADTRUST'))
|
||||
- attrs = {
|
||||
- 'ipaconfigstring': ['enabledService'],
|
||||
- }
|
||||
- fake_conn = LDAPClient('ldap://localhost', no_schema=True)
|
||||
- ldapentry = LDAPEntry(fake_conn, service_dn)
|
||||
- for attr, values in attrs.items():
|
||||
- ldapentry[attr] = values
|
||||
-
|
||||
- framework = object()
|
||||
- registry.initialize(framework, config.Config)
|
||||
- registry.trust_controller = True
|
||||
- f = IPATrustControllerServiceCheck(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.trust'
|
||||
- assert result.check == 'IPATrustControllerServiceCheck'
|
||||
- assert result.kw.get('key') == 'ADTRUST'
|
||||
+ for type in [ENABLED_SERVICE, HIDDEN_SERVICE]:
|
||||
+ attrs = {
|
||||
+ 'ipaconfigstring': [type],
|
||||
+ }
|
||||
+ fake_conn = LDAPClient('ldap://localhost', no_schema=True)
|
||||
+ ldapentry = LDAPEntry(fake_conn, service_dn)
|
||||
+ for attr, values in attrs.items():
|
||||
+ ldapentry[attr] = values
|
||||
+
|
||||
+ framework = object()
|
||||
+ registry.initialize(framework, config.Config)
|
||||
+ registry.trust_controller = True
|
||||
+ f = IPATrustControllerServiceCheck(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.trust'
|
||||
+ assert result.check == 'IPATrustControllerServiceCheck'
|
||||
+ assert result.kw.get('key') == 'ADTRUST'
|
||||
|
||||
def test_principal_fail(self):
|
||||
service_dn = DN(('cn', 'ADTRUST'))
|
||||
--
|
||||
2.31.1
|
||||
|
@ -0,0 +1,372 @@
|
||||
From c193957be7de8c538f88d28608f60ef8734fa63d Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Wed, 30 Mar 2022 09:08:41 -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 <rcritten@redhat.com>
|
||||
---
|
||||
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 82435f3..aecaac7 100644
|
||||
--- a/src/ipahealthcheck/ipa/certs.py
|
||||
+++ b/src/ipahealthcheck/ipa/certs.py
|
||||
@@ -732,12 +732,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:
|
||||
@@ -765,26 +767,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
|
||||
|
@ -0,0 +1,245 @@
|
||||
From 424daee946f9189d7ecdd2e5e21202c2ec02f4f8 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Wed, 6 Apr 2022 13:19:36 -0400
|
||||
Subject: [PATCH] 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 <rcritten@redhat.com>
|
||||
---
|
||||
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 bf9ca1f..c4322a5 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 6fd3640..aaadd3b 100644
|
||||
--- a/src/ipahealthcheck/core/core.py
|
||||
+++ b/src/ipahealthcheck/core/core.py
|
||||
@@ -163,6 +163,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',
|
||||
@@ -176,9 +178,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')
|
||||
parser.add_argument('--version', dest='version', action='store_true',
|
||||
help='Report the version number and exit')
|
||||
@@ -266,7 +269,6 @@ class RunChecks:
|
||||
add_output_options(self.parser, self.output_registry)
|
||||
self.add_options()
|
||||
options = parse_options(self.parser)
|
||||
- self.options = options
|
||||
|
||||
if options.version:
|
||||
for registry in self.entry_points:
|
||||
@@ -290,10 +292,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
|
||||
+
|
||||
# pylint: disable=assignment-from-none
|
||||
rval = self.pre_check()
|
||||
# pylint: enable=assignment-from-none
|
||||
@@ -327,7 +339,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 787ac6d..e44990e 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().__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
|
||||
|
@ -0,0 +1,198 @@
|
||||
From 621a32bb892fa36cb2fd78a3f30f6080300b0bef Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
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 <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/core/files.py | 31 ++++++++++++++++------
|
||||
src/ipahealthcheck/ipa/files.py | 3 ++-
|
||||
tests/test_core_files.py | 44 +++++++++++++++++++++++++++++---
|
||||
3 files changed, 65 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/core/files.py b/src/ipahealthcheck/core/files.py
|
||||
index 0a7641e..59e8b76 100644
|
||||
--- a/src/ipahealthcheck/core/files.py
|
||||
+++ b/src/ipahealthcheck/core/files.py
|
||||
@@ -16,7 +16,7 @@ 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 or a tuple of names, not uid/gid.
|
||||
|
||||
If owner and/or group are tuples then all names are checked.
|
||||
@@ -33,6 +33,8 @@ class FileCheck:
|
||||
owner = tuple((owner,))
|
||||
if not isinstance(group, tuple):
|
||||
group = tuple((group,))
|
||||
+ 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)
|
||||
@@ -43,19 +45,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 4728171..c86be0d 100644
|
||||
--- a/src/ipahealthcheck/ipa/files.py
|
||||
+++ b/src/ipahealthcheck/ipa/files.py
|
||||
@@ -123,7 +123,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 10824ab..6e3ec38 100644
|
||||
--- a/tests/test_core_files.py
|
||||
+++ b/tests/test_core_files.py
|
||||
@@ -17,7 +17,8 @@ nobody = pwd.getpwnam('nobody')
|
||||
files = (('foo', 'root', 'root', '0660'),
|
||||
('bar', 'nobody', 'nobody', '0664'),
|
||||
('baz', ('root', 'nobody'), ('root', 'nobody'), '0664'),
|
||||
- ('fiz', ('root', 'bin'), ('root', 'bin'), '0664'),)
|
||||
+ ('fiz', ('root', 'bin'), ('root', 'bin'), '0664'),
|
||||
+ ('zap', ('root', 'bin'), ('root', 'bin'), ('0664', '0640'),))
|
||||
|
||||
|
||||
def make_stat(mode=33200, uid=0, gid=0):
|
||||
@@ -27,6 +28,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
|
||||
"""
|
||||
# (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
|
||||
return posix.stat_result((mode, 1, 42, 1, uid, gid, 0, 1, 1, 1,))
|
||||
@@ -81,6 +89,11 @@ def test_files_owner(mock_stat):
|
||||
assert my_results.results[3].kw.get('msg') == \
|
||||
'Ownership of fiz is nobody and should be one of root,bin'
|
||||
|
||||
+ assert my_results.results[4].result == constants.WARNING
|
||||
+ assert my_results.results[4].kw.get('got') == 'nobody'
|
||||
+ assert my_results.results[4].kw.get('expected') == 'root,bin'
|
||||
+ assert my_results.results[4].kw.get('type') == 'owner'
|
||||
+
|
||||
|
||||
@patch('os.stat')
|
||||
def test_files_group(mock_stat):
|
||||
@@ -119,6 +132,11 @@ def test_files_group(mock_stat):
|
||||
assert my_results.results[3].kw.get('msg') == \
|
||||
'Group of fiz is nobody and should be one of root,bin'
|
||||
|
||||
+ assert my_results.results[4].result == constants.WARNING
|
||||
+ assert my_results.results[4].kw.get('got') == 'nobody'
|
||||
+ assert my_results.results[4].kw.get('expected') == 'root,bin'
|
||||
+ assert my_results.results[4].kw.get('type') == 'group'
|
||||
+
|
||||
|
||||
@patch('os.stat')
|
||||
def test_files_mode(mock_stat):
|
||||
@@ -133,17 +151,35 @@ 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)
|
||||
+ # Too restrictive
|
||||
+ 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
|
||||
+ assert my_results.results[3].result == constants.ERROR
|
||||
+ assert my_results.results[4].result == constants.ERROR
|
||||
|
||||
- mock_stat.return_value = make_stat(mode=33206)
|
||||
+ # Too permissive
|
||||
+ 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
|
||||
+ assert my_results.results[3].result == constants.WARNING
|
||||
+ assert my_results.results[4].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.ERROR
|
||||
+ assert my_results.results[3].result == constants.ERROR
|
||||
+ assert my_results.results[4].result == constants.SUCCESS
|
||||
|
||||
|
||||
@patch('os.path.exists')
|
||||
@@ -157,7 +193,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) == 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
|
||||
|
@ -0,0 +1,48 @@
|
||||
From ce89ed552ff6feb1ecc1c05269022913b5a8edcc Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Thu, 28 Apr 2022 08:46:02 -0400
|
||||
Subject: [PATCH] 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 <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/core/config.py | 11 +++++++++--
|
||||
1 file changed, 9 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py
|
||||
index c4322a5..01c7722 100644
|
||||
--- a/src/ipahealthcheck/core/config.py
|
||||
+++ b/src/ipahealthcheck/core/config.py
|
||||
@@ -87,7 +87,7 @@ def read_config(config_file):
|
||||
)
|
||||
return config
|
||||
|
||||
- parser = ConfigParser()
|
||||
+ parser = ConfigParser(delimiters='=')
|
||||
try:
|
||||
parser.read(config_file)
|
||||
except ParsingError as e:
|
||||
@@ -102,6 +102,13 @@ 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:
|
||||
+ config[key] = value
|
||||
|
||||
return config
|
||||
--
|
||||
2.31.1
|
||||
|
@ -0,0 +1,52 @@
|
||||
From 9967eb8adbcf471d652337ed77add05480773a1a Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Thu, 28 Apr 2022 08:57:38 -0400
|
||||
Subject: [PATCH 8/9] 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 <rcritten@redhat.com>
|
||||
---
|
||||
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 c04a6ee..6e6a928 100644
|
||||
--- a/src/ipahealthcheck/core/core.py
|
||||
+++ b/src/ipahealthcheck/core/core.py
|
||||
@@ -346,12 +346,6 @@ class RunChecks:
|
||||
if rval is not None:
|
||||
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:
|
||||
@@ -366,6 +360,12 @@ class RunChecks:
|
||||
self.options = config
|
||||
options = config
|
||||
|
||||
+ if options.verbose:
|
||||
+ logger.setLevel(logging.INFO)
|
||||
+
|
||||
+ if options.debug:
|
||||
+ logger.setLevel(logging.DEBUG)
|
||||
+
|
||||
# pylint: disable=assignment-from-none
|
||||
rval = self.pre_check()
|
||||
# pylint: enable=assignment-from-none
|
||||
--
|
||||
2.31.1
|
||||
|
@ -0,0 +1,139 @@
|
||||
From c0ef933e5465baa3882e7ed301f8a7a1f4f05301 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Fri, 29 Apr 2022 11:42:53 -0400
|
||||
Subject: [PATCH] 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 <something>)
|
||||
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 <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/core/config.py | 39 +++++++++++++++++++++++++++++++
|
||||
src/ipahealthcheck/core/output.py | 2 +-
|
||||
tests/test_config.py | 16 ++++++++++++-
|
||||
tests/test_options.py | 2 +-
|
||||
4 files changed, 56 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py
|
||||
index 01c7722..a1bd2d5 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
|
||||
@@ -110,5 +131,23 @@ def read_config(config_file):
|
||||
return None
|
||||
else:
|
||||
config[key] = value
|
||||
+ # 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 e44990e..ca9297c 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().__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 dbfca2b..02a7e63 100644
|
||||
--- a/tests/test_config.py
|
||||
+++ b/tests/test_config.py
|
||||
@@ -6,7 +6,7 @@ import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
-from ipahealthcheck.core.config import read_config
|
||||
+from ipahealthcheck.core.config import read_config, convert_string
|
||||
|
||||
|
||||
def test_config_no_section():
|
||||
@@ -59,3 +59,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
|
||||
|
@ -0,0 +1,54 @@
|
||||
From eb6cb8ec7abad0d627ad82fb2761fa313849f2fc Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Thu, 28 Apr 2022 08:33:35 -0400
|
||||
Subject: [PATCH] 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 <rcritten@redhat.com>
|
||||
---
|
||||
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 6a061f7..de27cea 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 c5275b6..c04a6ee 100644
|
||||
--- a/src/ipahealthcheck/core/core.py
|
||||
+++ b/src/ipahealthcheck/core/core.py
|
||||
@@ -320,7 +320,7 @@ class RunChecks:
|
||||
def run_healthcheck(self):
|
||||
framework = object()
|
||||
plugins = []
|
||||
- output = constants.DEFAULT_OUTPUT
|
||||
+ output = None
|
||||
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
@@ -413,6 +413,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
|
||||
|
@ -0,0 +1,577 @@
|
||||
From 0355be5205c8fa0645c7d7552654a331a4727821 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Wed, 6 Jul 2022 16:59:40 -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 <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/ipa/idns.py | 64 ++++++++-
|
||||
tests/test_ipa_dns.py | 228 +++++++++++++++++++++++++++------
|
||||
2 files changed, 246 insertions(+), 46 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/ipa/idns.py b/src/ipahealthcheck/ipa/idns.py
|
||||
index 3282e2c..9f1b158 100644
|
||||
--- a/src/ipahealthcheck/ipa/idns.py
|
||||
+++ b/src/ipahealthcheck/ipa/idns.py
|
||||
@@ -11,12 +11,25 @@ from ipahealthcheck.core.plugin import Result, duration
|
||||
from ipahealthcheck.core import constants
|
||||
|
||||
from ipalib import api
|
||||
-from dns import resolver
|
||||
+
|
||||
+try:
|
||||
+ from dns.resolver import resolve
|
||||
+except ImportError:
|
||||
+ 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 +45,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):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
@@ -45,6 +62,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()
|
||||
|
||||
@@ -65,6 +83,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)
|
||||
|
||||
@@ -97,10 +124,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 = []
|
||||
@@ -123,7 +179,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 = []
|
||||
@@ -157,7 +213,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 11e1aa9..43ddcb9 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 = []
|
||||
@@ -202,16 +250,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))
|
||||
]
|
||||
@@ -233,7 +284,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
|
||||
@@ -242,13 +297,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)),
|
||||
@@ -281,7 +342,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
|
||||
@@ -290,14 +355,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)),
|
||||
@@ -339,7 +411,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
|
||||
@@ -348,15 +424,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)),
|
||||
@@ -396,7 +478,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
|
||||
@@ -404,9 +490,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
|
||||
@@ -417,6 +504,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)),
|
||||
@@ -458,21 +550,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
|
||||
@@ -483,6 +584,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)),
|
||||
@@ -524,12 +630,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(
|
||||
@@ -541,9 +655,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
|
||||
@@ -555,6 +670,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)),
|
||||
@@ -598,12 +718,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') == \
|
||||
@@ -611,12 +739,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))
|
||||
]
|
||||
@@ -638,12 +768,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'
|
||||
@@ -651,11 +789,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))
|
||||
]
|
||||
@@ -678,7 +818,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
|
||||
|
@ -0,0 +1 @@
|
||||
[default]
|
@ -0,0 +1,318 @@
|
||||
%if 0%{?rhel}
|
||||
%global prefix ipa
|
||||
%global productname IPA
|
||||
%global alt_prefix freeipa
|
||||
%else
|
||||
# Fedora
|
||||
%global prefix freeipa
|
||||
%global productname FreeIPA
|
||||
%global alt_prefix ipa
|
||||
%endif
|
||||
%global debug_package %{nil}
|
||||
%global python3dir %{_builddir}/python3-%{name}-%{version}-%{release}
|
||||
%{!?python3_sitelib: %global python3_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
|
||||
%global alt_name %{alt_prefix}-healthcheck
|
||||
|
||||
%bcond_without tests
|
||||
|
||||
Name: %{prefix}-healthcheck
|
||||
Version: 0.9
|
||||
Release: 9%{?dist}
|
||||
Summary: Health check tool for %{productname}
|
||||
BuildArch: noarch
|
||||
License: GPLv3
|
||||
URL: https://github.com/freeipa/freeipa-healthcheck
|
||||
Source0: https://github.com/freeipa/freeipa-healthcheck/archive/%{version}.tar.gz
|
||||
Source1: ipahealthcheck.conf
|
||||
|
||||
Patch0001: 0001-Remove-ipaclustercheck.patch
|
||||
Patch0002: 0002-Handle-files-that-don-t-exist-in-FileCheck.patch
|
||||
Patch0003: 0003-Allow-for-HIDDEN_SERVICE-when-checking-ADTRUST-servi.patch
|
||||
Patch0004: 0004-Use-the-subject-base-from-the-IPA-configuration-not-.patch
|
||||
Patch0005: 0005-Unify-command-line-options-and-configuration.patch
|
||||
Patch0006: 0006-Allow-multiple-file-modes-in-the-FileChecker.patch
|
||||
Patch0007: 0007-Limit-config-file-delimiters-to-catch-empty-values.patch
|
||||
Patch0008: 0008-Relocate-eval-of-debug-verbose-in-case-they-are-set-.patch
|
||||
Patch0009: 0009-Convert-configuration-option-strings-into-native-dat.patch
|
||||
Patch0010: 0010-Validate-that-a-known-output-type-has-been-selected.patch
|
||||
Patch0011: 0011-Add-support-for-the-DNS-URI-type.patch
|
||||
|
||||
Requires: %{name}-core = %{version}-%{release}
|
||||
Requires: %{prefix}-server
|
||||
Requires: python3-ipalib
|
||||
Requires: python3-ipaserver
|
||||
Requires: python3-lib389 >= 1.4.2.14-1
|
||||
# cronie-anacron provides anacron
|
||||
Requires: anacron
|
||||
Requires: logrotate
|
||||
Requires(post): systemd-units
|
||||
Requires: %{name}-core = %{version}-%{release}
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-setuptools
|
||||
BuildRequires: systemd-devel
|
||||
%{?systemd_requires}
|
||||
# packages for make check
|
||||
%if %{with tests}
|
||||
BuildRequires: python3-pytest
|
||||
BuildRequires: python3-ipalib
|
||||
BuildRequires: python3-ipaserver
|
||||
%endif
|
||||
BuildRequires: python3-lib389
|
||||
BuildRequires: python3-libsss_nss_idmap
|
||||
|
||||
# Cross-provides for sibling OS
|
||||
Provides: %{alt_name} = %{version}
|
||||
Conflicts: %{alt_name}
|
||||
Obsoletes: %{alt_name} < %{version}
|
||||
|
||||
%description
|
||||
The %{productname} health check tool provides a set of checks to
|
||||
proactively detect defects in a FreeIPA cluster.
|
||||
|
||||
|
||||
%package -n %{name}-core
|
||||
Summary: Core plugin system for healthcheck
|
||||
|
||||
# Cross-provides for sibling OS
|
||||
Provides: %{alt_name}-core = %{version}
|
||||
Conflicts: %{alt_name}-core
|
||||
Obsoletes: %{alt_name}-core < %{version}
|
||||
|
||||
|
||||
%description -n %{name}-core
|
||||
Core plugin system for healthcheck, usable standalone with other
|
||||
packages.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n freeipa-healthcheck-%{version}
|
||||
|
||||
|
||||
%build
|
||||
%py3_build
|
||||
|
||||
|
||||
%install
|
||||
%py3_install
|
||||
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/ipahealthcheck
|
||||
install -m644 %{SOURCE1} %{buildroot}%{_sysconfdir}/ipahealthcheck
|
||||
|
||||
mkdir -p %{buildroot}/%{_unitdir}
|
||||
install -p -m644 %{_builddir}/freeipa-healthcheck-%{version}/systemd/ipa-healthcheck.service %{buildroot}%{_unitdir}
|
||||
install -p -m644 %{_builddir}/freeipa-healthcheck-%{version}/systemd/ipa-healthcheck.timer %{buildroot}%{_unitdir}
|
||||
|
||||
mkdir -p %{buildroot}/%{_libexecdir}/ipa
|
||||
install -p -m755 %{_builddir}/freeipa-healthcheck-%{version}/systemd/ipa-healthcheck.sh %{buildroot}%{_libexecdir}/ipa/
|
||||
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d
|
||||
install -p -m644 %{_builddir}/freeipa-healthcheck-%{version}/logrotate/ipahealthcheck %{buildroot}%{_sysconfdir}/logrotate.d
|
||||
|
||||
mkdir -p %{buildroot}/%{_localstatedir}/log/ipa/healthcheck
|
||||
|
||||
mkdir -p %{buildroot}/%{_mandir}/man8
|
||||
mkdir -p %{buildroot}/%{_mandir}/man5
|
||||
install -p -m644 %{_builddir}/freeipa-healthcheck-%{version}/man/man8/ipa-healthcheck.8 %{buildroot}%{_mandir}/man8/
|
||||
install -p -m644 %{_builddir}/freeipa-healthcheck-%{version}/man/man5/ipahealthcheck.conf.5 %{buildroot}%{_mandir}/man5/
|
||||
|
||||
(cd %{buildroot}/%{python3_sitelib}/ipahealthcheck && find . -type f | \
|
||||
grep -v '^./core' | \
|
||||
grep -v 'opt-1' | \
|
||||
sed -e 's,\.py.*$,.*,g' | sort -u | \
|
||||
sed -e 's,\./,%%{python3_sitelib}/ipahealthcheck/,g' ) >healthcheck.list
|
||||
|
||||
|
||||
%if %{with tests}
|
||||
%check
|
||||
PYTHONPATH=src PATH=$PATH:$RPM_BUILD_ROOT/usr/bin pytest-3 tests/test_*
|
||||
%endif
|
||||
|
||||
|
||||
%post
|
||||
%systemd_post ipa-healthcheck.service
|
||||
|
||||
|
||||
%preun
|
||||
%systemd_preun ipa-healthcheck.service
|
||||
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart ipa-healthcheck.service
|
||||
|
||||
|
||||
%files -f healthcheck.list
|
||||
%{!?_licensedir:%global license %%doc}
|
||||
%license COPYING
|
||||
%doc README.md
|
||||
%{_bindir}/ipa-healthcheck
|
||||
%dir %{_sysconfdir}/ipahealthcheck
|
||||
%dir %{_localstatedir}/log/ipa/healthcheck
|
||||
%config(noreplace) %{_sysconfdir}/ipahealthcheck/ipahealthcheck.conf
|
||||
%config(noreplace) %{_sysconfdir}/logrotate.d/ipahealthcheck
|
||||
%{python3_sitelib}/ipahealthcheck-%{version}-*.egg-info/
|
||||
%{python3_sitelib}/ipahealthcheck-%{version}-*-nspkg.pth
|
||||
%{_unitdir}/*
|
||||
%{_libexecdir}/*
|
||||
%{_mandir}/man8/*
|
||||
%{_mandir}/man5/*
|
||||
|
||||
|
||||
%files -n %{name}-core
|
||||
%{!?_licensedir:%global license %%doc}
|
||||
%license COPYING
|
||||
%doc README.md
|
||||
%{python3_sitelib}/ipahealthcheck/core/
|
||||
|
||||
|
||||
%changelog
|
||||
* Wed Jul 06 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-9
|
||||
- Add support for the DNS URI type (#2104495)
|
||||
|
||||
* Wed May 18 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-8
|
||||
- Validate that a known output type has been selected (#2079698)
|
||||
|
||||
* Wed May 04 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-7
|
||||
- debug='True' in ipahealthcheck.conf doesn't enable debug output (#2079861)
|
||||
- Validate value formats in the ipahealthcheck.conf file (#2079739)
|
||||
- Validate output_type options from ipahealthcheck.conf file (#2079698)
|
||||
|
||||
* Thu Apr 28 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-6
|
||||
- Allow multiple file modes in the FileChecker (#2072708)
|
||||
|
||||
* Wed Apr 06 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-5
|
||||
- Add CLI options to healthcheck configuration file (#2070981)
|
||||
|
||||
* Wed Mar 30 2022 Rob Crittenden <rcritten@redhat.com> - 0.9-4
|
||||
- Use the subject base from the IPA configuration, not REALM (#2067213)
|
||||
|
||||
* Tue Oct 12 2021 Rob Crittenden <rcritten@redhat.com> - 0.9-3
|
||||
- IPATrustControllerServiceCheck doesn't handle HIDDEN_SERVICE (#1976878)
|
||||
|
||||
* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 0.9-2
|
||||
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
|
||||
Related: rhbz#1991688
|
||||
|
||||
* Thu Jun 17 2021 Rob Crittenden <rcritten@redhat.com> - 0.9-1
|
||||
- Rebase to upstream 0.9 (#1969539)
|
||||
|
||||
* Thu Apr 22 2021 Rob Crittenden <rcritten@redhat.com> - 0.8-7.2
|
||||
- rpminspect: specname match on suffix to allow for differing
|
||||
spec/package naming (#1951733)
|
||||
|
||||
* Mon Apr 19 2021 Rob Crittenden <rcritten@redhat.com> - 0.8-7.1
|
||||
- Switch from tox to pytest as the test runner. tox is being deprecated
|
||||
in some distros. (#1942157)
|
||||
|
||||
* Mon Apr 19 2021 Rob Crittenden <rcritten@redhat.com> - 0.8-7
|
||||
- Add check to validate the KRA Agent is correct (#1894781)
|
||||
|
||||
* Thu Apr 15 2021 Mohan Boddu <mboddu@redhat.com> - 0.8-6.1
|
||||
- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
|
||||
|
||||
* Fri Mar 12 2021 Alexander Bokovoy <abokovoy@redhat.com> - 0.8-5.1
|
||||
- Re-enable package self-tests after bootstrap
|
||||
|
||||
* Mon Mar 8 2021 François Cami <fcami@redhat.com> - 0.8-5
|
||||
- Make the spec file distribution-agnostic (rhbz#1935773).
|
||||
|
||||
* Tue Mar 2 2021 Alexander Scheel <ascheel@redhat.com> - 0.8-4
|
||||
- Make the spec file more distribution-agnostic
|
||||
- Use tox as the test runner when tests are enabled
|
||||
|
||||
* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.8-3
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
|
||||
|
||||
* Mon Jan 18 2021 Rob Crittenden <rcritten@redhat.com> - 0.8-2
|
||||
- A bad file group was reported as a python list, not a string
|
||||
|
||||
* Wed Jan 13 2021 Rob Crittenden <rcritten@redhat.com> - 0.8-1
|
||||
- Update to upstream 0.8
|
||||
- Fix FTBFS in F34/rawhide (#1915256)
|
||||
|
||||
* Wed Dec 16 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-3
|
||||
- Include upstream patch to fix parsing input from json files
|
||||
|
||||
* Tue Nov 17 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-2
|
||||
- Include upstream patch to fix collection of AD trust domains
|
||||
- Include upstream patch to fix failing not-valid-after test
|
||||
|
||||
* Thu Oct 29 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-1
|
||||
- Update to upstream 0.7
|
||||
|
||||
* Wed Jul 29 2020 Rob Crittenden <rcritten@redhat.com> - 0.6-4
|
||||
- Set minimum Requires on python3-lib389
|
||||
- Don't assume that all users of healthcheck-core provide the same
|
||||
set of options.
|
||||
|
||||
* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.6-3
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
|
||||
|
||||
* Fri Jul 24 2020 Rob Crittenden <rcritten@redhat.com> - 0.6-2
|
||||
- Don't collect IPA servers in MetaCheck
|
||||
- Skip if dirsrv not available in IPAMetaCheck
|
||||
|
||||
* Wed Jul 1 2020 Rob Crittenden <rcritten@redhat.com> - 0.6-1
|
||||
- Update to upstream 0.6
|
||||
- Don't include cluster checking yet
|
||||
|
||||
* Tue Jun 23 2020 Rob Crittenden <rcritten@redhat.com> - 0.5-5
|
||||
- Add BuildRequires on python3-setuptools
|
||||
|
||||
* Tue May 26 2020 Miro Hrončok <mhroncok@redhat.com> - 0.5-4
|
||||
- Rebuilt for Python 3.9
|
||||
|
||||
* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.5-3
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
|
||||
|
||||
* Mon Jan 27 2020 Rob Crittenden <rcritten@redhat.com> - 0.5-2
|
||||
- Rebuild
|
||||
|
||||
* Thu Jan 2 2020 Rob Crittenden <rcritten@redhat.com> - 0.5-1
|
||||
- Update to upstream 0.5
|
||||
|
||||
* Mon Dec 2 2019 François Cami <fcami@redhat.com> - 0.4-2
|
||||
- Create subpackage to split out core processing (#1771710)
|
||||
|
||||
* Mon Dec 2 2019 François Cami <fcami@redhat.com> - 0.4-1
|
||||
- Update to upstream 0.4
|
||||
- Change Source0 to something "spectool -g" can use.
|
||||
- Correct URL (#1773512)
|
||||
- Errors not translated to strings (#1752849)
|
||||
- JSON output not indented by default (#1729043)
|
||||
- Add dependencies to checks to avoid false-positives (#1727900)
|
||||
- Verify expected DNS records (#1695125
|
||||
|
||||
* Thu Oct 03 2019 Miro Hrončok <mhroncok@redhat.com> - 0.3-3
|
||||
- Rebuilt for Python 3.8.0rc1 (#1748018)
|
||||
|
||||
* Mon Aug 19 2019 Miro Hrončok <mhroncok@redhat.com> - 0.3-2
|
||||
- Rebuilt for Python 3.8
|
||||
|
||||
* Thu Jul 25 2019 François Cami <fcami@redhat.com> - 0.3-1
|
||||
- Update to upstream 0.3
|
||||
- Add logrotate configs + depend on anacron and logrotate
|
||||
|
||||
* Thu Jul 25 2019 François Cami <fcami@redhat.com> - 0.2-6
|
||||
- Fix permissions
|
||||
|
||||
* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.2-5
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
|
||||
|
||||
* Thu Jul 11 2019 François Cami <fcami@redhat.com> - 0.2-4
|
||||
- Fix ipa-healthcheck.sh installation path (rhbz#1729188)
|
||||
- Create and own log directory (rhbz#1729188)
|
||||
|
||||
* Tue Apr 30 2019 François Cami <fcami@redhat.com> - 0.2-3
|
||||
- Add python3-lib389 to BRs
|
||||
|
||||
* Tue Apr 30 2019 François Cami <fcami@redhat.com> - 0.2-2
|
||||
- Fix changelog
|
||||
|
||||
* Thu Apr 25 2019 Rob Crittenden <rcritten@redhat.com> - 0.2-1
|
||||
- Update to upstream 0.2
|
||||
|
||||
* Thu Apr 4 2019 François Cami <fcami@redhat.com> - 0.1-2
|
||||
- Explicitly list dependencies
|
||||
|
||||
* Tue Apr 2 2019 François Cami <fcami@redhat.com> - 0.1-1
|
||||
- Initial package import
|
Loading…
Reference in new issue