]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
* mod_md:
authorStefan Eissing <icing@apache.org>
Fri, 14 Jul 2023 12:26:50 +0000 (12:26 +0000)
committerStefan Eissing <icing@apache.org>
Fri, 14 Jul 2023 12:26:50 +0000 (12:26 +0000)
   - New directive `MDMatchNames all|servernames` to allow more control over how
     MDomains are matched to VirtualHosts.
   - New directive `MDChallengeDns01Version`. Setting this to `2` will provide
     the command also with the challenge value on `teardown` invocation. In version
     1, the default, only the `setup` invocation gets this parameter.
     Refs #312. Thanks to @domrim for the idea.
   - For Managed Domain in "manual" mode, the checks if all used ServerName and
     ServerAlias are part of the MDomain now reports a warning instead of an error
     (AH10040) when not all names are present.
   - MDChallengeDns01 can now be configured for individual domains.
     Using PR from Jérôme Billiras (@bilhackmac) and adding test case and fixing proper working
   - Fixed a bug found by Jérôme Billiras (@bilhackmac) that caused the challenge
     teardown not being invoked as it should.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910996 13f79535-47bb-0310-9956-ffa450edef68

16 files changed:
changes-entries/md_v2.4.23.txt [new file with mode: 0644]
docs/manual/mod/mod_md.xml
modules/md/md.h
modules/md/md_acme_authz.c
modules/md/md_http.c
modules/md/md_util.c
modules/md/md_util.h
modules/md/md_version.h
modules/md/mod_md.c
modules/md/mod_md_config.c
modules/md/mod_md_config.h
test/modules/md/dns01_v2.py [new file with mode: 0755]
test/modules/md/test_300_conf_validate.py
test/modules/md/test_310_conf_store.py
test/modules/md/test_502_acmev2_drive.py
test/modules/md/test_602_roundtrip.py

diff --git a/changes-entries/md_v2.4.23.txt b/changes-entries/md_v2.4.23.txt
new file mode 100644 (file)
index 0000000..736e0c5
--- /dev/null
@@ -0,0 +1,14 @@
+ * mod_md:
+   - New directive `MDMatchNames all|servernames` to allow more control over how
+     MDomains are matched to VirtualHosts.
+   - New directive `MDChallengeDns01Version`. Setting this to `2` will provide
+     the command also with the challenge value on `teardown` invocation. In version
+     1, the default, only the `setup` invocation gets this parameter.
+     Refs #312. Thanks to @domrim for the idea.
+   - For Managed Domain in "manual" mode, the checks if all used ServerName and
+     ServerAlias are part of the MDomain now reports a warning instead of an error
+     (AH10040) when not all names are present.
+   - MDChallengeDns01 can now be configured for individual domains.
+     Using PR from Jérôme Billiras (@bilhackmac) and adding test case and fixing proper working
+   - Fixed a bug found by Jérôme Billiras (@bilhackmac) that caused the challenge
+     teardown not being invoked as it should.
index 454cb7fff4e99369d6641c536fae37771e5c3bc2..3a6014bcf11187c7fa79e85a35861bd7e2556e9c 100644 (file)
@@ -1033,7 +1033,9 @@ MDRequireHttps permanent
             <p>
                 Define a program to be called when the `dns-01` challenge needs to be setup/torn down. 
                 The program is given the argument `setup` or `teardown` followed by the domain name. 
-                For `setup` the challenge content is additionally given.
+                For `setup` the challenge content is additionally given. When
+                <directive module="mod_md">MDChallengeDns01Version</directive> is set to 2,
+                the `teardown` also gets the challenge content as argument.
             </p><p>
                 You do not need to specify this, as long as a 'http:' or 'https:' challenge
                 method is possible. However, Let's Encrypt makes 'dns-01' the only
@@ -1462,4 +1464,52 @@ MDMessageCmd /etc/apache/md-message
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>MDChallengeDns01Version</name>
+        <description></description>
+        <syntax>MDChallengeDns01Version 1|2</syntax>
+        <default>MDChallengeDns01Version 1</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <compatibility>Available in version 2.4.58 and later</compatibility>
+        <usage>
+            <p>
+                Set the way MDChallengeDns01 command is invoked, e.g the number and
+                types of arguments. See <directive module="mod_md">MDChallengeDns01</directive>
+                for the differences.
+                This setting is global and cannot be varied per domain.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDMatchNames</name>
+        <description></description>
+        <syntax>MDMatchNames all|servernames</syntax>
+        <default>MDMatchNames all</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <compatibility>Available in version 2.4.58 and later</compatibility>
+        <usage>
+            <p>
+                The mode `all` is the behaviour as in all previous versions. Both ServerName
+                and ServerAlias are inspected to find the MDomain matching a VirtualHost.
+                This automatically detects coverage, even when you only have added
+                one of the names to an MDomain.
+            </p><p>
+                However, this auto-magic has drawbacks in more complex setups. If you set
+                this directive to `servernames`, only the ServerName of a virtual host is
+                used for matching. ServerAliases are disregarded then, for matching.
+                Aliases will still be added to the certificate obtained, unless you also
+                run `MDMembers manual`.
+            </p><p>
+                Another advantage of `servernames` is that it gives you more flexibility
+                with sub-domains and wildcards. You can define one MDomain with a wildcard
+                and have other MDomains for specific sub-domain names.
+            </p>
+        </usage>
+    </directivesynopsis>
+
 </modulesynopsis>
index 1d75d102c8503cc4de764ad28f6ceaec248726fc..035ccba78376a98b5c75d2a43b41566b32c56cba 100644 (file)
@@ -78,12 +78,7 @@ struct md_t {
     struct apr_array_header_t *domains; /* all DNS names this MD includes */
     struct apr_array_header_t *contacts;   /* list of contact uris, e.g. mailto:xxx */
 
-    int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
-    md_require_t require_https;     /* Iff https: is required for this MD */
-    
-    int renew_mode;                 /* mode of obtaining credentials */
     struct md_pkeys_spec_t *pks;    /* specification for generating private keys */
-    int must_staple;                /* certificates should set the OCSP Must Staple extension */
     md_timeslice_t *renew_window;   /* time before expiration that starts renewal */
     md_timeslice_t *warn_window;    /* time before expiration that warnings are sent out */
     
@@ -98,19 +93,23 @@ struct md_t {
     const char *ca_eab_kid;         /* optional KEYID for external account binding */
     const char *ca_eab_hmac;        /* optional HMAC for external account binding */
 
-    md_state_t state;               /* state of this MD */
     const char *state_descr;        /* description of state of NULL */
     
     struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
-    int stapling;                   /* if OCSP stapling is enabled */
     const char *dns01_cmd;          /* DNS challenge command, override global command */
 
-    int watched;               /* if certificate is supervised (renew or expiration warning) */
     const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
     const char *defn_name;          /* config file this MD was defined */
     unsigned defn_line_number;      /* line number of definition */
-    
     const char *configured_name;    /* name this MD was configured with, if different */
+
+    int renew_mode;                 /* mode of obtaining credentials */
+    md_require_t require_https;     /* Iff https: is required for this MD */
+    md_state_t state;               /* state of this MD */
+    int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
+    int must_staple;                /* certificates should set the OCSP Must Staple extension */
+    int stapling;                   /* if OCSP stapling is enabled */
+    int watched;                    /* if certificate is supervised (renew or expiration warning) */
 };
 
 #define MD_KEY_ACCOUNT          "account"
@@ -128,6 +127,7 @@ struct md_t {
 #define MD_KEY_CHALLENGE        "challenge"
 #define MD_KEY_CHALLENGES       "challenges"
 #define MD_KEY_CMD_DNS01        "cmd-dns-01"
+#define MD_KEY_DNS01_VERSION    "cmd-dns-01-version"
 #define MD_KEY_COMPLETE         "complete"
 #define MD_KEY_CONTACT          "contact"
 #define MD_KEY_CONTACTS         "contacts"
index a55804e4686d365ffb10d04b442af42bf746d569..83e0bf153e5ad0263c7ae7331ef2d6d825fc3aad 100644 (file)
@@ -244,7 +244,8 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t
                                       md_acme_t *acme, md_store_t *store, 
                                       md_pkeys_spec_t *key_specs,
                                       apr_array_header_t *acme_tls_1_domains, const md_t *md,
-                                      apr_table_t *env, md_result_t *result, apr_pool_t *p)
+                                      apr_table_t *env, md_result_t *result,
+                                      const char **psetup_token, apr_pool_t *p)
 {
     const char *data;
     apr_status_t rv;
@@ -289,6 +290,8 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t
         rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
     }
 out:
+    *psetup_token = (APR_SUCCESS == rv)?
+        apr_psprintf(p, "%s:%s", MD_AUTHZ_TYPE_HTTP01, authz->domain) : NULL;
     return rv;
 }
 
@@ -302,7 +305,8 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth
                                           md_acme_t *acme, md_store_t *store, 
                                           md_pkeys_spec_t *key_specs,
                                           apr_array_header_t *acme_tls_1_domains, const md_t *md,
-                                          apr_table_t *env, md_result_t *result, apr_pool_t *p)
+                                          apr_table_t *env, md_result_t *result,
+                                          const char **psetup_token, apr_pool_t *p)
 {
     const char *acme_id, *token;
     apr_status_t rv;
@@ -407,6 +411,8 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth
         rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
     }
 out:    
+    *psetup_token = (APR_SUCCESS == rv)?
+        apr_psprintf(p, "%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain) : NULL;
     return rv;
 }
 
@@ -414,7 +420,8 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
                                      md_acme_t *acme, md_store_t *store, 
                                      md_pkeys_spec_t *key_specs,
                                      apr_array_header_t *acme_tls_1_domains, const md_t *md,
-                                     apr_table_t *env, md_result_t *result, apr_pool_t *p)
+                                     apr_table_t *env, md_result_t *result,
+                                     const char **psetup_token, apr_pool_t *p)
 {
     const char *token;
     const char * const *argv;
@@ -486,6 +493,8 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
     rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
     
 out:    
+    *psetup_token = (APR_SUCCESS == rv)?
+        apr_psprintf(p, "%s:%s %s", MD_AUTHZ_TYPE_DNS01, authz->domain, token) : NULL;
     return rv;
 }
 
@@ -493,7 +502,8 @@ static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, c
                                         apr_table_t *env, apr_pool_t *p)
 {
     const char * const *argv;
-    const char *cmdline, *dns01_cmd;
+    const char *cmdline, *dns01_cmd, *dns01v;
+    char *tmp, *s;
     apr_status_t rv;
     int exit_code;
     
@@ -508,7 +518,17 @@ static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, c
             md->name, domain);
         goto out;
     }
-    
+    dns01v = apr_table_get(env, MD_KEY_DNS01_VERSION);
+    if (!dns01v || strcmp(dns01v, "2")) {
+        /* use older version of teardown args with only domain, remove token */
+        tmp = apr_pstrdup(p, domain);
+        s = strchr(tmp, ' ');
+        if (s) {
+            *s = '\0';
+            domain = tmp;
+        }
+    }
+
     cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain); 
     apr_tokenize_to_argv(cmdline, (char***)&argv, p);
     if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code)) || exit_code) {
@@ -532,7 +552,8 @@ typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
                                md_acme_t *acme, md_store_t *store, 
                                md_pkeys_spec_t *key_specs,
                                apr_array_header_t *acme_tls_1_domains, const md_t *md,
-                               apr_table_t *env, md_result_t *result, apr_pool_t *p);
+                               apr_table_t *env, md_result_t *result,
+                               const char **psetup_token, apr_pool_t *p);
                                
 typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const md_t *md,
                                   apr_table_t *env, apr_pool_t *p);
@@ -590,8 +611,7 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
     apr_status_t rv;
     int i, j;
     cha_find_ctx fctx;
-    const char *challenge_setup;
-    
+
     assert(acme);
     assert(authz);
     assert(authz->resource);
@@ -613,7 +633,7 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
                       "type, this domain supports %s",
                       authz->domain, apr_array_pstrcat(p, challenges, ' '));
     rv = APR_ENOTIMPL;
-    challenge_setup = NULL;
+    *psetup_token = NULL;
     for (i = 0; i < challenges->nelts; ++i) {
         fctx.type = APR_ARRAY_IDX(challenges, i, const char *);
         fctx.accepted = NULL;
@@ -629,12 +649,12 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
                     md_result_activity_printf(result, "Setting up challenge '%s' for domain %s", 
                                               fctx.accepted->type, authz->domain);
                     rv = CHA_TYPES[j].setup(fctx.accepted, authz, acme, store, key_specs,
-                                            acme_tls_1_domains, md, env, result, p);
+                                            acme_tls_1_domains, md, env, result,
+                                            psetup_token, p);
                     if (APR_SUCCESS == rv) {
                         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                                       "%s: set up challenge '%s' for %s", 
                                       authz->domain, fctx.accepted->type, md->name);
-                        challenge_setup = CHA_TYPES[j].name;
                         goto out;
                     }
                     md_result_printf(result, rv, "error setting up challenge '%s' for %s, "
@@ -647,7 +667,6 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
     }
     
 out:
-    *psetup_token = (APR_SUCCESS == rv)? apr_psprintf(p, "%s:%s", challenge_setup, authz->domain) : NULL;
     if (!fctx.accepted || APR_ENOTIMPL == rv) {
         rv = APR_EINVAL;
         fctx.offered = apr_array_make(p, 5, sizeof(const char*));
index d10bf0d3ace5eaf1ba518bec533118adb42d3d66..0d21e7b14c6d3cddd03b793370403baa9bb9fbc1 100644 (file)
@@ -212,7 +212,7 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
                                const char *method, const char *url, 
                                struct apr_table_t *headers)
 {
-    md_http_request_t *req = NULL;
+    md_http_request_t *req;
     apr_pool_t *pool;
     apr_status_t rv;
     
index 884c0bb91e8a42c3d8f25408bf8962391584d291..126fb782dce8e55258ffa2829ad96cadcadc0097 100644 (file)
@@ -916,6 +916,19 @@ int md_dns_domains_match(const apr_array_header_t *domains, const char *name)
     return 0;
 }
 
+int md_is_wild_match(const apr_array_header_t *domains, const char *name)
+{
+    const char *domain;
+    int i;
+
+    for (i = 0; i < domains->nelts; ++i) {
+        domain = APR_ARRAY_IDX(domains, i, const char*);
+        if (md_dns_matches(domain, name))
+            return (domain[0] == '*' && domain[1] == '.');
+    }
+    return 0;
+}
+
 const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme)
 {
     const char *cp = s;
index e430655fca51a5aeb5cb713d0dac4be49c1fa3d1..311997e40322968fd027cf57a102f42eb01e9746 100644 (file)
@@ -174,6 +174,11 @@ struct apr_array_header_t *md_dns_make_minimal(apr_pool_t *p,
  */
 int md_dns_domains_match(const apr_array_header_t *domains, const char *name);
 
+/**
+ * @return != 0 iff `name` is matched by a wildcard pattern in `domains`
+ */
+int md_is_wild_match(const apr_array_header_t *domains, const char *name);
+
 /**************************************************************************************************/
 /* file system related */
 
index a8f3ef22f77276dcedf0adca30731f51cd2881fb..53702d15b04284dc31b7f9e2cca6851871907132 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "2.4.21"
+#define MOD_MD_VERSION "2.4.23"
 
 /**
  * @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 0x020415
+#define MOD_MD_VERSION_NUM 0x020417
 
 #define MD_ACME_DEF_URL         "https://acme-v02.api.letsencrypt.org/directory"
 #define MD_TAILSCALE_DEF_URL    "file://localhost/var/run/tailscale/tailscaled.sock"
index 00ed4baa95b48b1767b3db13a321dae6bc778884..d9ebe836bcdca77a27428ff069aef203c43899c1 100644 (file)
@@ -377,12 +377,12 @@ static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
         return APR_SUCCESS;
     }
     else {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10040)
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10040)
                      "Virtual Host %s:%d matches Managed Domain '%s', but the "
                      "name/alias %s itself is not managed. A requested MD certificate "
                      "will not match ServerName.",
                      s->server_hostname, s->port, md->name, domain);
-        return APR_EINVAL;
+        return APR_SUCCESS;
     }
 }
 
@@ -586,18 +586,30 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
         for (i = 0; i < md->domains->nelts; ++i) {
             domain = APR_ARRAY_IDX(md->domains, i, const char*);
 
-            if (ap_matches_request_vhost(&r, domain, s->port)
-                || (md_dns_is_wildcard(p, domain) && md_dns_matches(domain, s->server_hostname))) {
+            if ((mc->match_mode == MD_MATCH_ALL &&
+                 ap_matches_request_vhost(&r, domain, s->port))
+                || (((mc->match_mode == MD_MATCH_SERVERNAMES) || md_dns_is_wildcard(p, domain)) &&
+                    md_dns_matches(domain, s->server_hostname))) {
                 /* Create a unique md_srv_conf_t record for this server, if there is none yet */
                 sc = md_config_get_unique(s, p);
                 if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*));
-
+                if (sc->assigned->nelts == 1 && mc->match_mode == MD_MATCH_SERVERNAMES) {
+                    /* there is already an MD assigned for this server. But in
+                     * this match mode, wildcard matches are pre-empted by non-wildcards */
+                    int existing_wild = md_is_wild_match(
+                          APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->domains,
+                          s->server_hostname);
+                    if (!existing_wild && md_dns_is_wildcard(p, domain))
+                        continue;  /* do not add */
+                    if (existing_wild && !md_dns_is_wildcard(p, domain))
+                        sc->assigned->nelts = 0;  /* overwrite existing */
+                }
                 APR_ARRAY_PUSH(sc->assigned, md_t*) = md;
                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041)
-                             "Server %s:%d matches md %s (config %s) for domain %s, "
-                             "has now %d MDs",
+                             "Server %s:%d matches md %s (config %s, match-mode=%d) "
+                             "for domain %s, has now %d MDs",
                              s->server_hostname, s->port, md->name, sc->name,
-                             domain, (int)sc->assigned->nelts);
+                             mc->match_mode, domain, (int)sc->assigned->nelts);
 
                 if (md->contacts && md->contacts->nelts > 0) {
                     /* set explicitly */
@@ -670,17 +682,19 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
         md = APR_ARRAY_IDX(mc->mds, i, md_t*);
         merge_srv_config(md, base_conf, p);
 
-        /* Check that we have no overlap with the MDs already completed */
-        for (j = 0; j < i; ++j) {
-            omd = APR_ARRAY_IDX(mc->mds, j, md_t*);
-            if ((domain = md_common_name(md, omd)) != NULL) {
-                ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038)
-                             "two Managed Domains have an overlap in domain '%s'"
-                             ", first definition in %s(line %d), second in %s(line %d)",
-                             domain, md->defn_name, md->defn_line_number,
-                             omd->defn_name, omd->defn_line_number);
-                return APR_EINVAL;
-            }
+        if (mc->match_mode == MD_MATCH_ALL) {
+          /* Check that we have no overlap with the MDs already completed */
+          for (j = 0; j < i; ++j) {
+              omd = APR_ARRAY_IDX(mc->mds, j, md_t*);
+              if ((domain = md_common_name(md, omd)) != NULL) {
+                  ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038)
+                               "two Managed Domains have an overlap in domain '%s'"
+                               ", first definition in %s(line %d), second in %s(line %d)",
+                               domain, md->defn_name, md->defn_line_number,
+                               omd->defn_name, omd->defn_line_number);
+                  return APR_EINVAL;
+              }
+          }
         }
 
         if (md->cert_files && md->cert_files->nelts) {
index e117b160097bb639b020ae9bb78bae68960bce2e..31d06b4bc5b9081470aff8942e3ebcaa890bbf77 100644 (file)
@@ -88,6 +88,7 @@ static md_mod_conf_t defmc = {
     13,                        /* retry_failover after 14 errors, with 5s delay ~ half a day */
     0,                         /* store locks, disabled by default */
     apr_time_from_sec(5),      /* max time to wait to obaint a store lock */
+    MD_MATCH_ALL,              /* match vhost severname and aliases */
 };
 
 static md_timeslice_t def_renew_window = {
@@ -684,6 +685,27 @@ static const char *md_config_set_store_locks(cmd_parms *cmd, void *dc, const cha
     return NULL;
 }
 
+static const char *md_config_set_match_mode(cmd_parms *cmd, void *dc, const char *s)
+{
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+
+    (void)dc;
+    if (err) {
+        return err;
+    }
+    else if (!apr_strnatcasecmp("all", s)) {
+        config->mc->match_mode = MD_MATCH_ALL;
+    }
+    else if (!apr_strnatcasecmp("servernames", s)) {
+        config->mc->match_mode = MD_MATCH_SERVERNAMES;
+    }
+    else {
+        return "invalid argument, must be a 'all' or 'servernames'";
+    }
+    return NULL;
+}
+
 static const char *md_config_set_require_https(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
@@ -985,6 +1007,24 @@ static const char *md_config_set_dns01_cmd(cmd_parms *cmd, void *mconfig, const
     return NULL;
 }
 
+static const char *md_config_set_dns01_version(cmd_parms *cmd, void *mconfig, const char *value)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)mconfig;
+    if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+        return err;
+    }
+    if (!strcmp("1", value) || !strcmp("2", value)) {
+        apr_table_set(sc->mc->env, MD_KEY_DNS01_VERSION, value);
+    }
+    else {
+        return "Only versions `1` and `2` are supported";
+    }
+    return NULL;
+}
+
 static const char *md_config_add_cert_file(cmd_parms *cmd, void *mconfig, const char *arg)
 {
     md_srv_conf_t *sc = md_config_get(cmd->server);
@@ -1226,7 +1266,9 @@ const command_rec md_cmds[] = {
                   "Allow managing of base server outside virtual hosts."),
     AP_INIT_RAW_ARGS("MDChallengeDns01", md_config_set_dns01_cmd, NULL, RSRC_CONF, 
                   "Set the command for setup/teardown of dns-01 challenges"),
-    AP_INIT_TAKE1("MDCertificateFile", md_config_add_cert_file, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDChallengeDns01Version", md_config_set_dns01_version, NULL, RSRC_CONF,
+                  "Set the type of arguments to call `MDChallengeDns01` with"),
+    AP_INIT_TAKE1("MDCertificateFile", md_config_add_cert_file, NULL, RSRC_CONF,
                   "set the static certificate (chain) file to use for this domain."),
     AP_INIT_TAKE1("MDCertificateKeyFile", md_config_add_key_file, NULL, RSRC_CONF, 
                   "set the static private key file to use for this domain."),
@@ -1260,6 +1302,8 @@ const command_rec md_cmds[] = {
                   "The number of errors before a failover to another CA is triggered."),
     AP_INIT_TAKE1("MDStoreLocks", md_config_set_store_locks, NULL, RSRC_CONF,
                   "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(NULL, NULL, NULL, RSRC_CONF, NULL)
 };
index de42169c97d329d1749b65667142652002148b47..7e87440ed18051668aa40da2944836fd81e90e14 100644 (file)
@@ -41,6 +41,11 @@ typedef enum {
     MD_CONFIG_STAPLE_OTHERS,
 } md_config_var_t;
 
+typedef enum {
+    MD_MATCH_ALL,
+    MD_MATCH_SERVERNAMES,
+} md_match_mode_t;
+
 typedef struct md_mod_conf_t md_mod_conf_t;
 struct md_mod_conf_t {
     apr_array_header_t *mds;           /* all md_t* defined in the config, shared */
@@ -74,6 +79,7 @@ struct md_mod_conf_t {
     int retry_failover;                /* number of errors to trigger CA failover */
     int use_store_locks;               /* use locks when updating store */
     apr_time_t lock_wait_timeout;      /* fail after this time when unable to obtain lock */
+    md_match_mode_t match_mode;        /* how dns names are match to vhosts */
 };
 
 typedef struct md_srv_conf_t {
diff --git a/test/modules/md/dns01_v2.py b/test/modules/md/dns01_v2.py
new file mode 100755 (executable)
index 0000000..908b4f8
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+import subprocess
+import sys
+
+curl = "curl"
+challtestsrv = "localhost:8055"
+
+
+def run(args):
+    sys.stderr.write(f"run: {' '.join(args)}\n")
+    p = subprocess.Popen(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    output, errput = p.communicate(None)
+    rv = p.wait()
+    if rv != 0:
+        sys.stderr.write(errput.decode())
+    sys.stdout.write(output.decode())
+    return rv
+
+
+def teardown(domain):
+    rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}"}}',
+              f'{challtestsrv}/clear-txt'])
+    if rv == 0:
+        rv = run([curl, '-s', '-d', f'{{"host":"{domain}"}}',
+                  f'{challtestsrv}/set-txt'])
+    return rv
+
+
+def setup(domain, challenge):
+    teardown(domain)
+    rv = run([curl, '-s', '-d', f'{{"host":"{domain}", "addresses":["127.0.0.1"]}}',
+              f'{challtestsrv}/set-txt'])
+    if rv == 0:
+        rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}.", "value":"{challenge}"}}',
+                  f'{challtestsrv}/set-txt'])
+    return rv
+
+
+def main(argv):
+    if len(argv) > 1:
+        if argv[1] == 'setup':
+            if len(argv) != 4:
+                sys.stderr.write("wrong number of arguments: dns01.py setup <domain> <challenge>\n")
+                sys.exit(2)
+            rv = setup(argv[2], argv[3])
+        elif argv[1] == 'teardown':
+            if len(argv) != 4:
+                sys.stderr.write("wrong number of arguments: dns01.py teardown <domain> <challenge>\n")
+                sys.exit(1)
+            rv = teardown(argv[2])
+        else:
+            sys.stderr.write(f"unknown option {argv[1]}\n")
+            rv = 2
+    else:
+        sys.stderr.write("dns01.py wrong number of arguments\n")
+        rv = 2
+    sys.exit(rv)
+
+
+if __name__ == "__main__":
+    main(sys.argv)
index f348f5f17892f4ea2baaf7ff3fc15455aeb492c0..f73bf67999db2795074597fdfbdcee576180f35d 100644 (file)
@@ -455,3 +455,80 @@ class TestConf:
             assert len(md['ca']['urls']) == len(cas)
         else:
             assert rv != 0, "Server should not have accepted CAs '{}'".format(cas)
+
+    # messy ServerAliases, see #301
+    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}"
+        conf.add_vhost(port=env.http_port, domains=[domaina, domainb, dalias], with_ssl=False)
+        conf.add(f"""
+            MDomain {domaina} 
+            MDomain {domainb} {dalias}
+            """)
+        conf.add(f"""
+            <VirtualHost 10.0.0.1:{env.https_port}>
+              ServerName {domaina}
+              ServerAlias {dalias}
+              SSLEngine on
+            </VirtualHost>
+            <VirtualHost 10.0.0.1:{env.https_port}>
+              ServerName {domainb}
+              ServerAlias {dalias}
+              SSLEngine on
+            </VirtualHost>
+            """)
+        conf.install()
+        # This does not work as we have both MDs match domaina's vhost
+        assert env.apache_fail() == 0
+        env.httpd_error_log.ignore_recent(
+            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
+
+    # 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}"
+        subdomain = f"sub.{domain}"
+        conf.add_vhost(port=env.http_port, domains=[domain, subdomain], with_ssl=False)
+        conf.add(f"""
+            MDMembers manual
+            MDomain {domain} *.{domain} 
+            MDomain {subdomain}
+            """)
+        conf.add(f"""
+            <VirtualHost 10.0.0.1:{env.https_port}>
+              ServerName {domain}
+              SSLEngine on
+            </VirtualHost>
+            <VirtualHost 10.0.0.1:{env.https_port}>
+              ServerName another.{domain}
+              SSLEngine on
+            </VirtualHost>
+            <VirtualHost 10.0.0.1:{env.https_port}>
+              ServerName {subdomain}
+              SSLEngine on
+            </VirtualHost>
+            """)
+        conf.install()
+        # This does not work as we have overlapping names in MDs
+        assert env.apache_fail() == 0
+        env.httpd_error_log.ignore_recent(
+            lognos = [
+                "AH10038"   # 2 MDs overlap
+            ]
+        )
+        # It works, if we only match on ServerNames
+        conf.add("MDMatchNames servernames")
+        conf.install()
+        assert env.apache_restart() == 0
+
index f2bb9c723aced8bfea2ec285ae8c80cfcc005b7d..d56790bb1fbd5c73656591b1cffe70ea52794c11 100644 (file)
@@ -48,6 +48,11 @@ 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):
@@ -63,6 +68,11 @@ 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):
@@ -72,6 +82,11 @@ 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):
@@ -87,6 +102,11 @@ 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):
@@ -108,6 +128,11 @@ 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):
@@ -118,6 +143,11 @@ 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):
@@ -129,6 +159,11 @@ 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):
@@ -161,6 +196,11 @@ 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):
@@ -169,6 +209,11 @@ 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):
@@ -178,6 +223,11 @@ 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):
@@ -187,6 +237,11 @@ 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):
@@ -205,6 +260,11 @@ 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):
@@ -214,7 +274,12 @@ 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="""
@@ -223,6 +288,11 @@ 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):
@@ -232,6 +302,11 @@ 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):
@@ -241,6 +316,11 @@ 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):
@@ -269,6 +349,11 @@ 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):
@@ -281,6 +366,11 @@ 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):
@@ -293,6 +383,11 @@ 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):
@@ -302,6 +397,12 @@ 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):
@@ -311,6 +412,11 @@ 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):
@@ -334,6 +440,11 @@ 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):
@@ -347,6 +458,11 @@ 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):
@@ -363,6 +479,11 @@ 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):
@@ -382,6 +503,11 @@ 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):
@@ -398,6 +524,11 @@ 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):
@@ -413,6 +544,11 @@ 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", [
@@ -433,6 +569,11 @@ 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):
@@ -448,6 +589,11 @@ 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"])
@@ -464,6 +610,11 @@ 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"])
@@ -484,6 +635,12 @@ 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):
@@ -499,6 +656,11 @@ 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):
@@ -511,6 +673,11 @@ 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):
@@ -526,6 +693,11 @@ 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):
@@ -552,6 +724,11 @@ 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):
@@ -572,6 +749,11 @@ 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):
@@ -595,6 +777,11 @@ 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):
@@ -619,6 +806,11 @@ 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):
@@ -642,6 +834,11 @@ 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):
@@ -672,6 +869,11 @@ 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):
@@ -697,6 +899,12 @@ 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):
@@ -720,6 +928,11 @@ 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", [
@@ -792,6 +1005,11 @@ 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):
index eb754f25eff9fafea6bbe933b974fc24a1acaf31..a98e4ad97c7442798db92392262ba9ba83d03e51 100644 (file)
@@ -436,6 +436,11 @@ 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),
@@ -462,6 +467,11 @@ 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
 
index 9ff87e5df7e88361513ed3c5f3ba8194c905eb2e..e2e74c7d81b1f5ea6dd576637fd7fcd39b2aea98 100644 (file)
@@ -52,9 +52,13 @@ 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
@@ -93,6 +97,11 @@ 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
@@ -134,6 +143,11 @@ 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_ ---------