freeipa/ipatests/test_integration/test_ipa_cert_fix.py
Rob Crittenden 46ccf006ff ipatests: Remove certmonger tracking before uninstall in cert tests
There is some contention between certmonger starting during the
uninstallation process in order to stop the tracking and activity
going on within certmonger helpers.

As near as I can tell certmonger is not running, then IPA is
stopped in order to uninstall, then certmonger is started to stop
the tracking. certmonger checks cert status on startup but since
IPA isn't running it can't get a host ticket. During this time any
request over DBus may time out, causing a test to fail when we're
just trying to clean up.

https://pagure.io/freeipa/issue/8506

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
2022-02-10 11:55:26 -05:00

525 lines
19 KiB
Python

#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""
Module provides tests for ipa-cert-fix CLI.
"""
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from datetime import datetime, date
import pytest
import time
import logging
from ipaplatform.paths import paths
from ipapython.ipaldap import realm_to_serverid
from ipatests.pytest_ipa.integration import tasks
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration.test_caless import CALessBase, ipa_certs_cleanup
from ipatests.test_integration.test_cert import get_certmonger_fs_id
logger = logging.getLogger(__name__)
def server_install_teardown(func):
def wrapped(*args):
master = args[0].master
try:
func(*args)
finally:
ipa_certs_cleanup(master)
return wrapped
def check_status(host, cert_count, state, timeout=600):
"""Helper method to check that if all the certs are in given state
:param host: the host
:param cert_count: no of cert to look for
:param state: state to check for
:param timeout: max time in seconds to wait for the state
"""
for _i in range(0, timeout, 10):
result = host.run_command(['getcert', 'list'])
count = result.stdout_text.count(f"status: {state}")
logger.info("cert count in %s state : %s", state, count)
if int(count) == cert_count:
break
time.sleep(10)
else:
raise RuntimeError("request timed out")
return count
def needs_resubmit(host, req_id):
"""Helper method to identify if cert request needs to be resubmitted
:param host: the host
:param req_id: request id to perform operation for
Returns True if resubmit needed else False
"""
# check if cert is in monitoring state
tasks.wait_for_certmonger_status(
host, ('MONITORING'), req_id, timeout=600
)
# check if cert is valid and not expired
cmd = host.run_command(
'getcert list -i {} | grep expires'.format(req_id)
)
cert_expiry = cmd.stdout_text.split(' ')
cert_expiry = datetime.strptime(cert_expiry[1], '%Y-%m-%d').date()
if cert_expiry > date.today():
return False
else:
return True
def get_cert_expiry(host, nssdb_path, cert_nick):
"""Method to get cert expiry date of given certificate
:param host: the host
:param nssdb_path: nssdb path of certificate
:param cert_nick: certificate nick name for extracting cert from nssdb
"""
# get initial expiry date to compare later with renewed cert
host.run_command([
'certutil', '-L', '-a',
'-d', nssdb_path,
'-n', cert_nick,
'-o', '/root/cert.pem'
])
data = host.get_file_contents('/root/cert.pem')
cert = x509.load_pem_x509_certificate(data, backend=default_backend())
return cert.not_valid_after
@pytest.fixture
def expire_cert_critical():
"""
Fixture to expire the certs by moving the system date using
date -s command and revert it back
"""
hosts = dict()
def _expire_cert_critical(host, setup_kra=False):
hosts['host'] = host
# Do not install NTP as the test plays with the date
tasks.install_master(host, setup_dns=False,
extra_args=['--no-ntp'])
if setup_kra:
tasks.install_kra(host)
# move date to expire certs
tasks.move_date(host, 'stop', '+3Years+1day')
yield _expire_cert_critical
host = hosts.pop('host')
# Prior to uninstall remove all the cert tracking to prevent
# errors from certmonger trying to check the status of certs
# that don't matter because we are uninstalling.
host.run_command(['systemctl', 'stop', 'certmonger'])
host.run_command(
['rm', '-f', paths.CERTMONGER_REQUESTS_DIR + '/*']
)
tasks.uninstall_master(host)
tasks.move_date(host, 'start', '-3Years-1day')
class TestIpaCertFix(IntegrationTest):
@classmethod
def uninstall(cls, mh):
# Uninstall method is empty as the uninstallation is done in
# the fixture
pass
@pytest.fixture
def expire_ca_cert(self):
tasks.install_master(self.master, setup_dns=False,
extra_args=['--no-ntp'])
tasks.move_date(self.master, 'stop', '+20Years+1day')
yield
tasks.uninstall_master(self.master)
tasks.move_date(self.master, 'start', '-20Years-1day')
def test_missing_csr(self, expire_cert_critical):
"""
Test that ipa-cert-fix succeeds when CSR is missing from CS.cfg
Test case for https://pagure.io/freeipa/issue/8618
Scenario:
- move the date so that ServerCert cert-pki-ca is expired
- remove the ca.sslserver.certreq directive from CS.cfg
- call getcert resubmit in order to create the CSR in certmonger file
- use ipa-cert-fix, no issue should be seen
"""
expire_cert_critical(self.master)
# pki must be stopped in order to edit CS.cfg
self.master.run_command(['ipactl', 'stop'])
self.master.run_command(['sed', '-i', r'/ca\.sslserver\.certreq=/d',
paths.CA_CS_CFG_PATH])
# dirsrv needs to be up in order to run ipa-cert-fix
self.master.run_command(['ipactl', 'start',
'--ignore-service-failures'])
# It's the call to getcert resubmit that creates the CSR in certmonger.
# In normal operations it would be launched automatically when the
# expiration date is near but in the test we force the CSR creation.
self.master.run_command(['getcert', 'resubmit',
'-n', 'Server-Cert cert-pki-ca',
'-d', paths.PKI_TOMCAT_ALIAS_DIR])
# Wait a few secs
time.sleep(3)
# Now the real test, call ipa-cert-fix and ensure it doesn't
# complain about missing sslserver.crt
result = self.master.run_command(['ipa-cert-fix', '-v'],
stdin_text='yes\n',
raiseonerr=False)
msg = ("No such file or directory: "
"'/etc/pki/pki-tomcat/certs/sslserver.crt'")
assert msg not in result.stderr_text
# Because of BZ 1897120, pki-cert-fix fails on pki-core 10.10.0
# https://bugzilla.redhat.com/show_bug.cgi?id=1897120
if (tasks.get_pki_version(self.master)
!= tasks.parse_version('10.10.0')):
assert result.returncode == 0
# get the number of certs track by certmonger
cmd = self.master.run_command(['getcert', 'list'])
certs = cmd.stdout_text.count('Request ID')
timeout = 600
renewed = 0
start = time.time()
# wait up to 10 min for all certs to renew
while time.time() - start < timeout:
cmd = self.master.run_command(['getcert', 'list'])
renewed = cmd.stdout_text.count('status: MONITORING')
if renewed == certs:
break
time.sleep(100)
else:
# timeout
raise AssertionError('Timeout: Failed to renew all the certs')
def test_renew_expired_cert_on_master(self, expire_cert_critical):
"""Test if ipa-cert-fix renews expired certs
Test moves system date to expire certs. Then calls ipa-cert-fix
to renew them. This certs include subsystem, audit-signing,
OCSP signing, Dogtag HTTPS, IPA RA agent, LDAP and KDC certs.
related: https://pagure.io/freeipa/issue/7885
"""
expire_cert_critical(self.master)
# wait for cert expiry
check_status(self.master, 8, "CA_UNREACHABLE")
self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')
check_status(self.master, 9, "MONITORING")
# second iteration of ipa-cert-fix
result = self.master.run_command(
['ipa-cert-fix', '-v'],
stdin_text='yes\n'
)
assert "Nothing to do" in result.stdout_text
check_status(self.master, 9, "MONITORING")
def test_ipa_cert_fix_non_ipa(self):
"""Test ipa-cert-fix doesn't work on non ipa system
ipa-cert-fix tool should not work on non ipa system.
related: https://pagure.io/freeipa/issue/7885
"""
result = self.master.run_command(['ipa-cert-fix', '-v'],
stdin_text='yes\n',
raiseonerr=False)
assert result.returncode == 2
def test_missing_startup(self, expire_cert_critical):
"""
Test ipa-cert-fix fails/warns when startup directive is missing
This test checks that if 'selftests.container.order.startup' directive
is missing from CS.cfg, ipa-cert-fix fails and throw proper error
message. It also checks that underlying command 'pki-server cert-fix'
should fail to renew the cert.
related: https://pagure.io/freeipa/issue/8721
With https://github.com/dogtagpki/pki/pull/3466, it changed to display
a warning than failing.
This test also checks that if 'selftests.container.order.startup'
directive is missing from CS.cfg, ipa-cert-fix dsplay proper warning
(depending on pki version)
related: https://pagure.io/freeipa/issue/8890
"""
expire_cert_critical(self.master)
# pki must be stopped in order to edit CS.cfg
self.master.run_command(['ipactl', 'stop'])
self.master.run_command([
'sed', '-i', r'/selftests\.container\.order\.startup/d',
paths.CA_CS_CFG_PATH
])
# dirsrv needs to be up in order to run ipa-cert-fix
self.master.run_command(['ipactl', 'start',
'--ignore-service-failures'])
result = self.master.run_command(['ipa-cert-fix', '-v'],
stdin_text='yes\n',
raiseonerr=False)
err_msg1 = "ERROR: 'selftests.container.order.startup'"
# check that pki-server cert-fix command fails
err_msg2 = ("ERROR: CalledProcessError(Command "
"['pki-server', 'cert-fix'")
warn_msg = "WARNING: No selftests configured in"
if (tasks.get_pki_version(self.master)
< tasks.parse_version('10.11.0')):
assert (err_msg1 in result.stderr_text
and err_msg2 in result.stderr_text)
else:
assert warn_msg in result.stderr_text
def test_expired_CA_cert(self, expire_ca_cert):
"""Test to check ipa-cert-fix when CA certificate is expired
In order to fix expired certs using ipa-cert-fix, CA cert should be
valid. If CA cert expired, ipa-cert-fix won't work.
related: https://pagure.io/freeipa/issue/8721
"""
result = self.master.run_command(['ipa-cert-fix', '-v'],
stdin_text='yes\n',
raiseonerr=False)
# check that pki-server cert-fix command fails
err_msg = ("ERROR: CalledProcessError(Command "
"['pki-server', 'cert-fix'")
assert err_msg in result.stderr_text
class TestIpaCertFixThirdParty(CALessBase):
"""
Test that ipa-cert-fix works with an installation with custom certs.
"""
@classmethod
def install(cls, mh):
cls.nickname = 'ca1/server'
super(TestIpaCertFixThirdParty, cls).install(mh)
tasks.install_master(cls.master, setup_dns=True)
@server_install_teardown
def test_third_party_certs(self):
self.create_pkcs12(self.nickname,
password=self.cert_password,
filename='server.p12')
self.prepare_cacert('ca1')
# We have a chain length of one. If this is extended then the
# additional cert names will need to be calculated.
nick_chain = self.nickname.split('/')
ca_cert = '%s.crt' % nick_chain[0]
# Add the CA to the IPA store
self.copy_cert(self.master, ca_cert)
self.master.run_command(['ipa-cacert-manage', 'install', ca_cert])
# Apply the new cert chain otherwise ipa-server-certinstall will fail
self.master.run_command(['ipa-certupdate'])
# Install the updated certs and restart the world
self.copy_cert(self.master, 'server.p12')
args = ['ipa-server-certinstall',
'-p', self.master.config.dirman_password,
'--pin', self.master.config.admin_password,
'-d', 'server.p12']
self.master.run_command(args)
self.master.run_command(['ipactl', 'restart'])
# Run ipa-cert-fix. This is basically a no-op but tests that
# the DS nickname is used and not a hardcoded value.
result = self.master.run_command(['ipa-cert-fix', '-v'],)
assert self.nickname in result.stderr_text
class TestCertFixKRA(IntegrationTest):
@classmethod
def uninstall(cls, mh):
# Uninstall method is empty as the uninstallation is done in
# the fixture
pass
def test_renew_expired_cert_with_kra(self, expire_cert_critical):
"""Test if ipa-cert-fix renews expired certs with kra installed
This test check if ipa-cert-fix renews certs with kra
certificate installed.
related: https://pagure.io/freeipa/issue/7885
"""
expire_cert_critical(self.master, setup_kra=True)
# check if all subsystem cert expired
check_status(self.master, 11, "CA_UNREACHABLE")
self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')
check_status(self.master, 12, "MONITORING")
class TestCertFixReplica(IntegrationTest):
num_replicas = 1
@classmethod
def install(cls, mh):
tasks.install_master(
mh.master, setup_dns=False, extra_args=['--no-ntp']
)
tasks.install_replica(
mh.master, mh.replicas[0],
setup_dns=False, extra_args=['--no-ntp']
)
@classmethod
def uninstall(cls, mh):
# Uninstall method is empty as the uninstallation is done in
# the fixture
pass
@pytest.fixture
def expire_certs(self):
# move system date to expire certs
for host in self.master, self.replicas[0]:
tasks.move_date(host, 'stop', '+3years+1days')
yield
# move date back on replica and master
for host in self.replicas[0], self.master:
tasks.uninstall_master(host)
tasks.move_date(host, 'start', '-3years-1days')
def test_renew_expired_cert_replica(self, expire_certs):
"""Test renewal of certificates on replica with ipa-cert-fix
This is to check that ipa-cert-fix renews the certificates
on replica
related: https://pagure.io/freeipa/issue/7885
"""
# wait for cert expiry
check_status(self.master, 8, "CA_UNREACHABLE")
self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')
check_status(self.master, 9, "MONITORING")
# replica operations
# 'Server-Cert cert-pki-ca' cert will be in CA_UNREACHABLE state
cmd = self.replicas[0].run_command(
['getcert', 'list',
'-d', paths.PKI_TOMCAT_ALIAS_DIR,
'-n', 'Server-Cert cert-pki-ca']
)
req_id = get_certmonger_fs_id(cmd.stdout_text)
tasks.wait_for_certmonger_status(
self.replicas[0], ('CA_UNREACHABLE'), req_id, timeout=600
)
# get initial expiry date to compare later with renewed cert
initial_expiry = get_cert_expiry(
self.replicas[0],
paths.PKI_TOMCAT_ALIAS_DIR,
'Server-Cert cert-pki-ca'
)
# check that HTTP,LDAP,PKINIT are renewed and in MONITORING state
instance = realm_to_serverid(self.master.domain.realm)
dirsrv_cert = paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance
for cert in (paths.KDC_CERT, paths.HTTPD_CERT_FILE):
cmd = self.replicas[0].run_command(
['getcert', 'list', '-f', cert]
)
req_id = get_certmonger_fs_id(cmd.stdout_text)
tasks.wait_for_certmonger_status(
self.replicas[0], ('MONITORING'), req_id, timeout=600
)
cmd = self.replicas[0].run_command(
['getcert', 'list', '-d', dirsrv_cert]
)
req_id = get_certmonger_fs_id(cmd.stdout_text)
tasks.wait_for_certmonger_status(
self.replicas[0], ('MONITORING'), req_id, timeout=600
)
# check if replication working fine
testuser = 'testuser1'
password = 'Secret@123'
stdin = (f"{self.master.config.admin_password}\n"
f"{self.master.config.admin_password}\n"
f"{self.master.config.admin_password}\n")
self.master.run_command(['kinit', 'admin'], stdin_text=stdin)
tasks.user_add(self.master, testuser, password=password)
self.replicas[0].run_command(['kinit', 'admin'], stdin_text=stdin)
self.replicas[0].run_command(['ipa', 'user-show', testuser])
# renew shared certificates by resubmitting to certmonger
cmd = self.replicas[0].run_command(
['getcert', 'list', '-f', paths.RA_AGENT_PEM]
)
req_id = get_certmonger_fs_id(cmd.stdout_text)
if needs_resubmit(self.replicas[0], req_id):
self.replicas[0].run_command(
['getcert', 'resubmit', '-i', req_id]
)
tasks.wait_for_certmonger_status(
self.replicas[0], ('MONITORING'), req_id, timeout=600
)
for cert_nick in ('auditSigningCert cert-pki-ca',
'ocspSigningCert cert-pki-ca',
'subsystemCert cert-pki-ca'):
cmd = self.replicas[0].run_command(
['getcert', 'list',
'-d', paths.PKI_TOMCAT_ALIAS_DIR,
'-n', cert_nick]
)
req_id = get_certmonger_fs_id(cmd.stdout_text)
if needs_resubmit(self.replicas[0], req_id):
self.replicas[0].run_command(
['getcert', 'resubmit', '-i', req_id]
)
tasks.wait_for_certmonger_status(
self.replicas[0], ('MONITORING'), req_id, timeout=600
)
self.replicas[0].run_command(
['ipa-cert-fix', '-v'], stdin_text='yes\n'
)
check_status(self.replicas[0], 9, "MONITORING")
# Sometimes certmonger takes time to update the cert status
# So check in nssdb instead of relying on getcert command
renewed_expiry = get_cert_expiry(
self.replicas[0],
paths.PKI_TOMCAT_ALIAS_DIR,
'Server-Cert cert-pki-ca'
)
assert renewed_expiry > initial_expiry