0fcb1e
From 0f77b359e241fc4055fb8d785e18f96338451ebf Mon Sep 17 00:00:00 2001
0fcb1e
From: Mohammad Rizwan <myusuf@redhat.com>
0fcb1e
Date: Mon, 6 Feb 2023 15:31:27 +0530
0fcb1e
Subject: [PATCH] ipatests: tests for certificate pruning
0fcb1e
0fcb1e
1. Test to prune the expired certificate by manual run
0fcb1e
2. Test to prune expired certificate by cron job
0fcb1e
3. Test to prune expired certificate with retention unit option
0fcb1e
4. Test to prune expired certificate with search size limit option
0fcb1e
5. Test to check config-show command shows set param
0fcb1e
6. Test prune command shows proper status after disabling the pruning
0fcb1e
0fcb1e
related: https://pagure.io/freeipa/issue/9294
0fcb1e
0fcb1e
Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
0fcb1e
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
0fcb1e
---
0fcb1e
 ipatests/test_integration/test_acme.py | 306 +++++++++++++++++++++----
0fcb1e
 1 file changed, 260 insertions(+), 46 deletions(-)
0fcb1e
0fcb1e
diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py
0fcb1e
index 5ceba05976059de69414a79634d98045c3ab68bb..1334be52f4530dd8b2a4207744146cd0eb5477a3 100644
0fcb1e
--- a/ipatests/test_integration/test_acme.py
0fcb1e
+++ b/ipatests/test_integration/test_acme.py
0fcb1e
@@ -122,21 +122,23 @@ def certbot_register(host, acme_server):
0fcb1e
     )
0fcb1e
 
0fcb1e
 
0fcb1e
-def certbot_standalone_cert(host, acme_server):
0fcb1e
+def certbot_standalone_cert(host, acme_server, no_of_cert=1):
0fcb1e
     """method to issue a certbot's certonly standalone cert"""
0fcb1e
     # Get a cert from ACME service using HTTP challenge and Certbot's
0fcb1e
     # standalone HTTP server mode
0fcb1e
     host.run_command(['systemctl', 'stop', 'httpd'])
0fcb1e
-    host.run_command(
0fcb1e
-        [
0fcb1e
-            'certbot',
0fcb1e
-            '--server', acme_server,
0fcb1e
-            'certonly',
0fcb1e
-            '--domain', host.hostname,
0fcb1e
-            '--standalone',
0fcb1e
-            '--key-type', 'rsa',
0fcb1e
-        ]
0fcb1e
-    )
0fcb1e
+    for _i in range(0, no_of_cert):
0fcb1e
+        host.run_command(
0fcb1e
+            [
0fcb1e
+                'certbot',
0fcb1e
+                '--server', acme_server,
0fcb1e
+                'certonly',
0fcb1e
+                '--domain', host.hostname,
0fcb1e
+                '--standalone',
0fcb1e
+                '--key-type', 'rsa',
0fcb1e
+                '--force-renewal'
0fcb1e
+            ]
0fcb1e
+        )
0fcb1e
 
0fcb1e
 
0fcb1e
 class TestACME(CALessBase):
0fcb1e
@@ -573,43 +575,41 @@ class TestACMEwithExternalCA(TestACME):
0fcb1e
         tasks.install_replica(cls.master, cls.replicas[0])
0fcb1e
 
0fcb1e
 
0fcb1e
-class TestACMERenew(IntegrationTest):
0fcb1e
-
0fcb1e
-    num_clients = 1
0fcb1e
+@pytest.fixture
0fcb1e
+def issue_and_expire_acme_cert():
0fcb1e
+    """Fixture to expire cert by moving date past expiry of acme cert"""
0fcb1e
+    hosts = []
0fcb1e
 
0fcb1e
-    @classmethod
0fcb1e
-    def install(cls, mh):
0fcb1e
-
0fcb1e
-        # install packages before client install in case of IPA DNS problems
0fcb1e
-        cls.acme_server = prepare_acme_client(cls.master, cls.clients[0])
0fcb1e
+    def _issue_and_expire_acme_cert(
0fcb1e
+        master, client,
0fcb1e
+        acme_server_url, no_of_cert=1
0fcb1e
+    ):
0fcb1e
 
0fcb1e
-        tasks.install_master(cls.master, setup_dns=True)
0fcb1e
-        tasks.install_client(cls.master, cls.clients[0])
0fcb1e
+        hosts.append(master)
0fcb1e
+        hosts.append(client)
0fcb1e
 
0fcb1e
-    @pytest.fixture
0fcb1e
-    def issue_and_expire_cert(self):
0fcb1e
-        """Fixture to expire cert by moving date past expiry of acme cert"""
0fcb1e
         # enable the ACME service on master
0fcb1e
-        self.master.run_command(['ipa-acme-manage', 'enable'])
0fcb1e
+        master.run_command(['ipa-acme-manage', 'enable'])
0fcb1e
 
0fcb1e
         # register the account with certbot
0fcb1e
-        certbot_register(self.clients[0], self.acme_server)
0fcb1e
+        certbot_register(client, acme_server_url)
0fcb1e
 
0fcb1e
         # request a standalone acme cert
0fcb1e
-        certbot_standalone_cert(self.clients[0], self.acme_server)
0fcb1e
+        certbot_standalone_cert(client, acme_server_url, no_of_cert)
0fcb1e
 
0fcb1e
         # move system date to expire acme cert
0fcb1e
-        for host in self.clients[0], self.master:
0fcb1e
+        for host in hosts:
0fcb1e
             tasks.kdestroy_all(host)
0fcb1e
             tasks.move_date(host, 'stop', '+90days')
0fcb1e
 
0fcb1e
+        time.sleep(10)
0fcb1e
         tasks.get_kdcinfo(host)
0fcb1e
         # Note raiseonerr=False:
0fcb1e
         # the assert is located after kdcinfo retrieval.
0fcb1e
-        result = host.run_command(
0fcb1e
+        result = master.run_command(
0fcb1e
             "KRB5_TRACE=/dev/stdout kinit admin",
0fcb1e
             stdin_text='{0}\n{0}\n{0}\n'.format(
0fcb1e
-                self.clients[0].config.admin_password
0fcb1e
+                master.config.admin_password
0fcb1e
             ),
0fcb1e
             raiseonerr=False
0fcb1e
         )
0fcb1e
@@ -618,16 +618,28 @@ class TestACMERenew(IntegrationTest):
0fcb1e
         tasks.get_kdcinfo(host)
0fcb1e
         assert result.returncode == 0
0fcb1e
 
0fcb1e
-        yield
0fcb1e
+    yield _issue_and_expire_acme_cert
0fcb1e
 
0fcb1e
-        # move back date
0fcb1e
-        for host in self.clients[0], self.master:
0fcb1e
-            tasks.kdestroy_all(host)
0fcb1e
-            tasks.move_date(host, 'start', '-90days')
0fcb1e
-            tasks.kinit_admin(host)
0fcb1e
+    # move back date
0fcb1e
+    for host in hosts:
0fcb1e
+        tasks.move_date(host, 'start', '-90days')
0fcb1e
+
0fcb1e
+
0fcb1e
+class TestACMERenew(IntegrationTest):
0fcb1e
+
0fcb1e
+    num_clients = 1
0fcb1e
+
0fcb1e
+    @classmethod
0fcb1e
+    def install(cls, mh):
0fcb1e
+
0fcb1e
+        # install packages before client install in case of IPA DNS problems
0fcb1e
+        cls.acme_server = prepare_acme_client(cls.master, cls.clients[0])
0fcb1e
+
0fcb1e
+        tasks.install_master(cls.master, setup_dns=True)
0fcb1e
+        tasks.install_client(cls.master, cls.clients[0])
0fcb1e
 
0fcb1e
     @pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
0fcb1e
-    def test_renew(self, issue_and_expire_cert):
0fcb1e
+    def test_renew(self, issue_and_expire_acme_cert):
0fcb1e
         """Test if ACME renews the issued cert with cerbot
0fcb1e
 
0fcb1e
         This test is to check if ACME certificate renews upon
0fcb1e
@@ -635,6 +647,8 @@ class TestACMERenew(IntegrationTest):
0fcb1e
 
0fcb1e
         related: https://pagure.io/freeipa/issue/4751
0fcb1e
         """
0fcb1e
+        issue_and_expire_acme_cert(
0fcb1e
+            self.master, self.clients[0], self.acme_server)
0fcb1e
         data = self.clients[0].get_file_contents(
0fcb1e
             f'/etc/letsencrypt/live/{self.clients[0].hostname}/cert.pem'
0fcb1e
         )
0fcb1e
@@ -656,6 +670,7 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
     """Validate that ipa-acme-manage configures dogtag for pruning"""
0fcb1e
 
0fcb1e
     random_serial = True
0fcb1e
+    num_clients = 1
0fcb1e
 
0fcb1e
     @classmethod
0fcb1e
     def install(cls, mh):
0fcb1e
@@ -663,6 +678,8 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
             raise pytest.skip("RNSv3 not supported")
0fcb1e
         tasks.install_master(cls.master, setup_dns=True,
0fcb1e
                              random_serial=True)
0fcb1e
+        cls.acme_server = prepare_acme_client(cls.master, cls.clients[0])
0fcb1e
+        tasks.install_client(cls.master, cls.clients[0])
0fcb1e
 
0fcb1e
     @classmethod
0fcb1e
     def uninstall(cls, mh):
0fcb1e
@@ -718,7 +735,7 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
             ['ipa-acme-manage', 'pruning',
0fcb1e
              '--requestretention=60',
0fcb1e
              '--requestretentionunit=minute',
0fcb1e
-             '--requestresearchsizelimit=2000',
0fcb1e
+             '--requestsearchsizelimit=2000',
0fcb1e
              '--requestsearchtimelimit=5',]
0fcb1e
         )
0fcb1e
         cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
0fcb1e
@@ -741,7 +758,7 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
 
0fcb1e
         self.master.run_command(
0fcb1e
             ['ipa-acme-manage', 'pruning',
0fcb1e
-             '--cron="0 23 1 * *',]
0fcb1e
+             '--cron=0 23 1 * *',]
0fcb1e
         )
0fcb1e
         cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
0fcb1e
         assert (
0fcb1e
@@ -760,7 +777,7 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
              '--enable', '--disable'],
0fcb1e
             raiseonerr=False
0fcb1e
         )
0fcb1e
-        assert result.returncode == 1
0fcb1e
+        assert result.returncode == 2
0fcb1e
         assert "Cannot both enable and disable" in result.stderr_text
0fcb1e
 
0fcb1e
         for cmd in ('--config-show', '--run'):
0fcb1e
@@ -769,20 +786,20 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
                  cmd, '--enable'],
0fcb1e
                 raiseonerr=False
0fcb1e
             )
0fcb1e
-            assert result.returncode == 1
0fcb1e
+            assert result.returncode == 2
0fcb1e
             assert "Cannot change and show config" in result.stderr_text
0fcb1e
 
0fcb1e
         result = self.master.run_command(
0fcb1e
             ['ipa-acme-manage', 'pruning',
0fcb1e
-             '--cron="* *"'],
0fcb1e
+             '--cron=* *'],
0fcb1e
             raiseonerr=False
0fcb1e
         )
0fcb1e
-        assert result.returncode == 1
0fcb1e
-        assert "Invalid format format --cron" in result.stderr_text
0fcb1e
+        assert result.returncode == 2
0fcb1e
+        assert "Invalid format for --cron" in result.stderr_text
0fcb1e
 
0fcb1e
         result = self.master.run_command(
0fcb1e
             ['ipa-acme-manage', 'pruning',
0fcb1e
-             '--cron="100 * * * *"'],
0fcb1e
+             '--cron=100 * * * *'],
0fcb1e
             raiseonerr=False
0fcb1e
         )
0fcb1e
         assert result.returncode == 1
0fcb1e
@@ -790,8 +807,205 @@ class TestACMEPrune(IntegrationTest):
0fcb1e
 
0fcb1e
         result = self.master.run_command(
0fcb1e
             ['ipa-acme-manage', 'pruning',
0fcb1e
-             '--cron="10 1-5 * * *"'],
0fcb1e
+             '--cron=10 1-5 * * *'],
0fcb1e
             raiseonerr=False
0fcb1e
         )
0fcb1e
         assert result.returncode == 1
0fcb1e
         assert "1-5 ranges are not supported" in result.stderr_text
0fcb1e
+
0fcb1e
+    def test_prune_cert_manual(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test to prune expired certificate by manual run"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+
0fcb1e
+        issue_and_expire_acme_cert(
0fcb1e
+            self.master, self.clients[0], self.acme_server)
0fcb1e
+
0fcb1e
+        # check that the certificate issued for the client
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname]
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' in result.stdout_text
0fcb1e
+
0fcb1e
+        # run prune command manually
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
0fcb1e
+        self.master.run_command(['ipactl', 'restart'])
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--run'])
0fcb1e
+        # wait for cert to get prune
0fcb1e
+        time.sleep(50)
0fcb1e
+
0fcb1e
+        # check if client cert is removed
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname],
0fcb1e
+            raiseonerr=False
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' not in result.stdout_text
0fcb1e
+
0fcb1e
+    def test_prune_cert_cron(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test to prune expired certificate by cron job"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+
0fcb1e
+        issue_and_expire_acme_cert(
0fcb1e
+            self.master, self.clients[0], self.acme_server)
0fcb1e
+
0fcb1e
+        # check that the certificate issued for the client
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname]
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' in result.stdout_text
0fcb1e
+
0fcb1e
+        # enable pruning
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
0fcb1e
+
0fcb1e
+        # cron would be set to run the next minute
0fcb1e
+        cron_minute = self.master.run_command(
0fcb1e
+            [
0fcb1e
+                "python3",
0fcb1e
+                "-c",
0fcb1e
+                (
0fcb1e
+                    "from datetime import datetime; "
0fcb1e
+                    "print(int(datetime.now().strftime('%M')) + 5)"
0fcb1e
+                ),
0fcb1e
+            ]
0fcb1e
+        ).stdout_text.strip()
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             f'--cron={cron_minute} * * * *']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(['ipactl', 'restart'])
0fcb1e
+        # wait for 5 minutes to cron to execute and 20 sec for just in case
0fcb1e
+        time.sleep(320)
0fcb1e
+
0fcb1e
+        # check if client cert is removed
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname],
0fcb1e
+            raiseonerr=False
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' not in result.stdout_text
0fcb1e
+
0fcb1e
+    def test_prune_cert_retention_unit(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test to prune expired certificate with retention unit option"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+        issue_and_expire_acme_cert(
0fcb1e
+            self.master, self.clients[0], self.acme_server)
0fcb1e
+
0fcb1e
+        # check that the certificate issued for the client
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname]
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' in result.stdout_text
0fcb1e
+
0fcb1e
+        # enable pruning
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
0fcb1e
+
0fcb1e
+        # certretention set to 5 min
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--certretention=5', '--certretentionunit=minute']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(['ipactl', 'restart'])
0fcb1e
+
0fcb1e
+        # wait for 5 min and check if expired cert is removed
0fcb1e
+        time.sleep(310)
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--run'])
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname],
0fcb1e
+            raiseonerr=False
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' not in result.stdout_text
0fcb1e
+
0fcb1e
+    def test_prune_cert_search_size_limit(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test to prune expired certificate with search size limit option"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+        no_of_cert = 10
0fcb1e
+        search_size_limit = 5
0fcb1e
+        issue_and_expire_acme_cert(
0fcb1e
+            self.master, self.clients[0], self.acme_server, no_of_cert)
0fcb1e
+
0fcb1e
+        # check that the certificate issued for the client
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname]
0fcb1e
+        )
0fcb1e
+        assert f'CN={self.clients[0].hostname}' in result.stdout_text
0fcb1e
+        assert f'Number of entries returned {no_of_cert}'
0fcb1e
+
0fcb1e
+        # enable pruning
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
0fcb1e
+
0fcb1e
+        # certretention set to 5 min
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             f'--certsearchsizelimit={search_size_limit}',
0fcb1e
+             '--certsearchtimelimit=100']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(['ipactl', 'restart'])
0fcb1e
+
0fcb1e
+        # prune the certificates
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--run'])
0fcb1e
+
0fcb1e
+        # check if 5 expired cert is removed
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa', 'cert-find', '--subject', self.clients[0].hostname]
0fcb1e
+        )
0fcb1e
+        assert f'Number of entries returned {no_of_cert - search_size_limit}'
0fcb1e
+
0fcb1e
+    def test_prune_config_show(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test to check config-show command shows set param"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--cron=0 0 1 * *']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--certretention=30', '--certretentionunit=day']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--certsearchsizelimit=1000', '--certsearchtimelimit=0']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--requestretention=30', '--requestretentionunit=day']
0fcb1e
+        )
0fcb1e
+        self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--requestsearchsizelimit=1000', '--requestsearchtimelimit=0']
0fcb1e
+        )
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning', '--config-show']
0fcb1e
+        )
0fcb1e
+        assert 'Status: enabled' in result.stdout_text
0fcb1e
+        assert 'Certificate Retention Time: 30' in result.stdout_text
0fcb1e
+        assert 'Certificate Retention Unit: day' in result.stdout_text
0fcb1e
+        assert 'Certificate Search Size Limit: 1000' in result.stdout_text
0fcb1e
+        assert 'Certificate Search Time Limit: 100' in result.stdout_text
0fcb1e
+        assert 'Request Retention Time: 30' in result.stdout_text
0fcb1e
+        assert 'Request Retention Unit: day' in result.stdout_text
0fcb1e
+        assert 'Request Search Size Limit' in result.stdout_text
0fcb1e
+        assert 'Request Search Time Limit: 100' in result.stdout_text
0fcb1e
+        assert 'cron Schedule: 0 0 1 * *' in result.stdout_text
0fcb1e
+
0fcb1e
+    def test_prune_disable(self, issue_and_expire_acme_cert):
0fcb1e
+        """Test prune command throw error after disabling the pruning"""
0fcb1e
+        if (tasks.get_pki_version(self.master)
0fcb1e
+           < tasks.parse_version('11.3.0')):
0fcb1e
+            raise pytest.skip("Certificate pruning is not available")
0fcb1e
+
0fcb1e
+        self.master.run_command(['ipa-acme-manage', 'pruning', '--disable'])
0fcb1e
+        result = self.master.run_command(
0fcb1e
+            ['ipa-acme-manage', 'pruning',
0fcb1e
+             '--cron=0 0 1 * *']
0fcb1e
+        )
0fcb1e
+        assert 'Status: disabled' in result.stdout_text
0fcb1e
-- 
0fcb1e
2.39.1
0fcb1e