summaryrefslogtreecommitdiffstats
path: root/test/modules/md/test_300_conf_validate.py
blob: 85371ba227b9a5635c2c12db58ff47ca74349a96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# test mod_md basic configurations

import re
import time
from datetime import datetime, timedelta

import pytest

from .md_conf import MDConf
from .md_env import MDTestEnv


@pytest.mark.skipif(condition=not MDTestEnv.has_acme_server(),
                    reason="no ACME test server configured")
class TestConf:

    @pytest.fixture(autouse=True, scope='class')
    def _class_scope(self, env):
        env.clear_store()

    # test case: just one MDomain definition
    def test_md_300_001(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org
            """).install()
        assert env.apache_restart() == 0

    # test case: two MDomain definitions, non-overlapping
    def test_md_300_002(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org
            MDomain example2.org www.example2.org mail.example2.org
            """).install()
        assert env.apache_restart() == 0

    # test case: two MDomain definitions, exactly the same
    def test_md_300_003(self, env):
        assert env.apache_stop() == 0
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            """).install()
        assert env.apache_fail() == 0

    # test case: two MDomain definitions, overlapping
    def test_md_300_004(self, env):
        assert env.apache_stop() == 0
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            MDomain example2.org test3.not-forbidden.org www.example2.org mail.example2.org
            """).install()
        assert env.apache_fail() == 0

    # test case: two MDomains, one inside a virtual host
    def test_md_300_005(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            <VirtualHost *:12346>
                MDomain example2.org www.example2.org www.example3.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: two MDomains, one correct vhost name
    def test_md_300_006(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            <VirtualHost *:12346>
                ServerName example2.org
                MDomain example2.org www.example2.org www.example3.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: two MDomains, two correct vhost names
    def test_md_300_007(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            <VirtualHost *:12346>
                ServerName example2.org
                MDomain example2.org www.example2.org www.example3.org
            </VirtualHost>
            <VirtualHost *:12346>
                ServerName www.example2.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: two MDomains, overlapping vhosts
    def test_md_300_008(self, env):
        MDConf(env, text="""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            <VirtualHost *:12346>
                ServerName example2.org
                ServerAlias www.example3.org
                MDomain example2.org www.example2.org www.example3.org
            </VirtualHost>

            <VirtualHost *:12346>
                ServerName www.example2.org
                ServerAlias example2.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: vhosts with overlapping MDs
    def test_md_300_009(self, env):
        assert env.apache_stop() == 0
        conf = MDConf(env)
        conf.add("""
            MDMembers manual
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
            MDomain example2.org www.example2.org www.example3.org
            """)
        conf.add_vhost(port=12346, domains=["example2.org", "www.example3.org"], with_ssl=True)
        conf.add_vhost(port=12346, domains=["www.example2.org", "example2.org"], with_ssl=True)
        conf.add_vhost(port=12346, domains=["not-forbidden.org", "example2.org"], with_ssl=True)
        conf.install()
        assert env.apache_fail() == 0
        env.apache_stop()
        env.httpd_error_log.ignore_recent()

    # test case: MDomain, vhost with matching ServerAlias
    def test_md_300_010(self, env):
        conf = MDConf(env)
        conf.add("""
            MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org

            <VirtualHost *:12346>
                ServerName not-forbidden.org
                ServerAlias test3.not-forbidden.org
            </VirtualHost>
            """)
        conf.install()
        assert env.apache_restart() == 0

    # test case: MDomain, misses one ServerAlias
    def test_md_300_011a(self, env):
        env.apache_stop()
        conf = MDConf(env, text="""
            MDomain not-forbidden.org manual www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
        """)
        conf.add_vhost(port=env.https_port, domains=[
            "not-forbidden.org", "test3.not-forbidden.org", "test4.not-forbidden.org"
        ])
        conf.install()
        assert env.apache_fail() == 0
        env.apache_stop()

    # test case: MDomain, misses one ServerAlias, but auto add enabled
    def test_md_300_011b(self, env):
        env.apache_stop()
        MDConf(env, text="""
            MDomain not-forbidden.org auto mail.not-forbidden.org

            <VirtualHost *:%s>
                ServerName not-forbidden.org
                ServerAlias test3.not-forbidden.org
                ServerAlias test4.not-forbidden.org
            </VirtualHost>
            """ % env.https_port).install()
        assert env.apache_restart() == 0

    # test case: MDomain does not match any vhost
    def test_md_300_012(self, env):
        MDConf(env, text="""
            MDomain example012.org www.example012.org
            <VirtualHost *:12346>
                ServerName not-forbidden.org
                ServerAlias test3.not-forbidden.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: one md covers two vhosts
    def test_md_300_013(self, env):
        MDConf(env, text="""
            MDomain example2.org test-a.example2.org test-b.example2.org
            <VirtualHost *:12346>
                ServerName test-a.example2.org
            </VirtualHost>
            <VirtualHost *:12346>
                ServerName test-b.example2.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: global server name as managed domain name
    def test_md_300_014(self, env):
        MDConf(env, text=f"""
            MDomain www.{env.http_tld} www.example2.org

            <VirtualHost *:12346>
                ServerName www.example2.org
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: valid pkey specification
    def test_md_300_015(self, env):
        MDConf(env, text="""
            MDPrivateKeys Default
            MDPrivateKeys RSA
            MDPrivateKeys RSA 2048
            MDPrivateKeys RSA 3072
            MDPrivateKeys RSA 4096
            """).install()
        assert env.apache_restart() == 0

    # test case: invalid pkey specification
    @pytest.mark.parametrize("line,exp_err_msg", [
        ("MDPrivateKeys", "needs to specify the private key type"), 
        ("MDPrivateKeys Default RSA 1024", "'Default' allows no other parameter"),
        ("MDPrivateKeys RSA 1024", "must be 2048 or higher"),
        ("MDPrivateKeys RSA 1024", "must be 2048 or higher"),
        ("MDPrivateKeys rsa 2048 rsa 4096", "two keys of type 'RSA' are not possible"),
        ("MDPrivateKeys p-256 secp384r1 P-256", "two keys of type 'P-256' are not possible"),
        ])
    def test_md_300_016(self, env, line, exp_err_msg):
        MDConf(env, text=line).install()
        assert env.apache_fail() == 0
        assert exp_err_msg in env.apachectl_stderr

    # test case: invalid renew window directive
    @pytest.mark.parametrize("line,exp_err_msg", [
        ("MDRenewWindow dec-31", "has unrecognized format"), 
        ("MDRenewWindow 1y", "has unrecognized format"), 
        ("MDRenewWindow 10 d", "takes one argument"), 
        ("MDRenewWindow 102%", "a length of 100% or more is not allowed.")])
    def test_md_300_017(self, env, line, exp_err_msg):
        MDConf(env, text=line).install()
        assert env.apache_fail() == 0
        assert exp_err_msg in env.apachectl_stderr

    # test case: invalid uri for MDProxyPass
    @pytest.mark.parametrize("line,exp_err_msg", [
        ("MDHttpProxy", "takes one argument"), 
        ("MDHttpProxy localhost:8080", "scheme must be http or https"),
        ("MDHttpProxy https://127.0.0.1:-443", "invalid port"),
        ("MDHttpProxy HTTP localhost 8080", "takes one argument")])
    def test_md_300_018(self, env, line, exp_err_msg):
        MDConf(env, text=line).install()
        assert env.apache_fail() == 0, "Server accepted test config {}".format(line)
        assert exp_err_msg in env.apachectl_stderr

    # test case: invalid parameter for MDRequireHttps
    @pytest.mark.parametrize("line,exp_err_msg", [
        ("MDRequireHTTPS yes", "supported parameter values are 'temporary' and 'permanent'"),
        ("MDRequireHTTPS", "takes one argument")])
    def test_md_300_019(self, env, line, exp_err_msg):
        MDConf(env, text=line).install()
        assert env.apache_fail() == 0, "Server accepted test config {}".format(line)
        assert exp_err_msg in env.apachectl_stderr

    # test case: invalid parameter for MDMustStaple
    @pytest.mark.parametrize("line,exp_err_msg", [
        ("MDMustStaple", "takes one argument"), 
        ("MDMustStaple yes", "supported parameter values are 'on' and 'off'"),
        ("MDMustStaple true", "supported parameter values are 'on' and 'off'")])
    def test_md_300_020(self, env, line, exp_err_msg):
        MDConf(env, text=line).install()
        assert env.apache_fail() == 0, "Server accepted test config {}".format(line)
        assert exp_err_msg in env.apachectl_stderr
        env.httpd_error_log.ignore_recent()

    # test case: alt-names incomplete detection, github isse #68
    def test_md_300_021(self, env):
        env.apache_stop()
        conf = MDConf(env, text="""
            MDMembers manual
            MDomain secret.com
            """)
        conf.add_vhost(port=12344, domains=[
            "not.secret.com", "secret.com"
        ])
        conf.install()
        assert env.apache_fail() == 0
        # this is unreliable on debian
        #assert env.httpd_error_log.scan_recent(
        #    re.compile(r'.*Virtual Host not.secret.com:0 matches Managed Domain \'secret.com\', '
        #               'but the name/alias not.secret.com itself is not managed. A requested '
        #               'MD certificate will not match ServerName.*'), timeout=10
        #)

    # test case: use MDRequireHttps in an <if> construct, but not in <Directory
    def test_md_300_022(self, env):
        MDConf(env, text="""
            MDomain secret.com
            <If "1 == 1">
              MDRequireHttps temporary
            </If>
            <VirtualHost *:12344>
                ServerName secret.com
            </VirtualHost>
            """).install()
        assert env.apache_restart() == 0

    # test case: use MDRequireHttps not in <Directory
    def test_md_300_023(self, env):
        conf = MDConf(env, text="""
            MDomain secret.com
            <Directory /tmp>
              MDRequireHttps temporary
            </Directory>
            """)
        conf.add_vhost(port=12344, domains=["secret.com"])
        conf.install()
        assert env.apache_fail() == 0

    # test case: invalid parameter for MDCertificateAuthority
    @pytest.mark.parametrize("ca,exp_err_msg", [
        ("", "takes one argument"),
        ("yes", "The CA name 'yes' is not known "),
    ])
    def test_md_300_024(self, env, ca, exp_err_msg):
        conf = MDConf(env, text=f"""
            MDCertificateAuthority {ca}
            MDRenewMode manual  # lets not contact these in testing
        """)
        conf.install()
        assert env.apache_fail() == 0
        assert exp_err_msg in env.apachectl_stderr

    # test case: valid parameter for MDCertificateAuthority
    @pytest.mark.parametrize("ca, url", [
        ("LetsEncrypt", "https://acme-v02.api.letsencrypt.org/directory"),
        ("letsencrypt", "https://acme-v02.api.letsencrypt.org/directory"),
        ("letsencrypt-test", "https://acme-staging-v02.api.letsencrypt.org/directory"),
        ("LETSEncrypt-TESt", "https://acme-staging-v02.api.letsencrypt.org/directory"),
        ("buypass", "https://api.buypass.com/acme/directory"),
        ("buypass-test", "https://api.test4.buypass.no/acme/directory"),
    ])
    def test_md_300_025(self, env, ca, url):
        domain = f"test1.{env.http_tld}"
        conf = MDConf(env, text=f"""
            MDCertificateAuthority {ca}
            MDRenewMode manual
        """)
        conf.add_md([domain])
        conf.install()
        assert env.apache_restart() == 0, "Server did not accepted CA '{}'".format(ca)
        md = env.get_md_status(domain)
        assert md['ca']['urls'][0] == url, f"CA url '{url}' not set in {md}"

    # vhost on another address, see #278
    def test_md_300_026(self, env):
        assert env.apache_stop() == 0
        conf = MDConf(env)
        domain = f"t300_026.{env.http_tld}"
        conf.add(f"""
            MDomain {domain}
            """)
        conf.add_vhost(port=env.http_port, domains=[domain], with_ssl=False)
        conf.add(f"""
            <VirtualHost 10.0.0.1:{env.https_port}>
              ServerName {domain}
              ServerAlias xxx.{env.http_tld}
              SSLEngine on
            </VirtualHost>
            <VirtualHost 10.0.0.1:12345>
              ServerName {domain}
              SSLEngine on
            </VirtualHost>
            """)
        conf.install()
        assert env.apache_restart() == 0

    # test case: configure more than 1 CA
    @pytest.mark.parametrize("cas, should_work", [
        (["https://acme-v02.api.letsencrypt.org/directory"], True),
        (["https://acme-v02.api.letsencrypt.org/directory", "buypass"], True),
        (["x", "buypass"], False),
        (["letsencrypt", "abc"], False),
        (["letsencrypt", "buypass"], True),
    ])
    def test_md_300_027(self, env, cas, should_work):
        domain = f"test1.{env.http_tld}"
        conf = MDConf(env, text=f"""
            MDCertificateAuthority {' '.join(cas)}
            MDRenewMode manual
        """)
        conf.add_md([domain])
        conf.install()
        rv = env.apache_restart()
        if should_work:
            assert rv == 0, "Server did not accepted CAs '{}'".format(cas)
            md = env.get_md_status(domain)
            assert len(md['ca']['urls']) == len(cas)
        else:
            assert rv != 0, "Server should not have accepted CAs '{}'".format(cas)