Add check to validate KRA Agent LDAP entry is correct

Resolves: #1947043
This commit is contained in:
Rob Crittenden 2021-04-12 13:21:21 -04:00
parent d293b70fc2
commit fc47662be3
3 changed files with 449 additions and 1 deletions

View 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

View 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

View File

@ -17,7 +17,7 @@
Name: %{prefix}-healthcheck
Version: 0.8
Release: 6.1%{?dist}
Release: 7%{?dist}
Summary: Health check tool for %{productname}
BuildArch: noarch
License: GPLv3
@ -28,6 +28,8 @@ Source1: ipahealthcheck.conf
Patch0001: 0001-Remove-ipaclustercheck.patch
Patch0002: 0002-Handle-slight-different-in-exception-output-in-Pytho.patch
Patch0003: 0003-Failed-expected-group-should-be-a-string-not-a-list.patch
Patch0004: 0004-Add-check-for-IPA-KRA-Agent.patch
Patch0005: 0005-Add-tests-for-KRA-Agent-validation.patch
Requires: %{name}-core = %{version}-%{release}
Requires: %{prefix}-server
@ -158,6 +160,9 @@ tox -epy3
%changelog
* Tue Apr 6 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