Coverage for tests/staging_test_acme_dns_tiny.py: 97%
117 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-05-24 19:15 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-05-24 19:15 +0000
1"""Tests for acme_dns_tiny script to be run with real ACME server"""
2import unittest
3import sys
4import os
5import subprocess
6import configparser
7from io import StringIO
8import dns.version
9import acme_dns_tiny
10from tests.config_factory import generate_acme_dns_tiny_config
11from tools.acme_account_deactivate import account_deactivate
13ACME_DIRECTORY = os.getenv("GITLABCI_ACMEDIRECTORY_V2",
14 "https://acme-staging-v02.api.letsencrypt.org/directory")
15ACME_TIMEOUT = os.getenv("GITLABCI_ACMETIMEOUT", "10")
18def _openssl(command, options, communicate=None):
19 """Helper function to run openssl command."""
20 with subprocess.Popen(["openssl", command] + options,
21 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
22 stderr=subprocess.PIPE) as openssl:
23 out, err = openssl.communicate(communicate)
24 if openssl.returncode != 0:
25 raise IOError("OpenSSL Error: {0}".format(err))
26 return out.decode("utf8")
29class TestACMEDNSTiny(unittest.TestCase):
30 """Tests for acme_dns_tiny.get_crt()."""
32 @classmethod
33 def setUpClass(cls):
34 print("Init acme_dns_tiny with python modules:")
35 print(" - python: {0}".format(sys.version))
36 print(" - dns python: {0}".format(dns.version.version))
37 cls.configs = generate_acme_dns_tiny_config()
38 sys.stdout.flush()
39 super(TestACMEDNSTiny, cls).setUpClass()
41 # To clean ACME staging server and close correctly temporary files
42 # pylint: disable=bare-except
43 @classmethod
44 def tearDownClass(cls):
45 # close temp files correctly
46 for conf_name, file in cls.configs.items():
47 # for each configuration file, deactivate the account and remove linked temporary files
48 if conf_name != "cname_csr":
49 parser = configparser.ConfigParser()
50 parser.read(file)
51 try:
52 account_deactivate(
53 parser["acmednstiny"]["AccountKeyFile"], ACME_DIRECTORY, ACME_TIMEOUT)
54 except:
55 pass
56 try:
57 os.remove(parser["acmednstiny"]["AccountKeyFile"])
58 except:
59 pass
60 try:
61 os.remove(parser["acmednstiny"]["CSRFile"])
62 except:
63 pass
64 try:
65 os.remove(file)
66 except:
67 pass
68 super(TestACMEDNSTiny, cls).tearDownClass()
70 # helper function to valid success by making assertion on returned certificate chain
71 def _assert_certificate_chain(self, cert_chain):
72 # Output have to contain at least two certificates to create a chain
73 certlist = list(filter(None, cert_chain.split("-----BEGIN CERTIFICATE-----")))
74 self.assertTrue(len(certlist) >= 2)
75 for cert in certlist:
76 self.assertIn("-----END CERTIFICATE-----", cert)
77 # Use openssl to check validity of chain and simple test of readability
78 readablecertchain = _openssl("x509", ["-text", "-noout"],
79 cert_chain.encode("utf8"))
80 self.assertIn("Issuer", readablecertchain)
82 def test_success_cn(self):
83 """Successfully issue a certificate via common name."""
84 old_stdout = sys.stdout
85 sys.stdout = StringIO()
87 acme_dns_tiny.main([self.configs['good_cname'], "--verbose"])
88 certchain = sys.stdout.getvalue()
90 sys.stdout.close()
91 sys.stdout = old_stdout
93 self._assert_certificate_chain(certchain)
95 def test_success_cn_without_contacts(self):
96 """Successfully issue a certificate via CN, but without Contacts field."""
97 old_stdout = sys.stdout
98 sys.stdout = StringIO()
100 acme_dns_tiny.main([self.configs['good_cname_without_contacts'], "--verbose"])
101 certchain = sys.stdout.getvalue()
103 sys.stdout.close()
104 sys.stdout = old_stdout
106 self._assert_certificate_chain(certchain)
108 def test_success_cn_with_csr_option(self):
109 """Successfully issue a certificate using CSR option outside from the config file."""
110 old_stdout = sys.stdout
111 sys.stdout = StringIO()
113 acme_dns_tiny.main(["--csr", self.configs['cname_csr'],
114 self.configs['good_cname_without_csr'], "--verbose"])
115 certchain = sys.stdout.getvalue()
117 sys.stdout.close()
118 sys.stdout = old_stdout
120 self._assert_certificate_chain(certchain)
122 def test_success_wild_cn(self):
123 """Successfully issue a certificate via a wildcard common name."""
124 old_stdout = sys.stdout
125 sys.stdout = StringIO()
127 acme_dns_tiny.main([self.configs['wild_cname'], "--verbose"])
128 certchain = sys.stdout.getvalue()
130 sys.stdout.close()
131 sys.stdout = old_stdout
133 self._assert_certificate_chain(certchain)
135 def test_success_san(self):
136 """Successfully issue a certificate via subject alt name."""
137 old_stdout = sys.stdout
138 sys.stdout = StringIO()
140 acme_dns_tiny.main([self.configs['good_san'], "--verbose"])
141 certchain = sys.stdout.getvalue()
143 sys.stdout.close()
144 sys.stdout = old_stdout
146 self._assert_certificate_chain(certchain)
148 def test_success_wildsan(self):
149 """Successfully issue a certificate via wildcard in subject alt name."""
150 old_stdout = sys.stdout
151 sys.stdout = StringIO()
153 acme_dns_tiny.main([self.configs['wild_san']])
154 certchain = sys.stdout.getvalue()
156 sys.stdout.close()
157 sys.stdout = old_stdout
159 self._assert_certificate_chain(certchain)
161 def test_success_cli(self):
162 """Successfully issue a certificate via command line interface."""
163 with subprocess.Popen(["python3", "acme_dns_tiny.py",
164 self.configs['good_cname'], "--verbose"],
165 stdout=subprocess.PIPE, stderr=subprocess.PIPE) as cli:
166 certout, _ = cli.communicate()
167 certchain = certout.decode("utf8")
168 self._assert_certificate_chain(certchain)
170 def test_success_cli_with_csr_option(self):
171 """Successfully issue a certificate via command line interface using CSR option."""
172 with subprocess.Popen(["python3", "acme_dns_tiny.py", "--csr", self.configs['cname_csr'],
173 self.configs['good_cname_without_csr'], "--verbose"],
174 stdout=subprocess.PIPE, stderr=subprocess.PIPE) as cli:
175 certout, _ = cli.communicate()
176 certchain = certout.decode("utf8")
177 self._assert_certificate_chain(certchain)
179 def test_failure_dns_update_tsigkeyname(self):
180 """Fail to update DNS records by invalid TSIG Key name."""
181 self.assertRaisesRegex(RuntimeError,
182 "Unable to add DNS resource to _acme-challenge.{0}."
183 .format(os.getenv("GITLABCI_DOMAIN")),
184 acme_dns_tiny.main, [self.configs['invalid_tsig_name'],
185 "--verbose"])
188if __name__ == "__main__": # pragma: no cover
189 unittest.main()