config_factory.py 9.11 KB
Newer Older
Adrien Dorsaz's avatar
Adrien Dorsaz committed
1 2 3
"""Create real temporary ACME dns tiny configurations to run tests with real server"""
import os
import configparser
4 5 6 7
from tempfile import NamedTemporaryFile
from subprocess import Popen

# domain with server.py running on it for testing
8
DOMAIN = os.getenv("GITLABCI_DOMAIN")
Adrien Dorsaz's avatar
Adrien Dorsaz committed
9 10
ACMEDIRECTORY = os.getenv("GITLABCI_ACMEDIRECTORY_V2",
                          "https://acme-staging-v02.api.letsencrypt.org/directory")
11
DNSHOST = os.getenv("GITLABCI_DNSHOST")
12
DNSHOSTIP = os.getenv("GITLABCI_DNSHOSTIP")
13 14
DNSZONE = os.getenv("GITLABCI_DNSZONE")
DNSPORT = os.getenv("GITLABCI_DNSPORT", "53")
15
DNSTTL = os.getenv("GITLABCI_DNSTTL", "10")
16 17 18
TSIGKEYNAME = os.getenv("GITLABCI_TSIGKEYNAME")
TSIGKEYVALUE = os.getenv("GITLABCI_TSIGKEYVALUE")
TSIGALGORITHM = os.getenv("GITLABCI_TSIGALGORITHM")
19
CONTACT = os.getenv("GITLABCI_CONTACT")
20

21

22
def generate_config():
Adrien Dorsaz's avatar
Adrien Dorsaz committed
23
    """Generate basic acme-dns-tiny configuration"""
24
    # Account key
25
    account_key = NamedTemporaryFile(delete=False)
26 27
    Popen(["openssl", "genrsa", "-out", account_key.name, "2048"]).wait()

28
    # Domain key and CSR
29 30
    domain_key = NamedTemporaryFile(delete=False)
    domain_csr = NamedTemporaryFile(delete=False)
31
    Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key.name,
Adrien Dorsaz's avatar
Adrien Dorsaz committed
32
           "-subj", "/CN={0}".format(DOMAIN), "-out", domain_csr.name]).wait()
Daniel Roesler's avatar
Daniel Roesler committed
33

34 35 36
    # acme-dns-tiny configuration
    parser = configparser.ConfigParser()
    parser.read("./example.ini")
Adrien Dorsaz's avatar
Adrien Dorsaz committed
37 38
    parser["acmednstiny"]["AccountKeyFile"] = account_key.name
    parser["acmednstiny"]["CSRFile"] = domain_csr.name
39
    parser["acmednstiny"]["ACMEDirectory"] = ACMEDIRECTORY
Adrien Dorsaz's avatar
Adrien Dorsaz committed
40
    if CONTACT:
41
        parser["acmednstiny"]["Contacts"] = "mailto:{0}".format(CONTACT)
42
    elif "Contacts" in parser:
43
        del parser["acmednstiny"]["Contacts"]
44 45 46 47 48 49
    parser["TSIGKeyring"]["KeyName"] = TSIGKEYNAME
    parser["TSIGKeyring"]["KeyValue"] = TSIGKEYVALUE
    parser["TSIGKeyring"]["Algorithm"] = TSIGALGORITHM
    parser["DNS"]["Host"] = DNSHOST
    parser["DNS"]["Port"] = DNSPORT
    parser["DNS"]["Zone"] = DNSZONE
50
    parser["DNS"]["TTL"] = DNSTTL
51

52
    return account_key.name, domain_key.name, domain_csr.name, parser
53

54

55 56 57 58 59 60 61 62 63 64 65 66 67
def generate_acme_dns_tiny_unit_test_config():
    """Genereate acme_dns_tiny configurations used for unit tests"""
    # Configuration missing DNS section
    _, domain_key, _, config = generate_config()
    os.remove(domain_key)

    missing_dns = NamedTemporaryFile(delete=False)
    config["DNS"] = {}
    with open(missing_dns.name, 'w') as configfile:
        config.write(configfile)

    return {"missing_dns": missing_dns.name}

68 69

def generate_acme_dns_tiny_config():  # pylint: disable=too-many-locals,too-many-statements
Adrien Dorsaz's avatar
Adrien Dorsaz committed
70
    """Generate acme_dns_tiny configuration with account and domain keys"""
71
    # Simple configuration with good options
Adrien Dorsaz's avatar
Adrien Dorsaz committed
72
    account_key, domain_key, _, config = generate_config()
73
    os.remove(domain_key)
74

Adrien Dorsaz's avatar
Adrien Dorsaz committed
75 76
    good_cname = NamedTemporaryFile(delete=False)
    with open(good_cname.name, 'w') as configfile:
77
        config.write(configfile)
78

79
    # Simple configuration with good options, without contacts field
Adrien Dorsaz's avatar
Adrien Dorsaz committed
80
    account_key, domain_key, _, config = generate_config()
81 82 83 84
    os.remove(domain_key)

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

Adrien Dorsaz's avatar
Adrien Dorsaz committed
85 86
    good_cname_without_contacts = NamedTemporaryFile(delete=False)
    with open(good_cname_without_contacts.name, 'w') as configfile:
87 88
        config.write(configfile)

89
    # Simple configuration without CSR in configuration (will be passed as argument)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
90
    account_key, domain_key, cname_csr, config = generate_config()
91
    os.remove(domain_key)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
92

93
    config.remove_option("acmednstiny", "CSRFile")
Adrien Dorsaz's avatar
Adrien Dorsaz committed
94

Adrien Dorsaz's avatar
Adrien Dorsaz committed
95 96
    good_cname_without_csr = NamedTemporaryFile(delete=False)
    with open(good_cname_without_csr.name, 'w') as configfile:
97 98
        config.write(configfile)

99
    # Configuration with CSR containing a wildcard domain
Adrien Dorsaz's avatar
Adrien Dorsaz committed
100
    account_key, domain_key, domain_csr, config = generate_config()
101 102 103 104 105

    Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key,
           "-subj", "/CN=*.{0}".format(DOMAIN), "-out", domain_csr]).wait()
    os.remove(domain_key)

Adrien Dorsaz's avatar
Adrien Dorsaz committed
106 107
    wild_cname = NamedTemporaryFile(delete=False)
    with open(wild_cname.name, 'w') as configfile:
108 109
        config.write(configfile)

110
    # Configuration with IP as DNS Host
Adrien Dorsaz's avatar
Adrien Dorsaz committed
111
    account_key, domain_key, _, config = generate_config()
112 113
    os.remove(domain_key)

114
    config["DNS"]["Host"] = DNSHOSTIP
115

Adrien Dorsaz's avatar
Adrien Dorsaz committed
116 117
    dns_host_ip = NamedTemporaryFile(delete=False)
    with open(dns_host_ip.name, 'w') as configfile:
118
        config.write(configfile)
119 120

    # Configuration with CSR using subject alt-name domain instead of CN (common name)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
121
    account_key, domain_key, domain_csr, config = generate_config()
122 123

    san_conf = NamedTemporaryFile(delete=False)
124 125
    with open("/etc/ssl/openssl.cnf", 'r') as opensslcnf:
        san_conf.write(opensslcnf.read().encode("utf8"))
126 127 128
    san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:www.{0}\n".format(DOMAIN).encode("utf8"))
    san_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
Adrien Dorsaz's avatar
Adrien Dorsaz committed
129 130
           "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
           "-out", domain_csr]).wait()
131 132
    os.remove(san_conf.name)
    os.remove(domain_key)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
133

Adrien Dorsaz's avatar
Adrien Dorsaz committed
134 135
    good_san = NamedTemporaryFile(delete=False)
    with open(good_san.name, 'w') as configfile:
136
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
137

138
    # Configuration with CSR containing a wildcard domain inside subjetcAltName
Adrien Dorsaz's avatar
Adrien Dorsaz committed
139
    account_key, domain_key, domain_csr, config = generate_config()
140

Adrien Dorsaz's avatar
Adrien Dorsaz committed
141
    wild_san_conf = NamedTemporaryFile(delete=False)
142
    with open("/etc/ssl/openssl.cnf", 'r') as opensslcnf:
Adrien Dorsaz's avatar
Adrien Dorsaz committed
143
        wild_san_conf.write(opensslcnf.read().encode("utf8"))
144 145
    wild_san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:*.{0}\n"
                        .format(DOMAIN).encode("utf8"))
Adrien Dorsaz's avatar
Adrien Dorsaz committed
146
    wild_san_conf.seek(0)
147
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
Adrien Dorsaz's avatar
Adrien Dorsaz committed
148
           "-subj", "/", "-reqexts", "SAN", "-config", wild_san_conf.name,
149
           "-out", domain_csr]).wait()
Adrien Dorsaz's avatar
Adrien Dorsaz committed
150
    os.remove(wild_san_conf.name)
151 152
    os.remove(domain_key)

Adrien Dorsaz's avatar
Adrien Dorsaz committed
153 154
    wild_san = NamedTemporaryFile(delete=False)
    with open(wild_san.name, 'w') as configfile:
155 156
        config.write(configfile)

157
    # Bad configuration with weak 1024 bit account key
Adrien Dorsaz's avatar
Adrien Dorsaz committed
158
    account_key, domain_key, _, config = generate_config()
159 160 161 162
    os.remove(domain_key)

    Popen(["openssl", "genrsa", "-out", account_key, "1024"]).wait()

Adrien Dorsaz's avatar
Adrien Dorsaz committed
163 164
    weak_key = NamedTemporaryFile(delete=False)
    with open(weak_key.name, 'w') as configfile:
165
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
166

167
    # Bad configuration with account key as domain key
Adrien Dorsaz's avatar
Adrien Dorsaz committed
168
    account_key, domain_key, domain_csr, config = generate_config()
169 170 171 172
    os.remove(domain_key)

    # Create a new CSR signed with the account key instead of domain key
    Popen(["openssl", "req", "-new", "-sha256", "-key", account_key,
Adrien Dorsaz's avatar
Adrien Dorsaz committed
173
           "-subj", "/CN={0}".format(DOMAIN), "-out", domain_csr]).wait()
174

Adrien Dorsaz's avatar
Adrien Dorsaz committed
175 176
    account_as_domain = NamedTemporaryFile(delete=False)
    with open(account_as_domain.name, 'w') as configfile:
177
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
178

179
    # Create config parser from the good default config to generate custom configs
Adrien Dorsaz's avatar
Adrien Dorsaz committed
180
    account_key, domain_key, _, config = generate_config()
181 182
    os.remove(domain_key)

Adrien Dorsaz's avatar
Adrien Dorsaz committed
183
    invalid_tsig_name = NamedTemporaryFile(delete=False)
184
    config["TSIGKeyring"]["KeyName"] = "{0}.invalid".format(TSIGKEYNAME)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
185
    with open(invalid_tsig_name.name, 'w') as configfile:
186
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
187

188
    return {
189
        # configs
Adrien Dorsaz's avatar
Adrien Dorsaz committed
190 191 192 193 194 195 196 197 198 199 200 201
        "good_cname": good_cname.name,
        "good_cname_without_contacts": good_cname_without_contacts.name,
        "good_cname_without_csr": good_cname_without_csr.name,
        "wild_cname": wild_cname.name,
        "dns_host_ip": dns_host_ip.name,
        "good_san": good_san.name,
        "wild_san": wild_san.name,
        "weak_key": weak_key.name,
        "account_as_domain": account_as_domain.name,
        "invalid_tsig_name": invalid_tsig_name.name,
        # cname CSR file to use with good_cname_without_csr as argument
        "cname_csr": cname_csr,
202 203
    }

204

205
def generate_acme_account_rollover_config():
Adrien Dorsaz's avatar
Adrien Dorsaz committed
206 207 208
    """Generate config for acme_account_rollover script"""
    # Old account key is directly created by the config generator
    old_account_key, domain_key, _, config = generate_config()
209
    os.remove(domain_key)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
210

211
    # New account key
212
    new_account_key = NamedTemporaryFile(delete=False)
213
    Popen(["openssl", "genrsa", "-out", new_account_key.name, "2048"]).wait()
Adrien Dorsaz's avatar
Adrien Dorsaz committed
214

Adrien Dorsaz's avatar
Adrien Dorsaz committed
215 216
    rollover_account = NamedTemporaryFile(delete=False)
    with open(rollover_account.name, 'w') as configfile:
217 218
        config.write(configfile)

219
    return {
Adrien Dorsaz's avatar
Adrien Dorsaz committed
220
        # config and keys (returned to keep files on system)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
221 222 223
        "config": rollover_account.name,
        "old_account_key": old_account_key,
        "new_account_key": new_account_key.name
224
    }
225

226

227
def generate_acme_account_deactivate_config():
Adrien Dorsaz's avatar
Adrien Dorsaz committed
228
    """Generate config for acme_account_deactivate script"""
229
    # Account key is created by the by the config generator
Adrien Dorsaz's avatar
Adrien Dorsaz committed
230
    account_key, domain_key, _, config = generate_config()
231 232
    os.remove(domain_key)

Adrien Dorsaz's avatar
Adrien Dorsaz committed
233 234
    deactivate_account = NamedTemporaryFile(delete=False)
    with open(deactivate_account.name, 'w') as configfile:
235
        config.write(configfile)
236 237

    return {
Adrien Dorsaz's avatar
Adrien Dorsaz committed
238
        "config": deactivate_account.name,
239 240
        "key": account_key
    }