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