Skip to content

Commit 7f6999b

Browse files
committed
MINOR: acme: add 'dns-timeout' keyword for dns-01 challenge
When using the dns-01 challenge method with "challenge-ready dns", HAProxy retries DNS resolution indefinitely at the interval set by "dns-delay". This adds a "dns-timeout" keyword to set a maximum duration for the DNS check phase (default: 600s). If the next resolution attempt would be scheduled beyond that deadline, the renewal is aborted with an explicit error message. A new "dnsstarttime" field is stored in the acme_ctx to record when DNS resolution began, used to evaluate the timeout on each retry.
1 parent c49facb commit 7f6999b

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

doc/configuration.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32320,6 +32320,18 @@ dns-delay <time>
3232032320
section, not the authoritative name servers. Results may therefore still be
3232132321
affected by DNS caching at the resolver level.
3232232322

32323+
dns-timeout <time>
32324+
When "challenge-ready" includes "dns", configure the maximum time allowed to
32325+
successfully resolve the TXT record before aborting the challenge. The value
32326+
is a time expressed in HAProxy time format (e.g. "10m", "600s"). Default is
32327+
600 seconds.
32328+
32329+
If the next DNS resolution attempt would be triggered after the timeout has
32330+
elapsed (taking into account "dns-delay"), the challenge is aborted with an
32331+
error. This prevents an infinite retry loop when DNS propagation fails.
32332+
32333+
See also: "dns-delay"
32334+
3232332335
keytype <string>
3232432336
Configure the type of key that will be generated. Value can be either "RSA"
3232532337
or "ECDSA". You can also configure the "curves" for ECDSA and the number of

include/haproxy/acme-t.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct acme_cfg {
2323
int reuse_key; /* do we need to renew the private key */
2424
int cond_ready; /* ready condition */
2525
unsigned int dns_delay; /* delay in seconds before re-triggering DNS resolution (default: 300) */
26+
unsigned int dns_timeout; /* time after which the DNS check shouldn't be retried (default: 600) */
2627
char *directory; /* directory URL */
2728
char *map; /* storage for tokens + thumbprint */
2829
struct {
@@ -100,6 +101,7 @@ struct acme_ctx {
100101
struct ist finalize;
101102
struct ist certificate;
102103
unsigned int dnstasks; /* number of DNS tasks running for this ctx */
104+
unsigned int dnsstarttime; /* time at which we started the DNS checks */
103105
struct task *task;
104106
struct ebmb_node node;
105107
char name[VAR_ARRAY];

src/acme.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ struct acme_cfg *new_acme_cfg(const char *name)
198198

199199
ret->challenge = strdup("http-01"); /* default value */
200200
ret->dns_delay = 300; /* default DNS re-trigger delay in seconds */
201+
ret->dns_timeout = 600; /* default DNS retry timeout */
201202

202203
/* The default generated keys are EC-384 */
203204
ret->key.type = EVP_PKEY_EC;
@@ -524,6 +525,31 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
524525
err_code |= ERR_ALERT | ERR_FATAL;
525526
goto out;
526527
}
528+
} else if (strcmp(args[0], "dns-timeout") == 0) {
529+
const char *res;
530+
531+
if (!*args[1]) {
532+
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
533+
err_code |= ERR_ALERT | ERR_FATAL;
534+
goto out;
535+
}
536+
if (alertif_too_many_args(1, file, linenum, args, &err_code))
537+
goto out;
538+
539+
res = parse_time_err(args[1], &cur_acme->dns_timeout, TIME_UNIT_S);
540+
if (res == PARSE_TIME_OVER) {
541+
ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
542+
err_code |= ERR_ALERT | ERR_FATAL;
543+
goto out;
544+
} else if (res == PARSE_TIME_UNDER) {
545+
ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
546+
err_code |= ERR_ALERT | ERR_FATAL;
547+
goto out;
548+
} else if (res) {
549+
ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to '%s'\n", file, linenum, *res, args[0]);
550+
err_code |= ERR_ALERT | ERR_FATAL;
551+
goto out;
552+
}
527553
} else if (strcmp(args[0], "reuse-key") == 0) {
528554
if (!*args[1]) {
529555
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
@@ -930,6 +956,7 @@ static struct cfg_kw_list cfg_kws_acme = {ILH, {
930956
{ CFG_ACME, "reuse-key", cfg_parse_acme_kws },
931957
{ CFG_ACME, "challenge-ready", cfg_parse_acme_kws },
932958
{ CFG_ACME, "dns-delay", cfg_parse_acme_kws },
959+
{ CFG_ACME, "dns-timeout", cfg_parse_acme_kws },
933960
{ CFG_ACME, "acme-vars", cfg_parse_acme_vars_provider },
934961
{ CFG_ACME, "provider-name", cfg_parse_acme_vars_provider },
935962
{ CFG_GLOBAL, "acme.scheduler", cfg_parse_global_acme_sched },
@@ -2388,6 +2415,19 @@ struct task *acme_process(struct task *task, void *context, unsigned int state)
23882415
if ((ctx->cfg->cond_ready & ACME_RDY_CLI) && !(all_cond_ready & ACME_RDY_CLI))
23892416
goto wait;
23902417

2418+
/* set the start time of the DNS checks so we can apply
2419+
* the timeout */
2420+
if (ctx->dnsstarttime == 0)
2421+
ctx->dnsstarttime = ns_to_sec(now_ns);
2422+
2423+
/* Check if the next resolution would be triggered too
2424+
* late according to the dns_timeout and abort is
2425+
* necessary. */
2426+
if (ctx->dnsstarttime && ns_to_sec(now_ns) + ctx->cfg->dns_delay > ctx->dnsstarttime + ctx->cfg->dns_timeout) {
2427+
memprintf(&errmsg, "dns-01: Couldn't resolve the TXT records in %ds.", ctx->cfg->dns_timeout);
2428+
goto abort;
2429+
}
2430+
23912431
/* we don't need to wait, we can trigger the resolution
23922432
* after the delay */
23932433
st = ACME_RSLV_TRIGGER;

0 commit comments

Comments
 (0)