Coverage for tests/config_factory.py: 98%
137 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"""Create real temporary ACME dns tiny configurations to run tests with real server"""
2# pylint: disable=consider-using-with
3import os
4import configparser
5from tempfile import NamedTemporaryFile
6from subprocess import Popen
8# domain with server.py running on it for testing
9DOMAIN = os.getenv("GITLABCI_DOMAIN")
10ACMEDIRECTORY = os.getenv("GITLABCI_ACMEDIRECTORY_V2",
11 "https://acme-staging-v02.api.letsencrypt.org/directory")
12ACMETIMEOUT = os.getenv("GITLABCI_ACMETIMEOUT", "10")
13IS_PEBBLE = ACMEDIRECTORY.startswith('https://pebble')
14DNSNAMESERVER = os.getenv("GITLABCI_DNSNAMESERVER", "")
15DNSTTL = os.getenv("GITLABCI_DNSTTL", "10")
16DNSTIMEOUT = os.getenv("GITLABCI_DNSTIMEOUT", "10")
17TSIGKEYNAME = os.getenv("GITLABCI_TSIGKEYNAME", "")
18TSIGKEYVALUE = os.getenv("GITLABCI_TSIGKEYVALUE", "")
19TSIGALGORITHM = os.getenv("GITLABCI_TSIGALGORITHM", "")
20CONTACT = os.getenv("GITLABCI_CONTACT")
23def generate_config(account_key_path=None):
24 """Generate basic acme-dns-tiny configuration"""
25 # Account key should be created if not given
26 if account_key_path is None:
27 account_key = NamedTemporaryFile(delete=False)
28 Popen(["openssl", "genrsa", "-out", account_key.name, "2048"]).wait()
29 account_key_path = account_key.name
31 # Domain key and CSR
32 domain_key = NamedTemporaryFile(delete=False)
33 domain_csr = NamedTemporaryFile(delete=False)
34 if IS_PEBBLE: # Pebble server enforces usage of SAN instead of CN
35 san_conf = NamedTemporaryFile(delete=False)
36 with open("/etc/ssl/openssl.cnf", 'r', encoding='utf-8') as opensslcnf:
37 san_conf.write(opensslcnf.read().encode("utf8"))
38 san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0}\n".format(DOMAIN).encode("utf8"))
39 san_conf.seek(0)
40 Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key.name,
41 "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
42 "-out", domain_csr.name]).wait()
43 os.remove(san_conf.name)
44 else:
45 Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key.name,
46 "-subj", "/CN={0}".format(DOMAIN), "-out", domain_csr.name]).wait()
48 # acme-dns-tiny configuration
49 parser = configparser.ConfigParser()
50 parser.read("./example.ini")
51 parser["acmednstiny"]["AccountKeyFile"] = account_key_path
52 parser["acmednstiny"]["CSRFile"] = domain_csr.name
53 parser["acmednstiny"]["ACMEDirectory"] = ACMEDIRECTORY
54 if CONTACT:
55 parser["acmednstiny"]["Contacts"] = "mailto:{0}".format(CONTACT)
56 elif "Contacts" in parser:
57 del parser["acmednstiny"]["Contacts"]
58 if ACMETIMEOUT:
59 parser["acmednstiny"]["Timeout"] = ACMETIMEOUT
60 parser["TSIGKeyring"]["KeyName"] = TSIGKEYNAME
61 parser["TSIGKeyring"]["KeyValue"] = TSIGKEYVALUE
62 parser["TSIGKeyring"]["Algorithm"] = TSIGALGORITHM
63 parser["DNS"]["NameServer"] = DNSNAMESERVER
64 parser["DNS"]["TTL"] = DNSTTL
65 if DNSTIMEOUT:
66 parser["DNS"]["Timeout"] = DNSTIMEOUT
68 return account_key_path, domain_key.name, domain_csr.name, parser
71def generate_acme_dns_tiny_unit_test_config():
72 """Genereate acme_dns_tiny configurations used for unit tests"""
73 # Configuration missing DNS section
74 _, domain_key, _, config = generate_config()
75 os.remove(domain_key)
77 missing_tsigkeyring = NamedTemporaryFile(delete=False)
78 config["TSIGKeyring"] = {}
79 with open(missing_tsigkeyring.name, 'w', encoding='utf-8') as configfile:
80 config.write(configfile)
82 return {"missing_tsigkeyring": missing_tsigkeyring.name}
85def generate_acme_dns_tiny_config(): # pylint: disable=too-many-locals,too-many-statements
86 """Generate acme_dns_tiny configuration with account and domain keys"""
87 # Simple configuration with good options
88 account_key, domain_key, _, config = generate_config()
89 os.remove(domain_key)
91 good_cname = NamedTemporaryFile(delete=False)
92 with open(good_cname.name, 'w', encoding='utf-8') as configfile:
93 config.write(configfile)
95 # Simple configuration with good options, without contacts field
96 _, domain_key, _, config = generate_config(account_key)
97 os.remove(domain_key)
99 config.remove_option("acmednstiny", "Contacts")
101 good_cname_without_contacts = NamedTemporaryFile(delete=False)
102 with open(good_cname_without_contacts.name, 'w', encoding='utf-8') as configfile:
103 config.write(configfile)
105 # Simple configuration without CSR in configuration (will be passed as argument)
106 _, domain_key, cname_csr, config = generate_config(account_key)
107 os.remove(domain_key)
109 config.remove_option("acmednstiny", "CSRFile")
111 good_cname_without_csr = NamedTemporaryFile(delete=False)
112 with open(good_cname_without_csr.name, 'w', encoding='utf-8') as configfile:
113 config.write(configfile)
115 # Configuration with CSR containing a wildcard domain
116 _, domain_key, domain_csr, config = generate_config(account_key)
118 if IS_PEBBLE: # Pebble server enforces usage of SAN instead of CN
119 san_conf = NamedTemporaryFile(delete=False)
120 with open("/etc/ssl/openssl.cnf", 'r', encoding='utf-8') as opensslcnf:
121 san_conf.write(opensslcnf.read().encode("utf8"))
122 san_conf.write("\n[SAN]\nsubjectAltName=DNS:*.{0}\n".format(DOMAIN).encode("utf8"))
123 san_conf.seek(0)
124 Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key,
125 "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
126 "-out", domain_csr]).wait()
127 os.remove(san_conf.name)
128 else:
129 Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key,
130 "-subj", "/CN=*.{0}".format(DOMAIN), "-out", domain_csr]).wait()
131 os.remove(domain_key)
133 wild_cname = NamedTemporaryFile(delete=False)
134 with open(wild_cname.name, 'w', encoding='utf-8') as configfile:
135 config.write(configfile)
137 # Configuration with CSR using subject alt-name domain instead of CN (common name)
138 _, domain_key, domain_csr, config = generate_config(account_key)
140 san_conf = NamedTemporaryFile(delete=False)
141 with open("/etc/ssl/openssl.cnf", 'r', encoding='utf-8') as opensslcnf:
142 san_conf.write(opensslcnf.read().encode("utf8"))
143 san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:www.{0}\n".format(DOMAIN).encode("utf8"))
144 san_conf.seek(0)
145 Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
146 "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
147 "-out", domain_csr]).wait()
148 os.remove(san_conf.name)
149 os.remove(domain_key)
151 good_san = NamedTemporaryFile(delete=False)
152 with open(good_san.name, 'w', encoding='utf-8') as configfile:
153 config.write(configfile)
155 # Configuration with CSR containing a wildcard domain inside subjetcAltName
156 _, domain_key, domain_csr, config = generate_config(account_key)
158 wild_san_conf = NamedTemporaryFile(delete=False)
159 with open("/etc/ssl/openssl.cnf", 'r', encoding='utf-8') as opensslcnf:
160 wild_san_conf.write(opensslcnf.read().encode("utf8"))
161 wild_san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:*.{0}\n"
162 .format(DOMAIN).encode("utf8"))
163 wild_san_conf.seek(0)
164 Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
165 "-subj", "/", "-reqexts", "SAN", "-config", wild_san_conf.name,
166 "-out", domain_csr]).wait()
167 os.remove(wild_san_conf.name)
168 os.remove(domain_key)
170 wild_san = NamedTemporaryFile(delete=False)
171 with open(wild_san.name, 'w', encoding='utf-8') as configfile:
172 config.write(configfile)
174 # Invalid TSIG key name
175 _, domain_key, _, config = generate_config(account_key)
176 os.remove(domain_key)
178 config["TSIGKeyring"]["KeyName"] = "{0}.invalid".format(TSIGKEYNAME)
180 invalid_tsig_name = NamedTemporaryFile(delete=False)
181 with open(invalid_tsig_name.name, 'w', encoding='utf-8') as configfile:
182 config.write(configfile)
184 return {
185 # configs
186 "good_cname": good_cname.name,
187 "good_cname_without_contacts": good_cname_without_contacts.name,
188 "good_cname_without_csr": good_cname_without_csr.name,
189 "wild_cname": wild_cname.name,
190 "good_san": good_san.name,
191 "wild_san": wild_san.name,
192 "invalid_tsig_name": invalid_tsig_name.name,
193 # cname CSR file to use with good_cname_without_csr as argument
194 "cname_csr": cname_csr,
195 }
198def generate_acme_account_rollover_config():
199 """Generate config for acme_account_rollover script"""
200 # Old account key is directly created by the config generator
201 old_account_key, domain_key, _, config = generate_config()
202 os.remove(domain_key)
204 # New account key
205 new_account_key = NamedTemporaryFile(delete=False)
206 Popen(["openssl", "genrsa", "-out", new_account_key.name, "2048"]).wait()
208 rollover_account = NamedTemporaryFile(delete=False)
209 with open(rollover_account.name, 'w', encoding='utf-8') as configfile:
210 config.write(configfile)
212 return {
213 # config and keys (returned to keep files on system)
214 "config": rollover_account.name,
215 "old_account_key": old_account_key,
216 "new_account_key": new_account_key.name
217 }
220def generate_acme_account_deactivate_config():
221 """Generate config for acme_account_deactivate script"""
222 # Account key is created by the by the config generator
223 account_key, domain_key, _, config = generate_config()
224 os.remove(domain_key)
226 deactivate_account = NamedTemporaryFile(delete=False)
227 with open(deactivate_account.name, 'w', encoding='utf-8') as configfile:
228 config.write(configfile)
230 return {
231 "config": deactivate_account.name,
232 "key": account_key
233 }