From 8ffa19a1f7eb03b156e6bdbda65b3d2a2de9dfe8 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 8 Apr 2024 11:24:18 +0000 Subject: [PATCH] mod_md: update to v2.4.26 - Using OCSP stapling information to trigger certificate renewals. Proposed by @frasertweedale. - Added directive `MDCheckInterval` to control how often the server checks for detected revocations. Added proposals for configurations in the README.md chapter "Revocations". - OCSP stapling: accept OCSP responses without a `nextUpdate` entry which is allowed in RFC 6960. Treat those as having an update interval of 12 hours. Added by @frasertweedale. - Adapt OpenSSL usage to changes in their API. By Yann Ylavic. Test Updates - workarounds for using Pebble v2.5 - disable EAB tests for Pebble since v2.5 no longer supports HS256 FWT for EAB keys - some stability improvemnets in error/warning checks git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1916861 13f79535-47bb-0310-9956-ffa450edef68 --- changes-entries/md_2.4.26.txt | 10 + docs/manual/mod/mod_md.xml | 25 +++ modules/md/md_ocsp.c | 15 +- modules/md/md_reg.c | 36 ++++ modules/md/md_reg.h | 7 + modules/md/md_version.h | 4 +- modules/md/mod_md_config.c | 22 ++- modules/md/mod_md_config.h | 1 + modules/md/mod_md_drive.c | 20 +- test/modules/md/conftest.py | 3 +- test/modules/md/md_cert_util.py | 4 +- test/modules/md/md_env.py | 15 +- test/modules/md/test_300_conf_validate.py | 31 ++- test/modules/md/test_310_conf_store.py | 220 +--------------------- test/modules/md/test_502_acmev2_drive.py | 10 - test/modules/md/test_602_roundtrip.py | 16 +- test/modules/md/test_750_eab.py | 21 ++- 17 files changed, 177 insertions(+), 283 deletions(-) create mode 100644 changes-entries/md_2.4.26.txt diff --git a/changes-entries/md_2.4.26.txt b/changes-entries/md_2.4.26.txt new file mode 100644 index 00000000000..9b82f611f18 --- /dev/null +++ b/changes-entries/md_2.4.26.txt @@ -0,0 +1,10 @@ + * mod_md: + - Using OCSP stapling information to trigger certificate renewals. Proposed + by @frasertweedale. + - Added directive `MDCheckInterval` to control how often the server checks + for detected revocations. Added proposals for configurations in the + README.md chapter "Revocations". + - OCSP stapling: accept OCSP responses without a `nextUpdate` entry which is + allowed in RFC 6960. Treat those as having an update interval of 12 hours. + Added by @frasertweedale. + - Adapt OpenSSL usage to changes in their API. By Yann Ylavic. diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml index d4adac2114e..b136ca0d73a 100644 --- a/docs/manual/mod/mod_md.xml +++ b/docs/manual/mod/mod_md.xml @@ -1512,4 +1512,29 @@ MDMessageCmd /etc/apache/md-message + + MDCheckInterval + Determines how often certificates are checked + MDCheckInterval duration + MDCheckInterval 12h + + server config + + Available in version 2.4.60 and later + +

+ The time between certificate checks. By default, the validity + and need for renewals is checked twice a day. This interval is + not followed precisely. Instead the module randomly applies + a +/-50% jitter to it. With the default of 12 hours, this + means the actual time between runs varies between 6 and 18 + hours, jittered anew every run. This helps to mitigate + traffic peaks at ACME servers. +

+ The minimum duration you may configure is 1 second. It is + not recommended to use such short times in production. +

+
+
+ diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c index c957c1d9cd3..8276137c3e8 100644 --- a/modules/md/md_ocsp.c +++ b/modules/md/md_ocsp.c @@ -678,12 +678,6 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) md_result_log(update->result, MD_LOG_DEBUG); goto cleanup; } - if (!bnextup) { - rv = APR_EINVAL; - md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates"); - md_result_log(update->result, MD_LOG_DEBUG); - goto cleanup; - } /* Coming here, we have a response for our certid and it is either GOOD * or REVOKED. Both cases we want to remember and use in stapling. */ @@ -698,7 +692,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) new_der.free_data = md_openssl_free; nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED; valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now(); - valid.end = md_asn1_generalized_time_get(bnextup); + if (bnextup) { + valid.end = md_asn1_generalized_time_get(bnextup); + } + else { + /* nextUpdate not set; default to 12 hours. + * Refresh attempts will be started some time earlier. */ + valid.end = valid.start + apr_time_from_sec(MD_SECS_PER_DAY / 2); + } /* First, update the instance with a copy */ apr_thread_mutex_lock(ostat->reg->mutex); diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 8bceb0eb470..6aa7d788769 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -31,6 +31,7 @@ #include "md_json.h" #include "md_result.h" #include "md_reg.h" +#include "md_ocsp.h" #include "md_store.h" #include "md_status.h" #include "md_tailscale.h" @@ -1321,3 +1322,38 @@ md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p) { return md_job_make(p, reg->store, MD_SG_STAGING, mdomain, reg->min_delay); } + +static int get_cert_count(const md_t *md) +{ + if (md->cert_files && md->cert_files->nelts) { + return md->cert_files->nelts; + } + return md_pkeys_spec_count(md->pks); +} + +int md_reg_has_revoked_certs(md_reg_t *reg, struct md_ocsp_reg_t *ocsp, + const md_t *md, apr_pool_t *p) +{ + const md_pubcert_t *pubcert; + const md_cert_t *cert; + md_timeperiod_t ocsp_valid; + md_ocsp_cert_stat_t cert_stat; + apr_status_t rv = APR_SUCCESS; + int i; + + if (!md->stapling || !ocsp) + return 0; + + for (i = 0; i < get_cert_count(md); ++i) { + if (APR_SUCCESS != md_reg_get_pubcert(&pubcert, reg, md, i, p)) + continue; + cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*); + if(!cert) + continue; + rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md); + if (APR_SUCCESS == rv && cert_stat == MD_OCSP_CERT_ST_REVOKED) { + return 1; + } + } + return 0; +} diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index 58ee16ac62f..191b026e46a 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -23,6 +23,7 @@ struct md_pkey_t; struct md_cert_t; struct md_result_t; struct md_pkey_spec_t; +struct md_ocsp_reg_t; #include "md_store.h" @@ -310,4 +311,10 @@ apr_status_t md_reg_lock_global(md_reg_t *reg, apr_pool_t *p); */ void md_reg_unlock_global(md_reg_t *reg, apr_pool_t *p); +/** + * @return != 0 iff `md` has any certificates known to be REVOKED. + */ +int md_reg_has_revoked_certs(md_reg_t *reg, struct md_ocsp_reg_t *ocsp, + const md_t *md, apr_pool_t *p); + #endif /* mod_md_md_reg_h */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 86a1821c7c8..cefbb8ded72 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -27,7 +27,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "2.4.25" +#define MOD_MD_VERSION "2.4.26" /** * @macro @@ -35,7 +35,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_MD_VERSION_NUM 0x020419 +#define MOD_MD_VERSION_NUM 0x02041a #define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory" #define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock" diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 31d06b4bc5b..cdd1e297c74 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -84,6 +84,7 @@ static md_mod_conf_t defmc = { "crt.sh", /* default cert checker site name */ "https://crt.sh?q=", /* default cert checker site url */ NULL, /* CA cert file to use */ + apr_time_from_sec(MD_SECS_PER_DAY/2), /* default time between cert checks */ apr_time_from_sec(5), /* minimum delay for retries */ 13, /* retry_failover after 14 errors, with 5s delay ~ half a day */ 0, /* store locks, disabled by default */ @@ -624,6 +625,24 @@ static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const cha return set_on_off(&config->mc->manage_base_server, value, cmd->pool); } +static const char *md_config_set_check_interval(cmd_parms *cmd, void *dc, const char *value) +{ + md_srv_conf_t *config = md_config_get(cmd->server); + const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD); + apr_time_t interval; + + (void)dc; + if (err) return err; + if (md_duration_parse(&interval, value, "s") != APR_SUCCESS) { + return "unrecognized duration format"; + } + if (interval < apr_time_from_sec(1)) { + return "check interval cannot be less than one second"; + } + config->mc->check_interval = interval; + return NULL; +} + static const char *md_config_set_min_delay(cmd_parms *cmd, void *dc, const char *value) { md_srv_conf_t *config = md_config_get(cmd->server); @@ -1304,7 +1323,8 @@ const command_rec md_cmds[] = { "Configure locking of store for updates."), AP_INIT_TAKE1("MDMatchNames", md_config_set_match_mode, NULL, RSRC_CONF, "Determines how DNS names are matched to vhosts."), - + AP_INIT_TAKE1("MDCheckInterval", md_config_set_check_interval, NULL, RSRC_CONF, + "Time between certificate checks."), AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) }; diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 7e87440ed18..1ce2375f00d 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -75,6 +75,7 @@ struct md_mod_conf_t { const char *cert_check_name; /* name of the linked certificate check site */ const char *cert_check_url; /* url "template for" checking a certificate */ const char *ca_certs; /* root certificates to use for connections */ + apr_time_t check_interval; /* duration between cert renewal checks */ apr_time_t min_delay; /* minimum delay for retries */ int retry_failover; /* number of errors to trigger CA failover */ int use_store_locks; /* use locks when updating store */ diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c index 5565f44d758..f131f07b888 100644 --- a/modules/md/mod_md_drive.c +++ b/modules/md/mod_md_drive.c @@ -100,7 +100,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p } if (md_will_renew_cert(md)) { - /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly + /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly * without discarding previous/intermediate results. * Only returns SUCCESS when the renewal is complete, e.g. STAGING has a * complete set of new credentials. @@ -108,7 +108,12 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052) "md(%s): state=%d, driving", job->mdomain, md->state); - if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) { + if (md->stapling && dctx->mc->ocsp && + md_reg_has_revoked_certs(dctx->mc->reg, dctx->mc->ocsp, md, dctx->p)) { + ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO() + "md(%s): has revoked certificates", job->mdomain); + } + else if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053) "md(%s): no need to renew", job->mdomain); goto expiry; @@ -180,10 +185,13 @@ int md_will_renew_cert(const md_t *md) return 1; } -static apr_time_t next_run_default(void) +static apr_time_t next_run_default(md_renew_ctx_t *dctx) { - /* we'd like to run at least twice a day by default */ - return apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2); + unsigned char c; + apr_time_t delay = dctx->mc->check_interval; + + md_rand_bytes(&c, sizeof(c), dctx->p); + return apr_time_now() + delay + (delay * (c - 128) / 256); } static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) @@ -211,7 +219,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) * and we schedule ourself at the earliest of all. A job may specify 0 * as next_run to indicate that it wants to participate in the normal * regular runs. */ - next_run = next_run_default(); + next_run = next_run_default(dctx); for (i = 0; i < dctx->jobs->nelts; ++i) { job = APR_ARRAY_IDX(dctx->jobs, i, md_job_t *); diff --git a/test/modules/md/conftest.py b/test/modules/md/conftest.py index 192cd31a80b..0118de5e133 100755 --- a/test/modules/md/conftest.py +++ b/test/modules/md/conftest.py @@ -32,7 +32,8 @@ def env(pytestconfig) -> MDTestEnv: env.setup_httpd() env.apache_access_log_clear() env.httpd_error_log.clear_log() - return env + yield env + env.apache_stop() @pytest.fixture(autouse=True, scope="package") diff --git a/test/modules/md/md_cert_util.py b/test/modules/md/md_cert_util.py index 8cd99aa76f6..abcd36b938c 100755 --- a/test/modules/md/md_cert_util.py +++ b/test/modules/md/md_cert_util.py @@ -166,10 +166,10 @@ class MDCertUtil(object): def get_san_list(self): text = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, self.cert).decode("utf-8") - m = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text) + m = re.search(r"X509v3 Subject Alternative Name:(\s+critical)?\s*(.*)", text) sans_list = [] if m: - sans_list = m.group(1).split(",") + sans_list = m.group(2).split(",") def _strip_prefix(s): return s.split(":")[1] if s.strip().startswith("DNS:") else s.strip() diff --git a/test/modules/md/md_env.py b/test/modules/md/md_env.py index e8e36e5b1bc..193651948ad 100755 --- a/test/modules/md/md_env.py +++ b/test/modules/md/md_env.py @@ -73,7 +73,11 @@ class MDTestEnv(HttpdTestEnv): @classmethod def has_acme_eab(cls): - return cls.get_acme_server() == 'pebble' + return False + # Pebble, since v2.5.0 no longer supports HS256 for EAB, which + # is the only thing mod_md supports. Issue opened at pebble: + # https://github.com/letsencrypt/pebble/issues/455 + # return cls.get_acme_server() == 'pebble' @classmethod def is_pebble(cls) -> bool: @@ -356,13 +360,14 @@ class MDTestEnv(HttpdTestEnv): MDCertUtil.validate_privkey(self.store_domain_file(domain, 'privkey.pem')) cert = MDCertUtil(self.store_domain_file(domain, 'pubcert.pem')) cert.validate_cert_matches_priv_key(self.store_domain_file(domain, 'privkey.pem')) - # check SANs and CN - assert cert.get_cn() == domain + # No longer check CN, it may not be set or is not trusted anyway + # assert cert.get_cn() == domain, f'CN: expected "{domain}", got {cert.get_cn()}' + # check SANs # compare lists twice in opposite directions: SAN may not respect ordering san_list = list(cert.get_san_list()) assert len(san_list) == len(domains) - assert set(san_list).issubset(domains) - assert set(domains).issubset(san_list) + assert set(san_list).issubset(domains), f'{san_list} not subset of {domains}' + assert set(domains).issubset(san_list), f'{domains} not subset of {san_list}' # check valid dates interval not_before = cert.get_not_before() not_after = cert.get_not_after() diff --git a/test/modules/md/test_300_conf_validate.py b/test/modules/md/test_300_conf_validate.py index f73bf67999d..88df1683413 100644 --- a/test/modules/md/test_300_conf_validate.py +++ b/test/modules/md/test_300_conf_validate.py @@ -15,7 +15,8 @@ from .md_env import MDTestEnv class TestConf: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env): + def _class_scope(self, env, acme): + acme.start(config='default') env.clear_store() # test case: just one MDomain definition @@ -413,7 +414,7 @@ class TestConf: def test_md_300_026(self, env): assert env.apache_stop() == 0 conf = MDConf(env) - domain = f"t300_026.{env.http_tld}" + domain = f"t300-026.{env.http_tld}" conf.add(f""" MDomain {domain} """) @@ -460,11 +461,12 @@ class TestConf: def test_md_300_028(self, env): assert env.apache_stop() == 0 conf = MDConf(env) - domaina = f"t300_028a.{env.http_tld}" - domainb = f"t300_028b.{env.http_tld}" - dalias = f"t300_028alias.{env.http_tld}" + domaina = f"t300-028a.{env.http_tld}" + domainb = f"t300-028b.{env.http_tld}" + dalias = f"t300-028alias.{env.http_tld}" conf.add_vhost(port=env.http_port, domains=[domaina, domainb, dalias], with_ssl=False) conf.add(f""" + MDMembers manual MDomain {domaina} MDomain {domainb} {dalias} """) @@ -481,23 +483,28 @@ class TestConf: """) conf.install() - # This does not work as we have both MDs match domaina's vhost + # This does not work as we have both MDs match domain's vhost assert env.apache_fail() == 0 env.httpd_error_log.ignore_recent( - lognos = [ - "AH10238" # 2 MDs match the same vhost + lognos=[ + "AH10238", # 2 MDs match the same vhost ] ) # It works, if we only match on ServerNames conf.add("MDMatchNames servernames") conf.install() assert env.apache_restart() == 0 + env.httpd_error_log.ignore_recent( + lognos=[ + "AH10040", # ServerAlias not covered + ] + ) # wildcard and specfic MD overlaps def test_md_300_029(self, env): assert env.apache_stop() == 0 conf = MDConf(env) - domain = f"t300_029.{env.http_tld}" + domain = f"t300-029.{env.http_tld}" subdomain = f"sub.{domain}" conf.add_vhost(port=env.http_port, domains=[domain, subdomain], with_ssl=False) conf.add(f""" @@ -531,4 +538,10 @@ class TestConf: conf.add("MDMatchNames servernames") conf.install() assert env.apache_restart() == 0 + time.sleep(2) + assert env.apache_stop() == 0 + # we need dns-01 challenge for the wildcard, which is not configured + env.httpd_error_log.ignore_recent(matches=[ + r'.*None of offered challenge types.*are supported.*' + ]) diff --git a/test/modules/md/test_310_conf_store.py b/test/modules/md/test_310_conf_store.py index d56790bb1fb..f2bb9c723ac 100644 --- a/test/modules/md/test_310_conf_store.py +++ b/test/modules/md/test_310_conf_store.py @@ -48,11 +48,6 @@ class TestConf: assert env.apache_restart() == 0 for i in range(0, len(dns_lists)): env.check_md(dns_lists[i], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add managed domains as separate steps def test_md_310_101(self, env): @@ -68,11 +63,6 @@ class TestConf: assert env.apache_restart() == 0 env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) env.check_md(["testdomain2.org", "www.testdomain2.org", "mail.testdomain2.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add dns to existing md def test_md_310_102(self, env): @@ -82,11 +72,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add new md definition with acme url, acme protocol, acme agreement def test_md_310_103(self, env): @@ -102,11 +87,6 @@ class TestConf: env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, ca="http://acme.test.org:4000/directory", protocol="ACME", agreement="http://acme.test.org:4000/terms/v1") - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add to existing md: acme url, acme protocol def test_md_310_104(self, env): @@ -128,11 +108,6 @@ class TestConf: env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, ca="http://acme.test.org:4000/directory", protocol="ACME", agreement="http://acme.test.org:4000/terms/v1") - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add new md definition with server admin def test_md_310_105(self, env): @@ -143,11 +118,6 @@ class TestConf: name = "testdomain.org" env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, contacts=["mailto:admin@testdomain.org"]) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: add to existing md: server admin def test_md_310_106(self, env): @@ -159,11 +129,6 @@ class TestConf: assert env.apache_restart() == 0 env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, contacts=["mailto:admin@testdomain.org"]) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: assign separate contact info based on VirtualHost def test_md_310_107(self, env): @@ -196,11 +161,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: default drive mode - auto def test_md_310_109(self, env): @@ -209,11 +169,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-mode'] == 1 - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: drive mode manual def test_md_310_110(self, env): @@ -223,11 +178,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-mode'] == 0 - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: drive mode auto def test_md_310_111(self, env): @@ -237,11 +187,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-mode'] == 1 - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: drive mode always def test_md_310_112(self, env): @@ -260,11 +205,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-window'] == '14d' - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: renew window - 10 percent def test_md_310_113b(self, env): @@ -274,12 +214,7 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-window'] == '10%' - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) - + # test case: ca challenge type - http-01 def test_md_310_114(self, env): MDConf(env, text=""" @@ -288,11 +223,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['ca']['challenges'] == ['http-01'] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: ca challenge type - http-01 def test_md_310_115(self, env): @@ -302,11 +232,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['ca']['challenges'] == ['tls-alpn-01'] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: ca challenge type - all def test_md_310_116(self, env): @@ -316,11 +241,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['ca']['challenges'] == ['http-01', 'tls-alpn-01'] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: automatically collect md names from vhost config def test_md_310_117(self, env): @@ -349,11 +269,6 @@ class TestConf: assert env.apache_restart() == 0 stat = env.get_md_status("testdomain.org") assert stat['renew-window'] == '14d' - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: set RSA key length 2048 def test_md_310_119(self, env): @@ -366,11 +281,6 @@ class TestConf: "type": "RSA", "bits": 2048 } - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: set RSA key length 4096 def test_md_310_120(self, env): @@ -383,11 +293,6 @@ class TestConf: "type": "RSA", "bits": 4096 } - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: require HTTPS def test_md_310_121(self, env): @@ -397,12 +302,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['require-https'] == "temporary" - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045", # No VirtualHost matches Managed Domain - "AH10105" # no domain match - ] - ) # test case: require OCSP stapling def test_md_310_122(self, env): @@ -412,11 +311,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['must-staple'] is True - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove managed domain from config def test_md_310_200(self, env): @@ -440,11 +334,6 @@ class TestConf: assert env.apache_restart() == 0 # check: DNS has been removed from md in store env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove primary name from managed domain def test_md_310_202(self, env): @@ -458,11 +347,6 @@ class TestConf: # check: md overwrite previous name and changes name env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], md="testdomain.org", state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove one md, keep another def test_md_310_203(self, env): @@ -479,11 +363,6 @@ class TestConf: # all mds stay in store env.check_md(dns_list1, state=1) env.check_md(dns_list2, state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove ca info from md, should switch over to new defaults def test_md_310_204(self, env): @@ -503,11 +382,6 @@ class TestConf: assert env.apache_restart() == 0 env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, ca="https://acme-v02.api.letsencrypt.org/directory", protocol="ACME") - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove server admin from md def test_md_310_205(self, env): @@ -524,11 +398,6 @@ class TestConf: # check: md stays the same with previous admin info env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, contacts=["mailto:admin@testdomain.org"]) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove renew window from conf -> fallback to default def test_md_310_206(self, env): @@ -544,11 +413,6 @@ class TestConf: assert env.apache_restart() == 0 # check: renew window not set assert env.a2md(["list"]).json['output'][0]['renew-window'] == '33%' - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove drive mode from conf -> fallback to default (auto) @pytest.mark.parametrize("renew_mode,exp_code", [ @@ -569,11 +433,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-mode'] == 1 - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: remove challenges from conf -> fallback to default (not set) def test_md_310_208(self, env): @@ -589,11 +448,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert 'challenges' not in env.a2md(["list"]).json['output'][0]['ca'] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: specify RSA key @pytest.mark.parametrize("key_size", ["2048", "4096"]) @@ -610,11 +464,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert "privkey" not in env.a2md(["list"]).json['output'][0] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: require HTTPS @pytest.mark.parametrize("mode", ["temporary", "permanent"]) @@ -635,12 +484,6 @@ class TestConf: assert env.apache_restart() == 0 assert "require-https" not in env.a2md(["list"]).json['output'][0], \ "HTTPS require still persisted in store. config: {}".format(mode) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045", # No VirtualHost matches Managed Domain - "AH10105", # MDomain does not match any vhost - ] - ) # test case: require OCSP stapling def test_md_310_211(self, env): @@ -656,11 +499,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['must-staple'] is False - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: reorder DNS names in md definition def test_md_310_300(self, env): @@ -673,11 +511,6 @@ class TestConf: assert env.apache_restart() == 0 # check: dns list changes env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: move DNS from one md to another def test_md_310_301(self, env): @@ -693,11 +526,6 @@ class TestConf: assert env.apache_restart() == 0 env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) env.check_md(["testdomain2.org", "www.testdomain2.org", "mail.testdomain2.org"], state=1) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change ca info def test_md_310_302(self, env): @@ -724,11 +552,6 @@ class TestConf: env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, ca="http://somewhere.com:6666/directory", protocol="ACME", agreement="http://somewhere.com:6666/terms/v1") - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change server admin def test_md_310_303(self, env): @@ -749,11 +572,6 @@ class TestConf: # check: md stays the same with previous admin info env.check_md([name, "www.testdomain.org", "mail.testdomain.org"], state=1, contacts=["mailto:webmaster@testdomain.org"]) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change drive mode - manual -> auto -> always def test_md_310_304(self, env): @@ -777,11 +595,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['renew-mode'] == 2 - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change config value for renew window, use various syntax alternatives def test_md_310_305(self, env): @@ -806,11 +619,6 @@ class TestConf: assert env.apache_restart() == 0 md = env.a2md(["list"]).json['output'][0] assert md['renew-window'] == '10%' - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change challenge types - http -> tls-sni -> all def test_md_310_306(self, env): @@ -834,11 +642,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['ca']['challenges'] == ['http-01', 'tls-alpn-01'] - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: RSA key length: 4096 -> 2048 -> 4096 def test_md_310_307(self, env): @@ -869,11 +672,6 @@ class TestConf: "type": "RSA", "bits": 4096 } - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change HTTPS require settings on existing md def test_md_310_308(self, env): @@ -899,12 +697,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['require-https'] == "permanent" - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045", # No VirtualHost matches Managed Domain - "AH10105", # MDomain matches no vhost - ] - ) # test case: change OCSP stapling settings on existing md def test_md_310_309(self, env): @@ -928,11 +720,6 @@ class TestConf: """).install() assert env.apache_restart() == 0 assert env.a2md(["list"]).json['output'][0]['must-staple'] is False - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: change renew window parameter @pytest.mark.parametrize("window", [ @@ -1005,11 +792,6 @@ class TestConf: env.check_md(["testdomain.org", "www.testdomain.org", "mail.testdomain.org"], state=1) env.clear_store() env.set_store_dir_default() - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test case: place an unexpected file into the store, check startup survival, see #218 def test_md_310_501(self, env): diff --git a/test/modules/md/test_502_acmev2_drive.py b/test/modules/md/test_502_acmev2_drive.py index a98e4ad97c7..eb754f25eff 100644 --- a/test/modules/md/test_502_acmev2_drive.py +++ b/test/modules/md/test_502_acmev2_drive.py @@ -436,11 +436,6 @@ class TestDrivev2: md = env.a2md(["list", name]).json['output'][0] assert md["renew"] == tc["renew"], \ "Expected renew == {} indicator in {}, test case {}".format(tc["renew"], md, tc) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) @pytest.mark.parametrize("key_type,key_params,exp_key_length", [ ("RSA", [2048], 2048), @@ -467,11 +462,6 @@ class TestDrivev2: # check cert key length cert = MDCertUtil(env.store_domain_file(name, 'pubcert.pem')) assert cert.get_key_length() == exp_key_length - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # test_502_203 removed, as ToS agreement is not really checked in ACMEv2 diff --git a/test/modules/md/test_602_roundtrip.py b/test/modules/md/test_602_roundtrip.py index e2e74c7d81b..9ff87e5df7e 100644 --- a/test/modules/md/test_602_roundtrip.py +++ b/test/modules/md/test_602_roundtrip.py @@ -52,13 +52,9 @@ class TestRoundtripv2: # check: SSL is running OK cert = env.get_cert(domain) assert domain in cert.get_san_list() + # check file system permissions: env.check_file_permissions(domain) - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) def test_md_602_001(self, env): # test case: same as test_600_000, but with two parallel managed domains @@ -97,11 +93,6 @@ class TestRoundtripv2: assert domains_a == cert_a.get_san_list() cert_b = env.get_cert(domain_b) assert domains_b == cert_b.get_san_list() - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) def test_md_602_002(self, env): # test case: one md, that covers two vhosts @@ -143,11 +134,6 @@ class TestRoundtripv2: assert cert_a.same_serial_as(cert_b) assert env.get_content(name_a, "/name.txt") == name_a assert env.get_content(name_b, "/name.txt") == name_b - env.httpd_error_log.ignore_recent( - lognos = [ - "AH10045" # No VirtualHost matches Managed Domain - ] - ) # --------- _utils_ --------- diff --git a/test/modules/md/test_750_eab.py b/test/modules/md/test_750_eab.py index 7d81917829e..aec7e89b8c2 100644 --- a/test/modules/md/test_750_eab.py +++ b/test/modules/md/test_750_eab.py @@ -82,14 +82,17 @@ class TestEab: assert env.apache_restart() == 0 md = env.await_error(domain) assert md['renewal']['errors'] > 0 - assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized' + assert md['renewal']['last']['problem'] in [ + 'urn:ietf:params:acme:error:unauthorized', + 'urn:ietf:params:acme:error:malformed', + ] # env.httpd_error_log.ignore_recent( lognos = [ "AH10056" # the field 'kid' references a key that is not known to the ACME server ], matches = [ - r'.*urn:ietf:params:acme:error:unauthorized.*' + r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*' ] ) @@ -105,14 +108,17 @@ class TestEab: assert env.apache_restart() == 0 md = env.await_error(domain) assert md['renewal']['errors'] > 0 - assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized' + assert md['renewal']['last']['problem'] in [ + 'urn:ietf:params:acme:error:unauthorized', + 'urn:ietf:params:acme:error:malformed', + ] # env.httpd_error_log.ignore_recent( lognos = [ "AH10056" # the field 'kid' references a key that is not known to the ACME server ], matches = [ - r'.*urn:ietf:params:acme:error:unauthorized.*' + r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*' ] ) @@ -128,14 +134,17 @@ class TestEab: assert env.apache_restart() == 0 md = env.await_error(domain) assert md['renewal']['errors'] > 0 - assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized' + assert md['renewal']['last']['problem'] in [ + 'urn:ietf:params:acme:error:unauthorized', + 'urn:ietf:params:acme:error:malformed', + ] # env.httpd_error_log.ignore_recent( lognos = [ "AH10056" # external account binding JWS verification error: square/go-jose: error in cryptographic primitive ], matches = [ - r'.*urn:ietf:params:acme:error:unauthorized.*' + r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*' ] ) -- 2.47.2