]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: acme: split the initial delay from the retry DNS delay
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 2 Apr 2026 14:15:12 +0000 (16:15 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 2 Apr 2026 16:29:26 +0000 (18:29 +0200)
The previous ACME_RSLV_WAIT state served a dual role: it applied the
initial dns-delay before the first DNS probe and also handled the
delay between retries. There was no way to simply wait a fixed delay
before submitting the challenge without also triggering DNS pre-checks.

Replace ACME_RSLV_WAIT with two distinct states:
  - ACME_INITIAL_DELAY: an optional initial wait before proceeding,
    only applied when "challenge-ready" includes the new "delay" keyword
  - ACME_RSLV_RETRY_DELAY: the delay between resolution retries, always
    applied when DNS pre-checks are in progress

The new "delay" keyword in "challenge-ready" can be used standalone
(wait then submit the challenge directly) or combined with "dns" (wait
then start the DNS pre-checks). When "delay" is not set, the first DNS
probe fires immediately.

Update the documentation accordingly.

doc/configuration.txt
include/haproxy/acme-t.h
src/acme.c

index 45b9486035440cd887099adf2583195db7fb2a0e..9f4d1ef6d44220619de9e312d855d77ffd943c97 100644 (file)
@@ -32290,14 +32290,19 @@ challenge-ready <value>[,<value>]*
            option is independent of the CLI command, so no human intervention
            is required.
 
+    delay - apply an initial wait of "dns-delay" before proceeding. Without
+            "dns", the challenge is submitted after the delay expires. When
+            combined with "dns", the initial wait is applied before starting
+            the DNS pre-checks.
+
     none - no readiness condition; the challenge is submitted to the ACME
            server immediately without waiting for any external confirmation.
            This option cannot be combined with others.
 
-  Multiple values can be combined with a comma so that both conditions must be
-  met. The order of the values is not significant. When "cli" and "dns" are
-  combined, HAProxy first waits for the CLI confirmation before triggering the
-  DNS propagation check.
+  Multiple values can be combined with a comma. When several conditions are
+  specified, HAProxy processes them in the following order: first it waits for
+  the CLI confirmation ("cli"), then applies the initial delay ("delay"), then
+  performs the DNS pre-checks ("dns").
 
   This option is only compatible with the dns-01 challenge type.
 
@@ -32322,9 +32327,20 @@ directory <string>
     directory https://acme-staging-v02.api.letsencrypt.org/directory
 
 dns-delay <time>
-  When "challenge-ready" includes "dns", configure the delay before the first
-  DNS resolution attempt and between retries. The value is a time expressed in
-  HAProxy time format (e.g. "5m", "300s"). Default is 30 seconds.
+  Configure the delay used by "challenge-ready" conditions "delay" and "dns".
+  The value is a time expressed in HAProxy time format (e.g. "5m", "300s").
+  Default is 30 seconds.
+
+  Its role depends on the "challenge-ready" conditions in use:
+
+    delay     - the challenge is submitted after this delay expires, without
+                any DNS pre-check.
+
+    dns       - the delay between two consecutive DNS resolution attempts.
+                The first probe fires immediately without any initial wait.
+
+    dns+delay - the initial wait before the first DNS resolution attempt, and
+                the delay between subsequent retries.
 
   Note that the resolution goes through the configured "default" resolvers
   section, not the authoritative name servers. Results may therefore still be
index 50e02f128b5a021f0976e8bf28c04d29c4baa32f..4418d369be7d1b5701bb639852f006934acf9caf 100644 (file)
@@ -14,6 +14,7 @@
 #define ACME_RDY_NONE  0x00
 #define ACME_RDY_CLI   0x01
 #define ACME_RDY_DNS   0x02
+#define ACME_RDY_DELAY 0x04
 
 /* acme section configuration */
 struct acme_cfg {
@@ -52,7 +53,8 @@ enum acme_st {
        ACME_NEWORDER,
        ACME_AUTH,
        ACME_CLI_WAIT,               /* wait for the ACME_RDY_CLI */
-       ACME_RSLV_WAIT,
+       ACME_INITIAL_DELAY,
+       ACME_RSLV_RETRY_DELAY,
        ACME_RSLV_TRIGGER,
        ACME_RSLV_READY,
        ACME_CHALLENGE,
index 0eea8a754e04ac646518b26acadd81210b00e107..eb7137b4c890d21ab0bba94731ffbce7bc0d116d 100644 (file)
@@ -115,22 +115,23 @@ static void acme_trace(enum trace_level level, uint64_t mask, const struct trace
                }
                chunk_appendf(&trace_buf, ", st: ");
                switch (ctx->state) {
-                       case ACME_RESOURCES:      chunk_appendf(&trace_buf, "ACME_RESOURCES");    break;
-                       case ACME_NEWNONCE:       chunk_appendf(&trace_buf, "ACME_NEWNONCE");     break;
-                       case ACME_CHKACCOUNT:     chunk_appendf(&trace_buf, "ACME_CHKACCOUNT");   break;
-                       case ACME_NEWACCOUNT:     chunk_appendf(&trace_buf, "ACME_NEWACCOUNT");   break;
-                       case ACME_NEWORDER:       chunk_appendf(&trace_buf, "ACME_NEWORDER");     break;
-                       case ACME_AUTH:           chunk_appendf(&trace_buf, "ACME_AUTH");         break;
-                       case ACME_CLI_WAIT :      chunk_appendf(&trace_buf, "ACME_CLI_WAIT");    break;
-                       case ACME_RSLV_WAIT:      chunk_appendf(&trace_buf, "ACME_RSLV_WAIT");    break;
-                       case ACME_RSLV_TRIGGER:   chunk_appendf(&trace_buf, "ACME_RSLV_TRIGGER");   break;
-                       case ACME_RSLV_READY:     chunk_appendf(&trace_buf, "ACME_RSLV_READY");   break;
-                       case ACME_CHALLENGE:      chunk_appendf(&trace_buf, "ACME_CHALLENGE");    break;
-                       case ACME_CHKCHALLENGE:   chunk_appendf(&trace_buf, "ACME_CHKCHALLENGE"); break;
-                       case ACME_FINALIZE:       chunk_appendf(&trace_buf, "ACME_FINALIZE");     break;
-                       case ACME_CHKORDER:       chunk_appendf(&trace_buf, "ACME_CHKORDER");     break;
-                       case ACME_CERTIFICATE:    chunk_appendf(&trace_buf, "ACME_CERTIFICATE");  break;
-                       case ACME_END:            chunk_appendf(&trace_buf, "ACME_END");          break;
+                       case ACME_RESOURCES:         chunk_appendf(&trace_buf, "ACME_RESOURCES");        break;
+                       case ACME_NEWNONCE:          chunk_appendf(&trace_buf, "ACME_NEWNONCE");         break;
+                       case ACME_CHKACCOUNT:        chunk_appendf(&trace_buf, "ACME_CHKACCOUNT");       break;
+                       case ACME_NEWACCOUNT:        chunk_appendf(&trace_buf, "ACME_NEWACCOUNT");       break;
+                       case ACME_NEWORDER:          chunk_appendf(&trace_buf, "ACME_NEWORDER");         break;
+                       case ACME_AUTH:              chunk_appendf(&trace_buf, "ACME_AUTH");             break;
+                       case ACME_CLI_WAIT :         chunk_appendf(&trace_buf, "ACME_CLI_WAIT");         break;
+                       case ACME_INITIAL_DELAY:     chunk_appendf(&trace_buf, "ACME_INITIAL_DELAY");    break;
+                       case ACME_RSLV_RETRY_DELAY:  chunk_appendf(&trace_buf, "ACME_RSLV_RETRY_DELAY"); break;
+                       case ACME_RSLV_TRIGGER:      chunk_appendf(&trace_buf, "ACME_RSLV_TRIGGER");     break;
+                       case ACME_RSLV_READY:        chunk_appendf(&trace_buf, "ACME_RSLV_READY");       break;
+                       case ACME_CHALLENGE:         chunk_appendf(&trace_buf, "ACME_CHALLENGE");        break;
+                       case ACME_CHKCHALLENGE:      chunk_appendf(&trace_buf, "ACME_CHKCHALLENGE");     break;
+                       case ACME_FINALIZE:          chunk_appendf(&trace_buf, "ACME_FINALIZE");         break;
+                       case ACME_CHKORDER:          chunk_appendf(&trace_buf, "ACME_CHKORDER");         break;
+                       case ACME_CERTIFICATE:       chunk_appendf(&trace_buf, "ACME_CERTIFICATE");      break;
+                       case ACME_END:               chunk_appendf(&trace_buf, "ACME_END");              break;
                }
        }
        if (mask & (ACME_EV_REQ|ACME_EV_RES)) {
@@ -480,6 +481,9 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
                        } else if (strcmp(str, "dns") == 0) {
                                /* wait for the DNS-check to run the challenge */
                                cur_acme->cond_ready |= ACME_RDY_DNS;
+                       } else if (strcmp(str, "delay") == 0) {
+                               /* wait for the DNS-check to run the challenge */
+                               cur_acme->cond_ready |= ACME_RDY_DELAY;
                        } else if (strcmp(str, "none") == 0) {
                                if (cur_acme->cond_ready || (saveptr && *saveptr)) {
                                        err_code |= ERR_ALERT | ERR_FATAL;
@@ -2417,11 +2421,49 @@ re:
                                goto wait;
 
                        /* next step */
-                       st = ACME_RSLV_WAIT;
+                       st = ACME_INITIAL_DELAY;
                        goto nextreq;
                }
                break;
-               case ACME_RSLV_WAIT: {
+               case ACME_INITIAL_DELAY: {
+                       struct acme_auth *auth;
+                       int all_cond_ready = ctx->cfg->cond_ready;
+
+                       for (auth = ctx->auths; auth != NULL; auth = auth->next) {
+                               all_cond_ready &= auth->ready;
+                       }
+
+                       /* if everything is ready, let's do the challenge request */
+                       if ((all_cond_ready & ctx->cfg->cond_ready) == ctx->cfg->cond_ready) {
+                               st = ACME_CHALLENGE;
+                               goto nextreq;
+                       }
+
+                       /* if we don't have an initial delay, let's trigger */
+                       if (!(ctx->cfg->cond_ready & ACME_RDY_DELAY)) {
+                               st = ACME_RSLV_TRIGGER;
+                               goto nextreq;
+                       }
+
+                       for (auth = ctx->auths; auth != NULL; auth = auth->next) {
+                               auth->ready |= ACME_RDY_DELAY;
+                       }
+
+                       /* either trigger the resolution of the challenge */
+                       if (ctx->cfg->cond_ready & ACME_RDY_DNS)
+                               st = ACME_RSLV_TRIGGER;
+                       else
+                               st = ACME_CHALLENGE;
+                       ctx->http_state = ACME_HTTP_REQ;
+                       ctx->state = st;
+                       send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: waiting %ds\n",
+                                                   ctx->store->path, ctx->cfg->dns_delay);
+
+                       task->expire = tick_add(now_ms, ctx->cfg->dns_delay * 1000);
+                       return task;
+               }
+               break;
+               case ACME_RSLV_RETRY_DELAY: {
                        struct acme_auth *auth;
                        int all_cond_ready = ctx->cfg->cond_ready;
 
@@ -2448,7 +2490,7 @@ re:
                        st = ACME_RSLV_TRIGGER;
                        ctx->http_state = ACME_HTTP_REQ;
                        ctx->state = st;
-                       send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: triggering the resolution in %ds\n",
+                       send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: retrying the resolution in %ds\n",
                                                    ctx->store->path, ctx->cfg->dns_delay);
 
                        task->expire = tick_add(now_ms, ctx->cfg->dns_delay * 1000);
@@ -2519,7 +2561,7 @@ re:
                        }
 
                        /* not all ready yet, retry after dns-delay */
-                       st = ACME_RSLV_WAIT;
+                       st = ACME_RSLV_RETRY_DELAY;
                        ctx->http_state = ACME_HTTP_REQ;
                        ctx->state = st;
                        goto nextreq;