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