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