Configures PKI to remove expired certificates and non-resolved requests on a schedule. This is geared towards ACME which can generate a lot of certificates over a short period of time but is general purpose. It lives in ipa-acme-manage because that is the primary reason for including it. Random Serial Numbers v3 must be enabled for this to work. Enabling pruning enables the job scheduler within CS and sets the job user as the IPA RA user which has full rights to certificates and requests. Disabling pruning does not disable the job scheduler because the tool is stateless. Having the scheduler enabled should not be a problem. A restart of PKI is required to apply any changes. This tool forks out to pki-server which does direct writes to CS.cfg. It might be easier to use our own tooling for this but this makes the integration tighter so we pick up any improvements in PKI. The "cron" setting is quite limited, taking only integer values and *. It does not accept ranges, either - or /. No error checking is done in PKI when setting a value, only when attempting to use it, so some rudimentary validation is done. Fixes: https://pagure.io/freeipa/issue/9294 Signed-off-by: Rob Crittenden rcritten@redhat.com Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
791 lines
27 KiB
Python
791 lines
27 KiB
Python
#
|
|
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
|
|
#
|
|
|
|
import time
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography import x509
|
|
import pytest
|
|
|
|
from ipalib.constants import IPA_CA_RECORD
|
|
from ipatests.test_integration.base import IntegrationTest
|
|
from ipatests.pytest_ipa.integration import tasks
|
|
from ipatests.test_integration.test_caless import CALessBase, ipa_certs_cleanup
|
|
from ipatests.test_integration.test_random_serial_numbers import (
|
|
pki_supports_RSNv3
|
|
)
|
|
from ipaplatform.osinfo import osinfo
|
|
from ipaplatform.paths import paths
|
|
from ipatests.test_integration.test_external_ca import (
|
|
install_server_external_ca_step1,
|
|
install_server_external_ca_step2,
|
|
)
|
|
|
|
|
|
IPA_CA = "ipa_ca.crt"
|
|
ROOT_CA = "root_ca.crt"
|
|
|
|
# RHEL does not have certbot. EPEL's version is broken with
|
|
# python-cryptography-2.3; likewise recent PyPI releases.
|
|
# So for now, on RHEL we suppress tests that use certbot.
|
|
skip_certbot_tests = osinfo.id not in ['fedora', 'rhel']
|
|
|
|
# Fedora mod_md package needs some patches before it will work.
|
|
# RHEL version has the patches.
|
|
skip_mod_md_tests = osinfo.id not in ['rhel', 'fedora', ]
|
|
|
|
CERTBOT_DNS_IPA_SCRIPT = '/usr/libexec/ipa/acme/certbot-dns-ipa'
|
|
|
|
|
|
def check_acme_status(host, exp_status, timeout=60):
|
|
"""Helper method to check the status of acme server"""
|
|
for _i in range(0, timeout, 5):
|
|
result = host.run_command(['ipa-acme-manage', 'status'])
|
|
status = result.stdout_text.split(" ")[2].strip()
|
|
print("ACME status: %s" % status)
|
|
if status == exp_status:
|
|
break
|
|
time.sleep(5)
|
|
else:
|
|
raise RuntimeError("request timed out")
|
|
|
|
return status
|
|
|
|
|
|
def get_selinux_status(host):
|
|
"""
|
|
Return the SELinux enforcing status.
|
|
|
|
Return True if enabled and enforcing, otherwise False
|
|
"""
|
|
result = host.run_command(['/usr/sbin/selinuxenabled'], raiseonerr=False)
|
|
if result.returncode != 0:
|
|
return False
|
|
|
|
result = host.run_command(['/usr/sbin/getenforce'], raiseonerr=False)
|
|
if 'Enforcing' in result.stdout_text:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def server_install_teardown(func):
|
|
def wrapped(*args):
|
|
master = args[0].master
|
|
try:
|
|
func(*args)
|
|
finally:
|
|
ipa_certs_cleanup(master)
|
|
return wrapped
|
|
|
|
|
|
def prepare_acme_client(master, client):
|
|
# cache the acme service uri
|
|
acme_host = f'{IPA_CA_RECORD}.{master.domain.name}'
|
|
acme_server = f'https://{acme_host}/acme/directory'
|
|
|
|
# install acme client packages
|
|
if not skip_certbot_tests:
|
|
tasks.install_packages(client, ['certbot'])
|
|
if not skip_mod_md_tests:
|
|
tasks.install_packages(client, ['mod_md'])
|
|
|
|
return acme_server
|
|
|
|
|
|
def certbot_register(host, acme_server):
|
|
"""method to register the host to acme server"""
|
|
# clean up any existing registration and certificates
|
|
host.run_command(
|
|
[
|
|
'rm', '-rf',
|
|
'/etc/letsencrypt/accounts',
|
|
'/etc/letsencrypt/archive',
|
|
'/etc/letsencrypt/csr',
|
|
'/etc/letsencrypt/keys',
|
|
'/etc/letsencrypt/live',
|
|
'/etc/letsencrypt/renewal',
|
|
'/etc/letsencrypt/renewal-hooks'
|
|
]
|
|
)
|
|
# service is enabled; registration should succeed
|
|
host.run_command(
|
|
[
|
|
'certbot',
|
|
'--server', acme_server,
|
|
'register',
|
|
'-m', 'nobody@example.test',
|
|
'--agree-tos',
|
|
'--no-eff-email',
|
|
],
|
|
)
|
|
|
|
|
|
def certbot_standalone_cert(host, acme_server):
|
|
"""method to issue a certbot's certonly standalone cert"""
|
|
# Get a cert from ACME service using HTTP challenge and Certbot's
|
|
# standalone HTTP server mode
|
|
host.run_command(['systemctl', 'stop', 'httpd'])
|
|
host.run_command(
|
|
[
|
|
'certbot',
|
|
'--server', acme_server,
|
|
'certonly',
|
|
'--domain', host.hostname,
|
|
'--standalone',
|
|
'--key-type', 'rsa',
|
|
]
|
|
)
|
|
|
|
|
|
class TestACME(CALessBase):
|
|
"""
|
|
Test the FreeIPA ACME service by using ACME clients on a FreeIPA client.
|
|
|
|
We currently test:
|
|
|
|
* service enable/disable (using Curl)
|
|
* http-01 challenge with Certbot's standalone HTTP server
|
|
* dns-01 challenge with Certbot and FreeIPA DNS via hook scripts
|
|
* revocation with Certbot
|
|
* http-01 challenge with mod_md
|
|
|
|
Tests we should add:
|
|
|
|
* dns-01 challenge with mod_md (see
|
|
https://httpd.apache.org/docs/current/mod/mod_md.html#mdchallengedns01)
|
|
|
|
Things that are not implmented/supported yet, but may be in future:
|
|
|
|
* IP address SAN
|
|
* tls-alpn-01 challenge
|
|
* Other clients or service scenarios
|
|
|
|
"""
|
|
num_replicas = 1
|
|
num_clients = 1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
super(TestACME, cls).install(mh)
|
|
|
|
# install packages before client install in case of IPA DNS problems
|
|
cls.acme_server = prepare_acme_client(cls.master, cls.clients[0])
|
|
|
|
# Each subclass handles its own server installation procedure
|
|
if cls.__name__ != 'TestACME':
|
|
return
|
|
|
|
tasks.install_master(cls.master, setup_dns=True)
|
|
|
|
tasks.install_client(cls.master, cls.clients[0])
|
|
tasks.install_replica(cls.master, cls.replicas[0])
|
|
|
|
def certinstall(self, certfile=None, keyfile=None,
|
|
pin=None):
|
|
"""Small wrapper around ipa-server-certinstall
|
|
|
|
We are always replacing only the web server with a fixed
|
|
pre-generated value and returning the result for the caller
|
|
to figure out.
|
|
"""
|
|
self.create_pkcs12('ca1/server', password=None, filename='server.p12')
|
|
self.copy_cert(self.master, 'server.p12')
|
|
if pin is None:
|
|
pin = self.cert_password
|
|
args = ['ipa-server-certinstall',
|
|
'-p', self.master.config.dirman_password,
|
|
'--pin', pin,
|
|
'-w']
|
|
if certfile: # implies keyfile
|
|
args.append(certfile)
|
|
args.append(keyfile)
|
|
else:
|
|
args.append('server.p12')
|
|
|
|
return self.master.run_command(args,
|
|
raiseonerr=False)
|
|
|
|
#######
|
|
# kinit
|
|
#######
|
|
|
|
def test_kinit_master(self):
|
|
# Some tests require executing ipa commands, e.g. to
|
|
# check revocation status or add/remove DNS entries.
|
|
# Preemptively kinit as admin on the master.
|
|
tasks.kinit_admin(self.master)
|
|
|
|
#####################
|
|
# Enable ACME service
|
|
#####################
|
|
|
|
def test_acme_service_not_yet_enabled(self):
|
|
# --fail makes curl exit code 22 when response status >= 400.
|
|
# ACME service should return 503 because it was not enabled yet.
|
|
self.clients[0].run_command(
|
|
['curl', '--fail', self.acme_server],
|
|
ok_returncode=22,
|
|
)
|
|
result = self.master.run_command(['ipa-acme-manage', 'status'])
|
|
assert 'disabled' in result.stdout_text
|
|
|
|
def test_enable_acme_service(self):
|
|
self.master.run_command(['ipa-acme-manage', 'enable'])
|
|
|
|
# wait a short time for Dogtag ACME service to observe config
|
|
# change and reconfigure itself to service requests
|
|
exc = None
|
|
for _i in range(5):
|
|
time.sleep(2)
|
|
try:
|
|
self.clients[0].run_command(
|
|
['curl', '--fail', self.acme_server])
|
|
break
|
|
except Exception as e:
|
|
exc = e
|
|
else:
|
|
raise exc
|
|
|
|
def test_centralize_acme_enable(self):
|
|
"""Test if ACME enable on replica if enabled on master"""
|
|
status = check_acme_status(self.replicas[0], 'enabled')
|
|
assert status == 'enabled'
|
|
|
|
###############
|
|
# Certbot tests
|
|
###############
|
|
|
|
@pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
|
|
def test_certbot_register(self):
|
|
certbot_register(self.clients[0], self.acme_server)
|
|
|
|
@pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
|
|
def test_certbot_certonly_standalone(self):
|
|
certbot_standalone_cert(self.clients[0], self.acme_server)
|
|
|
|
@pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
|
|
def test_certbot_revoke(self):
|
|
# Assume previous certonly operation succeeded.
|
|
# Read certificate to learn serial number.
|
|
cert_path = \
|
|
f'/etc/letsencrypt/live/{self.clients[0].hostname}/cert.pem'
|
|
data = self.clients[0].get_file_contents(cert_path)
|
|
cert = x509.load_pem_x509_certificate(data, backend=default_backend())
|
|
|
|
# revoke cert via ACME
|
|
self.clients[0].run_command(
|
|
[
|
|
'certbot',
|
|
'--server', self.acme_server,
|
|
'revoke',
|
|
'--cert-name', self.clients[0].hostname,
|
|
'--delete-after-revoke',
|
|
],
|
|
)
|
|
|
|
# check cert is revoked (kinit already performed)
|
|
result = self.master.run_command(
|
|
['ipa', 'cert-show', str(cert.serial_number), '--raw']
|
|
)
|
|
assert 'revocation_reason:' in result.stdout_text
|
|
|
|
@pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
|
|
def test_certbot_dns(self):
|
|
# Assume previous revoke operation succeeded and cert was deleted.
|
|
# We can now request a new certificate.
|
|
|
|
# Get a cert from ACME service using dns-01 challenge and Certbot's
|
|
# standalone HTTP server mode
|
|
self.clients[0].run_command([
|
|
'certbot',
|
|
'--server', self.acme_server,
|
|
'certonly',
|
|
'--non-interactive',
|
|
'--domain', self.clients[0].hostname,
|
|
'--preferred-challenges', 'dns',
|
|
'--manual',
|
|
'--manual-public-ip-logging-ok',
|
|
'--manual-auth-hook', CERTBOT_DNS_IPA_SCRIPT,
|
|
'--manual-cleanup-hook', CERTBOT_DNS_IPA_SCRIPT,
|
|
'--key-type', 'rsa',
|
|
])
|
|
|
|
##############
|
|
# mod_md tests
|
|
##############
|
|
|
|
@pytest.mark.skipif(skip_mod_md_tests, reason='mod_md not available')
|
|
def test_mod_md(self):
|
|
if get_selinux_status(self.clients[0]):
|
|
# mod_md requires its own SELinux policy to grant perms to
|
|
# maintaining ACME registration and cert state.
|
|
raise pytest.skip("SELinux is enabled, this will fail")
|
|
# write config
|
|
self.clients[0].run_command(['mkdir', '-p', '/etc/httpd/conf.d'])
|
|
self.clients[0].run_command(['mkdir', '-p', '/etc/httpd/md'])
|
|
self.clients[0].put_file_contents(
|
|
'/etc/httpd/conf.d/md.conf',
|
|
'\n'.join([
|
|
f'MDCertificateAuthority {self.acme_server}',
|
|
'MDCertificateAgreement accepted',
|
|
'MDStoreDir /etc/httpd/md',
|
|
f'MDomain {self.clients[0].hostname}',
|
|
'<VirtualHost *:443>',
|
|
f' ServerName {self.clients[0].hostname}',
|
|
' SSLEngine on',
|
|
'</VirtualHost>\n',
|
|
]),
|
|
)
|
|
|
|
# To check for successful cert issuance means knowing how mod_md
|
|
# stores certificates, or looking for specific log messages.
|
|
# If the thing we are inspecting changes, the test will break.
|
|
# So I prefer a conservative sleep.
|
|
#
|
|
self.clients[0].run_command(['systemctl', 'restart', 'httpd'])
|
|
time.sleep(15)
|
|
|
|
# We expect mod_md has acquired the certificate by now.
|
|
# Perform a graceful restart to begin using the cert.
|
|
# (If mod_md ever learns to start using newly acquired
|
|
# certificates /without/ the second restart, then both
|
|
# of these sleeps can be replaced by "loop until good".)
|
|
#
|
|
self.clients[0].run_command(['systemctl', 'reload', 'httpd'])
|
|
time.sleep(3)
|
|
|
|
# HTTPS request from server to client (should succeed)
|
|
self.master.run_command(
|
|
['curl', f'https://{self.clients[0].hostname}'])
|
|
|
|
# clean-up
|
|
self.clients[0].run_command(['rm', '-rf', '/etc/httpd/md'])
|
|
self.clients[0].run_command(['rm', '-f', '/etc/httpd/conf.d/md.conf'])
|
|
|
|
######################
|
|
# Disable ACME service
|
|
######################
|
|
|
|
def test_disable_acme_service(self):
|
|
"""
|
|
Disable ACME service again, and observe that it no longer services
|
|
requests.
|
|
|
|
"""
|
|
self.master.run_command(['ipa-acme-manage', 'disable'])
|
|
|
|
# wait a short time for Dogtag ACME service to observe config
|
|
# change and reconfigure itself to no longer service requests
|
|
time.sleep(3)
|
|
|
|
# should fail now
|
|
self.clients[0].run_command(
|
|
['curl', '--fail', self.acme_server],
|
|
ok_returncode=22,
|
|
)
|
|
|
|
def test_centralize_acme_disable(self):
|
|
"""Test if ACME disable on replica if disabled on master"""
|
|
status = check_acme_status(self.replicas[0], 'disabled')
|
|
assert status == 'disabled'
|
|
|
|
def test_acme_pruning_no_random_serial(self):
|
|
"""This ACME install is configured without random serial
|
|
numbers. Verify that we can't enable pruning on it."""
|
|
self.master.run_command(['ipa-acme-manage', 'enable'])
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning', '--enable'],
|
|
raiseonerr=False)
|
|
assert result.returncode == 1
|
|
assert "requires random serial numbers" in result.stderr_text
|
|
|
|
@server_install_teardown
|
|
def test_third_party_certs(self):
|
|
"""Require ipa-ca SAN on replacement web certificates"""
|
|
|
|
self.master.run_command(['ipa-acme-manage', 'enable'])
|
|
|
|
self.create_pkcs12('ca1/server')
|
|
self.prepare_cacert('ca1')
|
|
|
|
# Re-install the existing Apache certificate that has a SAN to
|
|
# verify that it will be accepted.
|
|
pin = self.master.get_file_contents(
|
|
paths.HTTPD_PASSWD_FILE_FMT.format(host=self.master.hostname)
|
|
)
|
|
result = self.certinstall(
|
|
certfile=paths.HTTPD_CERT_FILE,
|
|
keyfile=paths.HTTPD_KEY_FILE,
|
|
pin=pin
|
|
)
|
|
assert result.returncode == 0
|
|
|
|
# Install using a 3rd party cert with a missing SAN for ipa-ca
|
|
# which should be rejected.
|
|
result = self.certinstall()
|
|
assert result.returncode == 1
|
|
|
|
self.master.run_command(['ipa-acme-manage', 'disable'])
|
|
|
|
# Install using a 3rd party cert with a missing SAN for ipa-ca
|
|
# which should be ok since ACME is disabled.
|
|
result = self.certinstall()
|
|
assert result.returncode == 0
|
|
|
|
# Enable ACME which should fail since the Apache cert lacks the SAN
|
|
result = self.master.run_command(['ipa-acme-manage', 'enable'],
|
|
raiseonerr=False)
|
|
assert result.returncode == 1
|
|
assert "invalid 'certificate'" in result.stderr_text
|
|
|
|
|
|
class TestACMECALess(IntegrationTest):
|
|
"""Test to check the CA less replica setup"""
|
|
num_replicas = 1
|
|
num_clients = 0
|
|
|
|
@pytest.fixture
|
|
def test_setup_teardown(self):
|
|
tasks.install_master(self.master, setup_dns=True)
|
|
|
|
tasks.install_replica(self.master, self.replicas[0], setup_ca=False)
|
|
|
|
yield
|
|
|
|
tasks.uninstall_replica(self.master, self.replicas[0])
|
|
tasks.uninstall_master(self.master)
|
|
|
|
def test_caless_to_cafull_replica(self, test_setup_teardown):
|
|
"""Test ACME is enabled on CA-less replica when converted to CA-full
|
|
|
|
Deployment where one server is deployed as CA-less, when converted
|
|
to CA full, should have ACME enabled by default.
|
|
|
|
related: https://pagure.io/freeipa/issue/8524
|
|
"""
|
|
tasks.kinit_admin(self.master)
|
|
# enable acme on master
|
|
self.master.run_command(['ipa-acme-manage', 'enable'])
|
|
|
|
# check status of acme server on master
|
|
status = check_acme_status(self.master, 'enabled')
|
|
assert status == 'enabled'
|
|
|
|
tasks.kinit_admin(self.replicas[0])
|
|
# check status of acme on replica, result: CA is not installed
|
|
result = self.replicas[0].run_command(['ipa-acme-manage', 'status'],
|
|
raiseonerr=False)
|
|
assert result.returncode == 3
|
|
|
|
# Install CA on replica
|
|
tasks.install_ca(self.replicas[0])
|
|
|
|
# check acme status, should be enabled now
|
|
status = check_acme_status(self.replicas[0], 'enabled')
|
|
assert status == 'enabled'
|
|
|
|
# disable acme on replica
|
|
self.replicas[0].run_command(['ipa-acme-manage', 'disable'])
|
|
|
|
# check acme status on master, should be disabled
|
|
status = check_acme_status(self.master, 'disabled')
|
|
assert status == 'disabled'
|
|
|
|
def test_enable_caless_to_cafull_replica(self, test_setup_teardown):
|
|
"""Test ACME with CA-less replica when converted to CA-full
|
|
|
|
Deployment have one ca-less replica and ACME is not enabled.
|
|
After converting ca-less replica to ca-full, ACME can be
|
|
enabled or disabled.
|
|
|
|
related: https://pagure.io/freeipa/issue/8524
|
|
"""
|
|
tasks.kinit_admin(self.master)
|
|
|
|
# check status of acme server on master
|
|
status = check_acme_status(self.master, 'disabled')
|
|
assert status == 'disabled'
|
|
|
|
tasks.kinit_admin(self.replicas[0])
|
|
# check status of acme on replica, result: CA is not installed
|
|
result = self.replicas[0].run_command(['ipa-acme-manage', 'status'],
|
|
raiseonerr=False)
|
|
assert result.returncode == 3
|
|
|
|
# Install CA on replica
|
|
tasks.install_ca(self.replicas[0])
|
|
|
|
# check acme status on replica, should not throw error
|
|
status = check_acme_status(self.replicas[0], 'disabled')
|
|
assert status == 'disabled'
|
|
|
|
# enable acme on replica
|
|
self.replicas[0].run_command(['ipa-acme-manage', 'enable'])
|
|
|
|
# check acme status on master
|
|
status = check_acme_status(self.master, 'enabled')
|
|
assert status == 'enabled'
|
|
|
|
# check acme status on replica
|
|
status = check_acme_status(self.replicas[0], 'enabled')
|
|
assert status == 'enabled'
|
|
|
|
# disable acme on master
|
|
self.master.run_command(['ipa-acme-manage', 'disable'])
|
|
|
|
# check acme status on replica, should be disabled
|
|
status = check_acme_status(self.replicas[0], 'disabled')
|
|
assert status == 'disabled'
|
|
|
|
|
|
class TestACMEwithExternalCA(TestACME):
|
|
"""Test the FreeIPA ACME service with external CA"""
|
|
|
|
num_replicas = 1
|
|
num_clients = 1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
super(TestACMEwithExternalCA, cls).install(mh)
|
|
|
|
# install master with external-ca
|
|
result = install_server_external_ca_step1(cls.master)
|
|
assert result.returncode == 0
|
|
root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport(
|
|
cls.master, paths.ROOT_IPA_CSR, ROOT_CA, IPA_CA
|
|
)
|
|
|
|
install_server_external_ca_step2(
|
|
cls.master, ipa_ca_fname, root_ca_fname
|
|
)
|
|
tasks.kinit_admin(cls.master)
|
|
|
|
tasks.install_client(cls.master, cls.clients[0])
|
|
tasks.install_replica(cls.master, cls.replicas[0])
|
|
|
|
|
|
class TestACMERenew(IntegrationTest):
|
|
|
|
num_clients = 1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
|
|
# install packages before client install in case of IPA DNS problems
|
|
cls.acme_server = prepare_acme_client(cls.master, cls.clients[0])
|
|
|
|
tasks.install_master(cls.master, setup_dns=True)
|
|
tasks.install_client(cls.master, cls.clients[0])
|
|
|
|
@pytest.fixture
|
|
def issue_and_expire_cert(self):
|
|
"""Fixture to expire cert by moving date past expiry of acme cert"""
|
|
# enable the ACME service on master
|
|
self.master.run_command(['ipa-acme-manage', 'enable'])
|
|
|
|
# register the account with certbot
|
|
certbot_register(self.clients[0], self.acme_server)
|
|
|
|
# request a standalone acme cert
|
|
certbot_standalone_cert(self.clients[0], self.acme_server)
|
|
|
|
# move system date to expire acme cert
|
|
for host in self.clients[0], self.master:
|
|
tasks.kdestroy_all(host)
|
|
tasks.move_date(host, 'stop', '+90days')
|
|
|
|
tasks.get_kdcinfo(host)
|
|
# Note raiseonerr=False:
|
|
# the assert is located after kdcinfo retrieval.
|
|
result = host.run_command(
|
|
"KRB5_TRACE=/dev/stdout kinit admin",
|
|
stdin_text='{0}\n{0}\n{0}\n'.format(
|
|
self.clients[0].config.admin_password
|
|
),
|
|
raiseonerr=False
|
|
)
|
|
# Retrieve kdc.$REALM after the password change, just in case SSSD
|
|
# domain status flipped to online during the password change.
|
|
tasks.get_kdcinfo(host)
|
|
assert result.returncode == 0
|
|
|
|
yield
|
|
|
|
# move back date
|
|
for host in self.clients[0], self.master:
|
|
tasks.kdestroy_all(host)
|
|
tasks.move_date(host, 'start', '-90days')
|
|
tasks.kinit_admin(host)
|
|
|
|
@pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
|
|
def test_renew(self, issue_and_expire_cert):
|
|
"""Test if ACME renews the issued cert with cerbot
|
|
|
|
This test is to check if ACME certificate renews upon
|
|
reaching expiry
|
|
|
|
related: https://pagure.io/freeipa/issue/4751
|
|
"""
|
|
data = self.clients[0].get_file_contents(
|
|
f'/etc/letsencrypt/live/{self.clients[0].hostname}/cert.pem'
|
|
)
|
|
cert = x509.load_pem_x509_certificate(data, backend=default_backend())
|
|
initial_expiry = cert.not_valid_after
|
|
|
|
self.clients[0].run_command(['certbot', 'renew'])
|
|
|
|
data = self.clients[0].get_file_contents(
|
|
f'/etc/letsencrypt/live/{self.clients[0].hostname}/cert.pem'
|
|
)
|
|
cert = x509.load_pem_x509_certificate(data, backend=default_backend())
|
|
renewed_expiry = cert.not_valid_after
|
|
|
|
assert initial_expiry != renewed_expiry
|
|
|
|
|
|
class TestACMEPrune(IntegrationTest):
|
|
"""Validate that ipa-acme-manage configures dogtag for pruning"""
|
|
|
|
random_serial = True
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
if not pki_supports_RSNv3(mh.master):
|
|
raise pytest.skip("RNSv3 not supported")
|
|
tasks.install_master(cls.master, setup_dns=True,
|
|
random_serial=True)
|
|
|
|
@classmethod
|
|
def uninstall(cls, mh):
|
|
if not pki_supports_RSNv3(mh.master):
|
|
raise pytest.skip("RSNv3 not supported")
|
|
super(TestACMEPrune, cls).uninstall(mh)
|
|
|
|
def test_enable_pruning(self):
|
|
if (tasks.get_pki_version(self.master)
|
|
< tasks.parse_version('11.3.0')):
|
|
raise pytest.skip("Certificate pruning is not available")
|
|
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
|
|
assert "jobsScheduler.job.pruning.enabled=false".encode() in cs_cfg
|
|
|
|
self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
|
|
|
|
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
|
|
assert "jobsScheduler.enabled=true".encode() in cs_cfg
|
|
assert "jobsScheduler.job.pruning.enabled=true".encode() in cs_cfg
|
|
assert "jobsScheduler.job.pruning.owner=ipara".encode() in cs_cfg
|
|
|
|
def test_pruning_options(self):
|
|
if (tasks.get_pki_version(self.master)
|
|
< tasks.parse_version('11.3.0')):
|
|
raise pytest.skip("Certificate pruning is not available")
|
|
|
|
self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--certretention=60',
|
|
'--certretentionunit=minute',
|
|
'--certsearchsizelimit=2000',
|
|
'--certsearchtimelimit=5',]
|
|
)
|
|
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
|
|
assert (
|
|
"jobsScheduler.job.pruning.certRetentionTime=60".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.certRetentionUnit=minute".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.certSearchSizeLimit=2000".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.certSearchTimeLimit=5".encode()
|
|
in cs_cfg
|
|
)
|
|
|
|
self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--requestretention=60',
|
|
'--requestretentionunit=minute',
|
|
'--requestresearchsizelimit=2000',
|
|
'--requestsearchtimelimit=5',]
|
|
)
|
|
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
|
|
assert (
|
|
"jobsScheduler.job.pruning.requestRetentionTime=60".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.requestRetentionUnit=minute".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.requestSearchSizeLimit=2000".encode()
|
|
in cs_cfg
|
|
)
|
|
assert (
|
|
"jobsScheduler.job.pruning.requestSearchTimeLimit=5".encode()
|
|
in cs_cfg
|
|
)
|
|
|
|
self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--cron="0 23 1 * *',]
|
|
)
|
|
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
|
|
assert (
|
|
"jobsScheduler.job.pruning.cron=0 23 1 * *".encode()
|
|
in cs_cfg
|
|
)
|
|
|
|
def test_pruning_negative_options(self):
|
|
"""Negative option testing for things we directly cover"""
|
|
if (tasks.get_pki_version(self.master)
|
|
< tasks.parse_version('11.3.0')):
|
|
raise pytest.skip("Certificate pruning is not available")
|
|
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--enable', '--disable'],
|
|
raiseonerr=False
|
|
)
|
|
assert result.returncode == 1
|
|
assert "Cannot both enable and disable" in result.stderr_text
|
|
|
|
for cmd in ('--config-show', '--run'):
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
cmd, '--enable'],
|
|
raiseonerr=False
|
|
)
|
|
assert result.returncode == 1
|
|
assert "Cannot change and show config" in result.stderr_text
|
|
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--cron="* *"'],
|
|
raiseonerr=False
|
|
)
|
|
assert result.returncode == 1
|
|
assert "Invalid format format --cron" in result.stderr_text
|
|
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--cron="100 * * * *"'],
|
|
raiseonerr=False
|
|
)
|
|
assert result.returncode == 1
|
|
assert "100 not within the range 0-59" in result.stderr_text
|
|
|
|
result = self.master.run_command(
|
|
['ipa-acme-manage', 'pruning',
|
|
'--cron="10 1-5 * * *"'],
|
|
raiseonerr=False
|
|
)
|
|
assert result.returncode == 1
|
|
assert "1-5 ranges are not supported" in result.stderr_text
|