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

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 

7 

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") 

21 

22 

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 

30 

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() 

47 

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 

67 

68 return account_key_path, domain_key.name, domain_csr.name, parser 

69 

70 

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) 

76 

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) 

81 

82 return {"missing_tsigkeyring": missing_tsigkeyring.name} 

83 

84 

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) 

90 

91 good_cname = NamedTemporaryFile(delete=False) 

92 with open(good_cname.name, 'w', encoding='utf-8') as configfile: 

93 config.write(configfile) 

94 

95 # Simple configuration with good options, without contacts field 

96 _, domain_key, _, config = generate_config(account_key) 

97 os.remove(domain_key) 

98 

99 config.remove_option("acmednstiny", "Contacts") 

100 

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) 

104 

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) 

108 

109 config.remove_option("acmednstiny", "CSRFile") 

110 

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) 

114 

115 # Configuration with CSR containing a wildcard domain 

116 _, domain_key, domain_csr, config = generate_config(account_key) 

117 

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) 

132 

133 wild_cname = NamedTemporaryFile(delete=False) 

134 with open(wild_cname.name, 'w', encoding='utf-8') as configfile: 

135 config.write(configfile) 

136 

137 # Configuration with CSR using subject alt-name domain instead of CN (common name) 

138 _, domain_key, domain_csr, config = generate_config(account_key) 

139 

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) 

150 

151 good_san = NamedTemporaryFile(delete=False) 

152 with open(good_san.name, 'w', encoding='utf-8') as configfile: 

153 config.write(configfile) 

154 

155 # Configuration with CSR containing a wildcard domain inside subjetcAltName 

156 _, domain_key, domain_csr, config = generate_config(account_key) 

157 

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) 

169 

170 wild_san = NamedTemporaryFile(delete=False) 

171 with open(wild_san.name, 'w', encoding='utf-8') as configfile: 

172 config.write(configfile) 

173 

174 # Invalid TSIG key name 

175 _, domain_key, _, config = generate_config(account_key) 

176 os.remove(domain_key) 

177 

178 config["TSIGKeyring"]["KeyName"] = "{0}.invalid".format(TSIGKEYNAME) 

179 

180 invalid_tsig_name = NamedTemporaryFile(delete=False) 

181 with open(invalid_tsig_name.name, 'w', encoding='utf-8') as configfile: 

182 config.write(configfile) 

183 

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 } 

196 

197 

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) 

203 

204 # New account key 

205 new_account_key = NamedTemporaryFile(delete=False) 

206 Popen(["openssl", "genrsa", "-out", new_account_key.name, "2048"]).wait() 

207 

208 rollover_account = NamedTemporaryFile(delete=False) 

209 with open(rollover_account.name, 'w', encoding='utf-8') as configfile: 

210 config.write(configfile) 

211 

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 } 

218 

219 

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) 

225 

226 deactivate_account = NamedTemporaryFile(delete=False) 

227 with open(deactivate_account.name, 'w', encoding='utf-8') as configfile: 

228 config.write(configfile) 

229 

230 return { 

231 "config": deactivate_account.name, 

232 "key": account_key 

233 }