config_factory.py 7.43 KB
Newer Older
1
import os, configparser
2 3 4 5
from tempfile import NamedTemporaryFile
from subprocess import Popen

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

18 19 20
# generate simple config
def generate_config():
    # Account key
21
    account_key = NamedTemporaryFile(delete=False)
22 23
    Popen(["openssl", "genrsa", "-out", account_key.name, "2048"]).wait()

24
    # Domain key and CSR
25 26
    domain_key = NamedTemporaryFile(delete=False)
    domain_csr = NamedTemporaryFile(delete=False)
27 28
    Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key.name,
        "-subj", "/CN={0}".format(DOMAIN), "-out", domain_csr.name]).wait()
29

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

49
    config = NamedTemporaryFile(delete=False)
50 51 52
    with open(config.name, 'w') as configfile:
        parser.write(configfile)

53
    return account_key.name, domain_key.name, domain_csr.name, config.name
54 55 56 57 58 59

# generate account and domain keys
def generate_acme_dns_tiny_config():
    # Simple good configuration
    account_key, domain_key, domain_csr, goodCName = generate_config();

60 61 62 63 64
    # CSR for good configuration with wildcard domain
    wilddomain_csr = NamedTemporaryFile(delete=False)
    Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", domain_key,
           "-subj", "/CN=*.{0}".format(DOMAIN), "-out", wilddomain_csr.name]).wait()

65
    # weak 1024 bit account key
66
    weak_key = NamedTemporaryFile(delete=False)
67 68 69
    Popen(["openssl", "genrsa", "-out", weak_key.name, "1024"]).wait()

    # CSR using subject alt-name domain instead of CN (common name)
70 71
    san_csr = NamedTemporaryFile(delete=False)
    san_conf = NamedTemporaryFile(delete=False)
72
    san_conf.write(open("/etc/ssl/openssl.cnf").read().encode("utf8"))
73
    san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:www.{0}\n".format(DOMAIN).encode("utf8"))
74
    san_conf.seek(0)
75
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
76 77 78
        "-subj", "/", "-reqexts", "SAN", "-config", san_conf.name,
        "-out", san_csr.name]).wait()

79 80 81 82 83 84 85 86 87 88
    # CSR using wildcard in subject alt-name domain
    wildsan_csr = NamedTemporaryFile(delete=False)
    wildsan_conf = NamedTemporaryFile(delete=False)
    wildsan_conf.write(open("/etc/ssl/openssl.cnf").read().encode("utf8"))
    wildsan_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:*.{0}\n".format(DOMAIN).encode("utf8"))
    wildsan_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
           "-subj", "/", "-reqexts", "SAN", "-config", wildsan_conf.name,
           "-out", wildsan_csr.name]).wait()

89
    # CSR signed with the account key
90
    account_csr = NamedTemporaryFile(delete=False)
91
    Popen(["openssl", "req", "-new", "-sha256", "-key", account_key,
92
        "-subj", "/CN={0}".format(DOMAIN), "-out", account_csr.name]).wait()
Adrien Dorsaz's avatar
Adrien Dorsaz committed
93

94
    # Create config parser from the good default config to generate custom configs
95
    config = configparser.ConfigParser()
96
    config.read(goodCName)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
97

98
    goodCNameWithoutCSR = NamedTemporaryFile(delete=False)
99
    config.remove_option("acmednstiny", "CSRFile")
100 101 102
    with open(goodCNameWithoutCSR.name, 'w') as configfile:
        config.write(configfile)

103 104 105 106 107
    wildCName = NamedTemporaryFile(delete=False)
    config["acmednstiny"]["CSRFile"] = wilddomain_csr.name
    with open(wildCName.name, 'w') as configfile:
        config.write(configfile)

108
    dnsHostIP = NamedTemporaryFile(delete=False)
109 110 111 112
    config["DNS"]["Host"] = DNSHOSTIP
    with open(dnsHostIP.name, 'w') as configfile:
        config.write(configfile)
    config["DNS"]["Host"] = DNSHOST
Adrien Dorsaz's avatar
Adrien Dorsaz committed
113

114
    goodSAN = NamedTemporaryFile(delete=False)
115 116 117
    config["acmednstiny"]["CSRFile"] = san_csr.name
    with open(goodSAN.name, 'w') as configfile:
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
118

119 120 121 122 123
    wildSAN = NamedTemporaryFile(delete=False)
    config["acmednstiny"]["CSRFile"] = wildsan_csr.name
    with open(wildSAN.name, 'w') as configfile:
        config.write(configfile)

124
    weakKey = NamedTemporaryFile(delete=False)
125
    config["acmednstiny"]["AccountKeyFile"] = weak_key.name
126
    config["acmednstiny"]["CSRFile"] = domain_csr
127 128
    with open(weakKey.name, 'w') as configfile:
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
129

130
    accountAsDomain = NamedTemporaryFile(delete=False)
131
    config["acmednstiny"]["AccountKeyFile"] = account_key
132 133 134
    config["acmednstiny"]["CSRFile"] = account_csr.name
    with open(accountAsDomain.name, 'w') as configfile:
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
135

136
    invalidTSIGName = NamedTemporaryFile(delete=False)
137
    config["TSIGKeyring"]["KeyName"] = "{0}.invalid".format(TSIGKEYNAME)
138
    config["acmednstiny"]["CSRFile"] = domain_csr
139 140
    with open(invalidTSIGName.name, 'w') as configfile:
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
141

142
    missingDNS = NamedTemporaryFile(delete=False)
143 144 145
    config["DNS"] = {}
    with open(missingDNS.name, 'w') as configfile:
        config.write(configfile)
Adrien Dorsaz's avatar
Adrien Dorsaz committed
146

147
    return {
148
        # configs
149
        "goodCName": goodCName,
150
        "goodCNameWithoutCSR": goodCNameWithoutCSR.name,
151
        "wildCName": wildCName.name,
152 153
        "dnsHostIP": dnsHostIP.name,
        "goodSAN": goodSAN.name,
154
        "wildSAN": wildSAN.name,
155 156 157 158
        "weakKey": weakKey.name,
        "accountAsDomain": accountAsDomain.name,
        "invalidTSIGName": invalidTSIGName.name,
        "missingDNS": missingDNS.name,
159
        # key (just to simply remove the account from staging server)
160
        "accountkey": account_key,
161 162
        # CName CSR file to use with goodCNameWithoutCSR
        "cnameCSR": domain_csr,
163 164
    }

165
# generate two account keys to roll over them
166
def generate_acme_account_rollover_config():
167 168
    # Old account is directly created by the config generator
    old_account_key, domain_key, domain_csr, config = generate_config()
169

170
    # New account key
171
    new_account_key = NamedTemporaryFile(delete=False)
172
    Popen(["openssl", "genrsa", "-out", new_account_key.name, "2048"]).wait()
173

174
    return {
175
        # config and keys (returned to keep files on system)
176
        "config": config,
177
        "oldaccountkey": old_account_key,
178
        "newaccountkey": new_account_key.name
179
    }
180 181

# generate an account key to delete it
182
def generate_acme_account_deactivate_config():
183 184
    # Account key is created by the by the config generator
    account_key, domain_key, domain_csr, config = generate_config()
185 186

    return {
187
        "config": config,
188 189
        "key": account_key
    }