config_factory.py 8.28 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(account_key_path=None):
Adrien Dorsaz's avatar
Adrien Dorsaz committed
23
    """Generate basic acme-dns-tiny configuration"""
24 25 26 27 28
    # Account key should be created if not given
    if account_key_path is None:
        account_key = NamedTemporaryFile(delete=False)
        Popen(["openssl", "genrsa", "-out", account_key.name, "2048"]).wait()
        account_key_path = account_key.name
29

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

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

54
    return account_key_path, domain_key.name, domain_csr.name, parser
55

56

57 58 59 60 61 62 63 64 65 66 67 68 69
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}

70 71

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

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

81
    # Simple configuration with good options, without contacts field
82
    _, domain_key, _, config = generate_config(account_key)
83 84 85 86
    os.remove(domain_key)

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

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

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

95
    config.remove_option("acmednstiny", "CSRFile")
Adrien Dorsaz's avatar
Adrien Dorsaz committed
96

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

101
    # Configuration with CSR containing a wildcard domain
102
    _, domain_key, domain_csr, config = generate_config(account_key)
103 104 105 106 107

    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
108 109
    wild_cname = NamedTemporaryFile(delete=False)
    with open(wild_cname.name, 'w') as configfile:
110 111
        config.write(configfile)

112
    # Configuration with IP as DNS Host
113
    _, domain_key, _, config = generate_config(account_key)
114 115
    os.remove(domain_key)

116
    config["DNS"]["Host"] = DNSHOSTIP
117

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

    # Configuration with CSR using subject alt-name domain instead of CN (common name)
123
    _, domain_key, domain_csr, config = generate_config(account_key)
124 125

    san_conf = NamedTemporaryFile(delete=False)
126 127
    with open("/etc/ssl/openssl.cnf", 'r') as opensslcnf:
        san_conf.write(opensslcnf.read().encode("utf8"))
128 129 130
    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
131 132
           "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
           "-out", domain_csr]).wait()
133 134
    os.remove(san_conf.name)
    os.remove(domain_key)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
135

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

140
    # Configuration with CSR containing a wildcard domain inside subjetcAltName
141
    _, domain_key, domain_csr, config = generate_config(account_key)
142

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

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

159 160
    # Invalid TSIG key name
    _, domain_key, _, config = generate_config(account_key)
161 162
    os.remove(domain_key)

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

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

169
    return {
170
        # configs
Adrien Dorsaz's avatar
Adrien Dorsaz committed
171 172 173 174 175 176 177 178 179 180
        "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,
        "invalid_tsig_name": invalid_tsig_name.name,
        # cname CSR file to use with good_cname_without_csr as argument
        "cname_csr": cname_csr,
181 182
    }

183

184
def generate_acme_account_rollover_config():
Adrien Dorsaz's avatar
Adrien Dorsaz committed
185 186 187
    """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()
188
    os.remove(domain_key)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
189

190
    # New account key
191
    new_account_key = NamedTemporaryFile(delete=False)
192
    Popen(["openssl", "genrsa", "-out", new_account_key.name, "2048"]).wait()
Adrien Dorsaz's avatar
Adrien Dorsaz committed
193

Adrien Dorsaz's avatar
Adrien Dorsaz committed
194 195
    rollover_account = NamedTemporaryFile(delete=False)
    with open(rollover_account.name, 'w') as configfile:
196 197
        config.write(configfile)

198
    return {
Adrien Dorsaz's avatar
Adrien Dorsaz committed
199
        # config and keys (returned to keep files on system)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
200 201 202
        "config": rollover_account.name,
        "old_account_key": old_account_key,
        "new_account_key": new_account_key.name
203
    }
204

205

206
def generate_acme_account_deactivate_config():
Adrien Dorsaz's avatar
Adrien Dorsaz committed
207
    """Generate config for acme_account_deactivate script"""
208
    # Account key is created by the by the config generator
Adrien Dorsaz's avatar
Adrien Dorsaz committed
209
    account_key, domain_key, _, config = generate_config()
210 211
    os.remove(domain_key)

Adrien Dorsaz's avatar
Adrien Dorsaz committed
212 213
    deactivate_account = NamedTemporaryFile(delete=False)
    with open(deactivate_account.name, 'w') as configfile:
214
        config.write(configfile)
215 216

    return {
Adrien Dorsaz's avatar
Adrien Dorsaz committed
217
        "config": deactivate_account.name,
218 219
        "key": account_key
    }