diff options
Diffstat (limited to 'test/modules/md/test_300_conf_validate.py')
-rw-r--r-- | test/modules/md/test_300_conf_validate.py | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/test/modules/md/test_300_conf_validate.py b/test/modules/md/test_300_conf_validate.py new file mode 100644 index 0000000..85371ba --- /dev/null +++ b/test/modules/md/test_300_conf_validate.py @@ -0,0 +1,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) |