From 53f429b9f4871e169a3f9b1d66599e091bc31bac Mon Sep 17 00:00:00 2001 From: Yann Ylavic Date: Mon, 10 Jul 2023 09:08:14 +0000 Subject: [PATCH 1/7] ab: Check and handle POLLOUT before POLLIN. connect() failures can return POLLOUT|POLLHUP and the polling loop should take the POLLOUT branch in this case, not the POLLIN|POLLHUP one, so move the check for POLLOUT first. While at it, add some assert()ions to avoid infinite loops. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910911 13f79535-47bb-0310-9956-ffa450edef68 --- support/ab.c | 109 ++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/support/ab.c b/support/ab.c index 318eea6d21e..9bac3a9fe2c 100644 --- a/support/ab.c +++ b/support/ab.c @@ -279,7 +279,7 @@ typedef enum { #endif STATE_WRITE, /* in the write phase */ STATE_READ /* in the read phase */ -} connect_state_e; +} conn_state_e; #define CBUFFSIZE (8192) @@ -360,7 +360,7 @@ struct worker { int slot; int requests; int concurrency; - int polled; + int polls; /* number of connections polled */ int bind_rr; /* next address to bind (round robin) */ int succeeded_once; /* response header received once */ apr_int64_t started; /* number of requests started, so no excess */ @@ -483,7 +483,7 @@ static apr_thread_mutex_t *workers_mutex; static apr_thread_cond_t *workers_can_start; #endif -static APR_INLINE int worker_can_stop(struct worker *worker) +static APR_INLINE int worker_should_stop(struct worker *worker) { return (stoptime <= lasttime || (rlimited && worker->metrics.done >= worker->requests)); @@ -623,8 +623,8 @@ static int set_polled_events(struct connection *c, apr_int16_t new_reqevents) graceful_strerror("apr_pollset_remove()", rv); return 0; } - assert(c->worker->polled > 0); - c->worker->polled--; + assert(c->worker->polls > 0); + c->worker->polls--; } c->pollfd.reqevents = new_reqevents; @@ -634,18 +634,18 @@ static int set_polled_events(struct connection *c, apr_int16_t new_reqevents) graceful_strerror("apr_pollset_add()", rv); return 0; } - c->worker->polled++; + c->worker->polls++; } } return 1; } -static void set_conn_state(struct connection *c, connect_state_e new_state, +static void set_conn_state(struct connection *c, conn_state_e state, apr_int16_t events) { - c->state = new_state; + c->state = state; - if (!set_polled_events(c, events) && new_state != STATE_UNCONNECTED) { + if (!set_polled_events(c, events) && state != STATE_UNCONNECTED) { close_connection(c); } } @@ -1594,6 +1594,7 @@ static void start_connection(struct connection * c) return; } + assert(c->state == STATE_UNCONNECTED); if (!c->ctx) { apr_pool_create(&c->ctx, worker->pool); APR_RING_ELEM_INIT(c, delay_list); @@ -1606,7 +1607,6 @@ static void start_connection(struct connection * c) return; } - c->state = STATE_UNCONNECTED; c->pollfd.desc.s = c->aprsock; c->pollfd.desc_type = APR_POLL_SOCKET; c->pollfd.reqevents = c->pollfd.rtnevents = 0; @@ -2492,21 +2492,42 @@ static void worker_test(struct worker *worker) for (i = 0, pollfd = pollresults; i < n; i++, pollfd++) { c = pollfd->client_data; - /* - * If the connection isn't connected how can we check it? - */ - if (c->state == STATE_UNCONNECTED) - continue; + rtnev = pollfd->rtnevents; -#if 0 - /* - * Remove from the pollset while being handled. - */ - if (!set_polled_events(c, 0)) - continue; + if (rtnev & APR_POLLOUT) { + if (c->state == STATE_CONNECTING) { + /* call connect() again to detect errors */ + rv = apr_socket_connect(c->aprsock, worker->destsa); + if (rv != APR_SUCCESS) { + try_reconnect(c, rv); + continue; + } +#ifdef USE_SSL + if (c->ssl) + c->state = STATE_HANDSHAKE; + else #endif + c->state = STATE_WRITE; + } + switch (c->state) { +#ifdef USE_SSL + case STATE_HANDSHAKE: + ssl_proceed_handshake(c); + break; +#endif + case STATE_WRITE: + write_request(c); + break; + case STATE_READ: + read_response(c); + break; + default: + assert(0); + break; + } - rtnev = pollfd->rtnevents; + continue; + } /* * Notes: APR_POLLHUP is set after FIN is received on some @@ -2521,7 +2542,6 @@ static void worker_test(struct worker *worker) * apr_poll(). */ if (rtnev & (APR_POLLIN | APR_POLLHUP | APR_POLLPRI)) { - switch (c->state) { #ifdef USE_SSL case STATE_HANDSHAKE: @@ -2534,42 +2554,9 @@ static void worker_test(struct worker *worker) case STATE_READ: read_response(c); break; - } - - continue; - } - - if (rtnev & APR_POLLOUT) { - if (c->state == STATE_CONNECTING) { - /* call connect() again to detect errors */ - rv = apr_socket_connect(c->aprsock, worker->destsa); - if (rv != APR_SUCCESS) { - try_reconnect(c, rv); - continue; - } -#ifdef USE_SSL - if (c->ssl) - ssl_proceed_handshake(c); - else -#endif - write_request(c); - } - else { - - switch (c->state) { -#ifdef USE_SSL - case STATE_HANDSHAKE: - ssl_proceed_handshake(c); - break; -#endif - case STATE_WRITE: - write_request(c); - break; - case STATE_READ: - read_response(c); - break; - } - + default: + assert(0); + break; } continue; @@ -2586,9 +2573,7 @@ static void worker_test(struct worker *worker) continue; } } - } while (worker->polled > 0 && stoptime > lasttime); - - assert(worker_can_stop(worker)); + } while (!worker_should_stop(worker)); } #if APR_HAS_THREADS From 1aa10d984c4682f0a11fea61f1ade86f9383352d Mon Sep 17 00:00:00 2001 From: Yann Ylavic Date: Mon, 10 Jul 2023 09:17:51 +0000 Subject: [PATCH 2/7] ab: Add -D for a watchdog that prints counters on polled connections. This prints lines like the below (one per worker thread) every 5 seconds: Worker 0: requests 264484/12500, polls 2500, DISCONNECTED 0/0, CONNECTING 0/35, HANDSHAKE 0/0, WRITE 0/0, READ 2465/0 Worker 3: requests 248779/12500, polls 2500, DISCONNECTED 0/0, CONNECTING 0/25, HANDSHAKE 0/0, WRITE 0/0, READ 2475/0 Worker 1: requests 246151/12500, polls 2500, DISCONNECTED 0/0, CONNECTING 0/39, HANDSHAKE 0/0, WRITE 0/0, READ 2461/0 Worker 2: requests 257491/12500, polls 2500, DISCONNECTED 0/0, CONNECTING 0/44, HANDSHAKE 0/0, WRITE 0/0, READ 2456/0 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910912 13f79535-47bb-0310-9956-ffa450edef68 --- support/ab.c | 107 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/support/ab.c b/support/ab.c index 9bac3a9fe2c..6a617edca19 100644 --- a/support/ab.c +++ b/support/ab.c @@ -270,7 +270,7 @@ static int test_started = 0, * visiting set_conn_state() */ typedef enum { - STATE_UNCONNECTED = 0, + STATE_DISCONNECTED = 0, STATE_CONNECTING, /* TCP connect initiated, but we don't * know if it worked yet */ @@ -278,9 +278,21 @@ typedef enum { STATE_HANDSHAKE, /* in the handshake phase */ #endif STATE_WRITE, /* in the write phase */ - STATE_READ /* in the read phase */ + STATE_READ, /* in the read phase */ + + STATE_COUNT } conn_state_e; +const char *conn_state_str[STATE_COUNT] = { + "DISCONNECTED", + "CONNECTING", +#ifdef USE_SSL + "HANDSHAKE", +#endif + "WRITE", + "READ" +}; + #define CBUFFSIZE (8192) /* forward declare */ @@ -292,6 +304,7 @@ struct connection { apr_pool_t *ctx; apr_socket_t *aprsock; apr_pollfd_t pollfd; + apr_int16_t events; int state; apr_time_t delay; apr_size_t read; /* amount of bytes read */ @@ -370,6 +383,8 @@ struct worker { struct delayed_ring_t delayed_ring; struct metrics metrics; + int counters[STATE_COUNT][2]; + char tmp[1024]; char buffer[CBUFFSIZE]; /* throw-away buffer to read stuff into */ }; @@ -431,6 +446,9 @@ apr_interval_time_t aprtimeout = apr_time_from_sec(30); /* timeout value */ apr_interval_time_t ramp = apr_time_from_msec(0); /* ramp delay */ int pollset_wakeable = 0; +int watchdog = 0; +#define WATCHDOG_TIMEOUT apr_time_from_sec(5) + /* overrides for ab-generated common headers */ const char *opt_host; /* which optional "Host:" header specified, if any */ int opt_useragent = 0; /* was an optional "User-Agent:" header specified? */ @@ -643,11 +661,19 @@ static int set_polled_events(struct connection *c, apr_int16_t new_reqevents) static void set_conn_state(struct connection *c, conn_state_e state, apr_int16_t events) { - c->state = state; + struct worker *worker = c->worker; + + assert(worker->counters[c->state][(c->events & APR_POLLOUT) != 0] > 0); + worker->counters[c->state][(c->events & APR_POLLOUT) != 0]--; - if (!set_polled_events(c, events) && state != STATE_UNCONNECTED) { + c->state = state; + c->events = events; + if (!set_polled_events(c, events) && state != STATE_DISCONNECTED) { close_connection(c); + c->events = 0; } + + worker->counters[c->state][(c->events & APR_POLLOUT) != 0]++; } /* --------------------------------------------------------- */ @@ -1594,10 +1620,11 @@ static void start_connection(struct connection * c) return; } - assert(c->state == STATE_UNCONNECTED); + assert(c->state == STATE_DISCONNECTED); if (!c->ctx) { apr_pool_create(&c->ctx, worker->pool); APR_RING_ELEM_INIT(c, delay_list); + worker->counters[STATE_DISCONNECTED][0]++; worker->metrics.concurrent++; } @@ -1710,11 +1737,15 @@ static void start_connection(struct connection * c) /* connected first time */ #ifdef USE_SSL if (c->ssl) { + set_conn_state(c, STATE_HANDSHAKE, 0); ssl_proceed_handshake(c); } else #endif - write_request(c); + { + set_conn_state(c, STATE_WRITE, 0); + write_request(c); + } } /* --------------------------------------------------------- */ @@ -1723,7 +1754,7 @@ static void start_connection(struct connection * c) static void close_connection(struct connection *c) { - set_conn_state(c, STATE_UNCONNECTED, 0); + set_conn_state(c, STATE_DISCONNECTED, 0); #ifdef USE_SSL if (c->ssl) { SSL_shutdown(c->ssl); @@ -2444,6 +2475,8 @@ static int test(void) static void worker_test(struct worker *worker) { apr_status_t rv; + apr_time_t poll_expiry = 0; + apr_time_t watchdog_expiry = 0; struct connection *c; apr_int16_t rtnev; int i; @@ -2456,10 +2489,43 @@ static void worker_test(struct worker *worker) apr_int32_t n; const apr_pollfd_t *pollresults, *pollfd; apr_interval_time_t t = aprtimeout; - - if (!APR_RING_EMPTY(&worker->delayed_ring, connection, delay_list)) { - apr_time_t now = apr_time_now(); - do { + apr_time_t now = 0; + + if (watchdog || !APR_RING_EMPTY(&worker->delayed_ring, connection, delay_list)) { + now = apr_time_now(); + if (poll_expiry == 0) { + poll_expiry = now + aprtimeout; + } + else if (t > poll_expiry - now) { + t = poll_expiry > now ? poll_expiry - now : 0; + } + if (watchdog) { + if (watchdog_expiry && watchdog_expiry <= now) { + int state; + apr_size_t len = 0; + len += apr_snprintf(worker->tmp + len, sizeof(worker->tmp) - len, + "Worker %d: requests %" APR_INT64_T_FMT "/%d, polls %d", + worker->slot, + worker->metrics.done, worker->requests, + worker->polls); + for (state = 0; state < STATE_COUNT; ++state) { + len += apr_snprintf(worker->tmp + len, sizeof(worker->tmp) - len, + ", %s %d/%d", + conn_state_str[state], + worker->counters[state][0], + worker->counters[state][1]); + } + fprintf(stderr, "%s\n", worker->tmp); + fflush(stderr); + } + if (watchdog_expiry <= now) { + watchdog_expiry = now + WATCHDOG_TIMEOUT; + } + if (t > WATCHDOG_TIMEOUT) { + t = WATCHDOG_TIMEOUT; + } + } + while (!APR_RING_EMPTY(&worker->delayed_ring, connection, delay_list)) { c = APR_RING_FIRST(&worker->delayed_ring); if (c->delay <= now) { APR_RING_REMOVE(c, delay_list); @@ -2473,21 +2539,22 @@ static void worker_test(struct worker *worker) } break; } - } while (!APR_RING_EMPTY(&worker->delayed_ring, connection, delay_list)); + } } + assert(worker->polls > 0); n = worker->metrics.concurrent; rv = apr_pollset_poll(worker->pollset, t, &n, &pollresults); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_EINTR(rv) - || (APR_STATUS_IS_TIMEUP(rv) && - !APR_RING_EMPTY(&worker->delayed_ring, connection, - delay_list))) { + || (APR_STATUS_IS_TIMEUP(rv) + && poll_expiry && poll_expiry > apr_time_now())) { continue; } graceful_strerror("apr_pollset_poll", rv); return; } + poll_expiry = 0; for (i = 0, pollfd = pollresults; i < n; i++, pollfd++) { c = pollfd->client_data; @@ -2504,10 +2571,10 @@ static void worker_test(struct worker *worker) } #ifdef USE_SSL if (c->ssl) - c->state = STATE_HANDSHAKE; + set_conn_state(c, STATE_HANDSHAKE, 0); else #endif - c->state = STATE_WRITE; + set_conn_state(c, STATE_WRITE, 0); } switch (c->state) { #ifdef USE_SSL @@ -2730,6 +2797,7 @@ static void usage(const char *progname) fprintf(stderr, " -e filename Output CSV file with percentages served\n"); fprintf(stderr, " -r Don't exit on socket receive errors.\n"); fprintf(stderr, " -m method Method name\n"); + fprintf(stderr, " -D Debug watchdog to show some counters during runtime\n"); fprintf(stderr, " -h Display usage information (this message)\n"); #ifdef USE_SSL @@ -2958,7 +3026,7 @@ int main(int argc, const char * const argv[]) #endif apr_getopt_init(&opt, cntxt, argc, argv); - while ((status = apr_getopt(opt, "n:c:t:s:b:T:p:u:v:lrkVhwiIx:y:z:C:H:P:A:g:X:de:SqQB:m:R:" + while ((status = apr_getopt(opt, "n:c:t:s:b:T:p:u:v:lrkVhwiIx:y:z:C:H:P:A:g:X:de:SqQDB:m:R:" #if APR_HAS_THREADS "W:" #endif @@ -2990,6 +3058,9 @@ int main(int argc, const char * const argv[]) case 'Q': no_banner = 1; break; + case 'D': + watchdog = 1; + break; case 'c': concurrency = atoi(opt_arg); if (concurrency < 0) { From bb9b477eac923e5600b8384925fce8c5aa22c119 Mon Sep 17 00:00:00 2001 From: Yann Ylavic Date: Tue, 11 Jul 2023 20:24:18 +0000 Subject: [PATCH 3/7] Steal another APLOGNO for event_wip. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910945 13f79535-47bb-0310-9956-ffa450edef68 --- docs/log-message-tags/next-number | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number index ab2acdaf2df..6cea8e27cd7 100644 --- a/docs/log-message-tags/next-number +++ b/docs/log-message-tags/next-number @@ -1 +1 @@ -10473 +10474 From b4c42dc90e879bf9d6f70d4a98fbd79a403532c9 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 14 Jul 2023 08:49:52 +0000 Subject: [PATCH 4/7] test: update http2 test 008_03 for curl allowing the server to send almost all data git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910995 13f79535-47bb-0310-9956-ffa450edef68 --- test/modules/http2/test_008_ranges.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/modules/http2/test_008_ranges.py b/test/modules/http2/test_008_ranges.py index 8bf8a3f161b..339df1a0cc8 100644 --- a/test/modules/http2/test_008_ranges.py +++ b/test/modules/http2/test_008_ranges.py @@ -1,11 +1,15 @@ import inspect import json +import logging import os import pytest from .env import H2Conf, H2TestEnv +log = logging.getLogger(__name__) + + @pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here") class TestRanges: @@ -115,14 +119,17 @@ def test_h2_008_03(self, env, repeat): '--limit-rate', '2k', '-m', '2' ]) assert r.exit_code != 0, f'{r}' + # Restart for logs to be flushed out + assert env.apache_restart() == 0 found = False for line in open(TestRanges.LOGFILE).readlines(): e = json.loads(line) + log.info(f'inspecting logged request: {e["request"]}') if e['request'] == f'GET {path}?03broken HTTP/2.0': assert e['bytes_rx_I'] > 0 assert e['bytes_resp_B'] == 100*1024*1024 assert e['bytes_tx_O'] > 1024 - assert e['bytes_tx_O'] < 10*1024*1024 # curl buffers, but not that much + assert e['bytes_tx_O'] < 100*1024*1024 # curl buffers, but not that much found = True break assert found, f'request not found in {self.LOGFILE}' From c69fae8d562a8f0e4e1ebf075a423678799ebe82 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 14 Jul 2023 12:26:50 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=20*=20mod=5Fmd:=20=20=20=20-=20New=20direc?= =?UTF-8?q?tive=20`MDMatchNames=20all|servernames`=20to=20allow=20more=20c?= =?UTF-8?q?ontrol=20over=20how=20=20=20=20=20=20MDomains=20are=20matched?= =?UTF-8?q?=20to=20VirtualHosts.=20=20=20=20-=20New=20directive=20`MDChall?= =?UTF-8?q?engeDns01Version`.=20Setting=20this=20to=20`2`=20will=20provide?= =?UTF-8?q?=20=20=20=20=20=20the=20command=20also=20with=20the=20challenge?= =?UTF-8?q?=20value=20on=20`teardown`=20invocation.=20In=20version=20=20?= =?UTF-8?q?=20=20=20=201,=20the=20default,=20only=20the=20`setup`=20invoca?= =?UTF-8?q?tion=20gets=20this=20parameter.=20=20=20=20=20=20Refs=20#312.?= =?UTF-8?q?=20Thanks=20to=20@domrim=20for=20the=20idea.=20=20=20=20-=20For?= =?UTF-8?q?=20Managed=20Domain=20in=20"manual"=20mode,=20the=20checks=20if?= =?UTF-8?q?=20all=20used=20ServerName=20and=20=20=20=20=20=20ServerAlias?= =?UTF-8?q?=20are=20part=20of=20the=20MDomain=20now=20reports=20a=20warnin?= =?UTF-8?q?g=20instead=20of=20an=20error=20=20=20=20=20=20(AH10040)=20when?= =?UTF-8?q?=20not=20all=20names=20are=20present.=20=20=20=20-=20MDChalleng?= =?UTF-8?q?eDns01=20can=20now=20be=20configured=20for=20individual=20domai?= =?UTF-8?q?ns.=20=20=20=20=20=20Using=20PR=20from=20J=C3=A9r=C3=B4me=20Bil?= =?UTF-8?q?liras=20(@bilhackmac)=20and=20adding=20test=20case=20and=20fixi?= =?UTF-8?q?ng=20proper=20working=20=20=20=20-=20Fixed=20a=20bug=20found=20?= =?UTF-8?q?by=20J=C3=A9r=C3=B4me=20Billiras=20(@bilhackmac)=20that=20cause?= =?UTF-8?q?d=20the=20challenge=20=20=20=20=20=20teardown=20not=20being=20i?= =?UTF-8?q?nvoked=20as=20it=20should.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1910996 13f79535-47bb-0310-9956-ffa450edef68 --- changes-entries/md_v2.4.23.txt | 14 ++ docs/manual/mod/mod_md.xml | 52 ++++- modules/md/md.h | 18 +- modules/md/md_acme_authz.c | 43 +++-- modules/md/md_http.c | 2 +- modules/md/md_util.c | 13 ++ modules/md/md_util.h | 5 + modules/md/md_version.h | 4 +- modules/md/mod_md.c | 52 +++-- modules/md/mod_md_config.c | 46 ++++- modules/md/mod_md_config.h | 6 + test/modules/md/dns01_v2.py | 62 ++++++ test/modules/md/test_300_conf_validate.py | 77 ++++++++ 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 +- 16 files changed, 593 insertions(+), 47 deletions(-) create mode 100644 changes-entries/md_v2.4.23.txt create mode 100755 test/modules/md/dns01_v2.py diff --git a/changes-entries/md_v2.4.23.txt b/changes-entries/md_v2.4.23.txt new file mode 100644 index 00000000000..736e0c5c684 --- /dev/null +++ b/changes-entries/md_v2.4.23.txt @@ -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. diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml index 454cb7fff4e..3a6014bcf11 100644 --- a/docs/manual/mod/mod_md.xml +++ b/docs/manual/mod/mod_md.xml @@ -1033,7 +1033,9 @@ MDRequireHttps permanent

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 + MDChallengeDns01Version is set to 2, + the `teardown` also gets the challenge content as argument.

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 + + MDChallengeDns01Version + + MDChallengeDns01Version 1|2 + MDChallengeDns01Version 1 + + server config + + Available in version 2.4.58 and later + +

+ Set the way MDChallengeDns01 command is invoked, e.g the number and + types of arguments. See MDChallengeDns01 + for the differences. + This setting is global and cannot be varied per domain. +

+ + + + + MDMatchNames + + MDMatchNames all|servernames + MDMatchNames all + + server config + + Available in version 2.4.58 and later + +

+ 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. +

+ 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`. +

+ 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. +

+
+
+ diff --git a/modules/md/md.h b/modules/md/md.h index 1d75d102c85..035ccba7837 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -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" diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c index a55804e4686..83e0bf153e5 100644 --- a/modules/md/md_acme_authz.c +++ b/modules/md/md_acme_authz.c @@ -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*)); diff --git a/modules/md/md_http.c b/modules/md/md_http.c index d10bf0d3ace..0d21e7b14c6 100644 --- a/modules/md/md_http.c +++ b/modules/md/md_http.c @@ -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; diff --git a/modules/md/md_util.c b/modules/md/md_util.c index 884c0bb91e8..126fb782dce 100644 --- a/modules/md/md_util.c +++ b/modules/md/md_util.c @@ -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; diff --git a/modules/md/md_util.h b/modules/md/md_util.h index e430655fca5..311997e4032 100644 --- a/modules/md/md_util.h +++ b/modules/md/md_util.h @@ -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 */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index a8f3ef22f77..53702d15b04 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.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" diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 00ed4baa95b..d9ebe836bcd 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -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) { diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index e117b160097..31d06b4bc5b 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -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) }; diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index de42169c97d..7e87440ed18 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -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 index 00000000000..908b4f8fbea --- /dev/null +++ b/test/modules/md/dns01_v2.py @@ -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 \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 \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) diff --git a/test/modules/md/test_300_conf_validate.py b/test/modules/md/test_300_conf_validate.py index f348f5f1789..f73bf67999d 100644 --- a/test/modules/md/test_300_conf_validate.py +++ b/test/modules/md/test_300_conf_validate.py @@ -455,3 +455,80 @@ def test_md_300_027(self, env, cas, should_work): 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""" + + ServerName {domaina} + ServerAlias {dalias} + SSLEngine on + + + ServerName {domainb} + ServerAlias {dalias} + SSLEngine on + + """) + 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""" + + ServerName {domain} + SSLEngine on + + + ServerName another.{domain} + SSLEngine on + + + ServerName {subdomain} + SSLEngine on + + """) + 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 + diff --git a/test/modules/md/test_310_conf_store.py b/test/modules/md/test_310_conf_store.py index f2bb9c723ac..d56790bb1fb 100644 --- a/test/modules/md/test_310_conf_store.py +++ b/test/modules/md/test_310_conf_store.py @@ -48,6 +48,11 @@ def test_md_310_100(self, env, confline, dns_lists, md_count): 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 @@ def test_md_310_101(self, env): 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 @@ def test_md_310_102(self, env): """).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 @@ def test_md_310_103(self, env): 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 @@ def test_md_310_104(self, env): 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 @@ def test_md_310_105(self, env): 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 @@ def test_md_310_106(self, env): 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 @@ def test_md_310_108(self, env): """).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 @@ def test_md_310_109(self, env): """).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 @@ def test_md_310_110(self, env): """).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 @@ def test_md_310_111(self, env): """).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 @@ def test_md_310_113a(self, env): """).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 @@ def test_md_310_113b(self, env): """).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 @@ def test_md_310_114(self, env): """).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 @@ def test_md_310_115(self, env): """).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 @@ def test_md_310_116(self, env): """).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 @@ def test_md_310_118(self, env): 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 @@ def test_md_310_119(self, env): "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 @@ def test_md_310_120(self, env): "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 @@ def test_md_310_121(self, env): """).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 @@ def test_md_310_122(self, env): """).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 @@ def test_md_310_201(self, env): 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 @@ def test_md_310_202(self, env): # 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 @@ def test_md_310_203(self, env): # 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 @@ def test_md_310_204(self, env): 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 @@ def test_md_310_205(self, env): # 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 @@ def test_md_310_206(self, env): 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 @@ def test_md_310_207(self, env, renew_mode, exp_code): """).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 @@ def test_md_310_208(self, env): """).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 @@ def test_md_310_209(self, env, key_size): """).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 @@ def test_md_310_210(self, env, mode): 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 @@ def test_md_310_211(self, env): """).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 @@ def test_md_310_300(self, env): 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 @@ def test_md_310_301(self, env): 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 @@ def test_md_310_302(self, env): 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 @@ def test_md_310_303(self, env): # 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 @@ def test_md_310_304(self, env): """).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 @@ def test_md_310_305(self, env): 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 @@ def test_md_310_306(self, env): """).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 @@ def test_md_310_307(self, env): "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 @@ def test_md_310_308(self, env): """).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 @@ def test_md_310_309(self, env): """).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 @@ def test_md_310_500(self, env): 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 eb754f25eff..a98e4ad97c7 100644 --- a/test/modules/md/test_502_acmev2_drive.py +++ b/test/modules/md/test_502_acmev2_drive.py @@ -436,6 +436,11 @@ def test_md_502_201(self, env, renew_window, test_data_list): 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 @@ def test_md_502_202(self, env, key_type, key_params, exp_key_length): # 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 9ff87e5df7e..e2e74c7d81b 100644 --- a/test/modules/md/test_602_roundtrip.py +++ b/test/modules/md/test_602_roundtrip.py @@ -52,9 +52,13 @@ def test_md_602_000(self, env): # 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 @@ def test_md_602_001(self, env): 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 @@ def test_md_602_002(self, env): 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_ --------- From 11f9d6caac2d452edf996c93db1a88d6caf236a2 Mon Sep 17 00:00:00 2001 From: Lucien Gentis Date: Sat, 15 Jul 2023 16:19:13 +0000 Subject: [PATCH 6/7] fr doc XML file update. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1911035 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/mod/mod_md.xml.fr | 64 +++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/manual/mod/mod_md.xml.fr b/docs/manual/mod/mod_md.xml.fr index 9217e5ed681..98d76c15773 100644 --- a/docs/manual/mod/mod_md.xml.fr +++ b/docs/manual/mod/mod_md.xml.fr @@ -2,7 +2,7 @@ - +