MEDIUM: acme: add dns-01 DNS propagation pre-check

When using the dns-01 challenge type, TXT record propagation across
DNS servers can take time. If the ACME server verifies the challenge
before the record is visible, the challenge fails and it's not possible
to trigger it again.

This patch introduces an optional DNS pre-check mechanism controlled
by two new configuration directives in the "acme" section:

  - "dns-check on|off": enable DNS propagation verification before
    notifying the ACME server (default: off)
  - "dns-delay <time>": delay before querying DNS (default: 300s)

When enabled, three new states are inserted in the state machine
between AUTH and CHALLENGE:

  - ACME_RSLV_WAIT: waits dns-delay seconds before starting
  - ACME_RSLV_TRIGGER: starts an async TXT resolution for each
    pending authorization using HAProxy's resolver infrastructure
  - ACME_RSLV_READY: compares the resolved TXT record against the
    expected token; retries from ACME_RSLV_WAIT if any record is
    missing or does not match

The "acme_rslv" structure is implemented in acme_resolvers.c, it holds
the resolution for each domain. The "auth" structure which contains each
challenge to resolve contains an "acme_rslv" structure. Once
ACME_RSLV_TRIGGER leaves, the DNS tasks run on the same thread, and the
last DNS task which finishes will wake up acme_process().

Note that the resolution goes through the configured resolvers, not
through the authoritative name servers of the domain. The result may
therefore still be affected by DNS caching at the resolver level.
This commit is contained in:
William Lallemand 2026-03-24 21:53:57 +01:00
parent 5dcfbc5fad
commit 631fd5f99b
9 changed files with 443 additions and 24 deletions

View File

@ -643,7 +643,7 @@ ifneq ($(USE_OPENSSL:0=),)
OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \
src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o \
src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \
src/ssl_trace.o src/jwe.o
src/acme_resolvers.o src/ssl_trace.o src/jwe.o
endif
ifneq ($(USE_ENGINE:0=),)

View File

@ -2,9 +2,12 @@
#ifndef _ACME_T_H_
#define _ACME_T_H_
#include <haproxy/acme_resolvers-t.h>
#include <haproxy/istbuf.h>
#include <haproxy/openssl-compat.h>
#if defined(HAVE_ACME)
#define ACME_RETRY 5
/* acme section configuration */
@ -13,6 +16,8 @@ struct acme_cfg {
int linenum; /* config linenum */
char *name; /* section name */
int reuse_key; /* do we need to renew the private key */
int dns_check; /* enable DNS resolution to verify TXT record before challenge */
unsigned int dns_delay; /* delay in seconds before re-triggering DNS resolution (default: 300) */
char *directory; /* directory URL */
char *map; /* storage for tokens + thumbprint */
struct {
@ -40,6 +45,9 @@ enum acme_st {
ACME_NEWACCOUNT,
ACME_NEWORDER,
ACME_AUTH,
ACME_RSLV_WAIT,
ACME_RSLV_TRIGGER,
ACME_RSLV_READY,
ACME_CHALLENGE,
ACME_CHKCHALLENGE,
ACME_FINALIZE,
@ -59,6 +67,7 @@ struct acme_auth {
struct ist chall; /* challenge URI */
struct ist token; /* token */
int validated; /* already validated */
struct acme_rslv *rslv; /* acme dns-01 resolver */
int ready; /* is the challenge ready ? */
void *next;
};
@ -85,6 +94,7 @@ struct acme_ctx {
X509_REQ *req;
struct ist finalize;
struct ist certificate;
unsigned int dnstasks; /* number of DNS tasks running for this ctx */
struct task *task;
struct ebmb_node node;
char name[VAR_ARRAY];
@ -102,4 +112,6 @@ struct acme_ctx {
#define ACME_VERB_ADVANCED 4
#define ACME_VERB_COMPLETE 5
#endif /* ! HAVE_ACME */
#endif

View File

@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _HAPROXY_ACME_RESOLVERS_T_H
#define _HAPROXY_ACME_RESOLVERS_T_H
#include <haproxy/obj_type-t.h>
#include <haproxy/resolvers-t.h>
struct dns_counters;
/* TXT records for dns-01 */
struct acme_rslv {
enum obj_type obj_type; /* OBJ_TYPE_ACME_RSLV */
unsigned int *dnstasks; /* number of running DNS resolution for the same acme_task */
char *hostname_dn;
int hostname_dn_len;
struct resolvers *resolvers;
struct resolv_requester *requester;
int result; /* RSLV_STATUS_* — NONE until done */
int error_code; /* RSLV_RESP_* from the error callback */
struct task *acme_task; /* ACME task to wake on completion, or NULL */
struct ist txt; /* first TXT record found */
int (*success_cb)(struct resolv_requester *, struct dns_counters *);
int (*error_cb)(struct resolv_requester *, int);
};
#endif /* _HAPROXY_ACME_RESOLVERS_T_H */

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _HAPROXY_ACME_RESOLVERS_H
#define _HAPROXY_ACME_RESOLVERS_H
#include <haproxy/openssl-compat.h>
#if defined(HAVE_ACME)
#include <haproxy/acme_resolvers-t.h>
#include <haproxy/acme-t.h>
#include <haproxy/resolvers-t.h>
struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, char **errmsg);
void acme_rslv_free(struct acme_rslv *rslv);
#endif
#endif /* _HAPROXY_ACME_RESOLVERS_H */

View File

@ -47,6 +47,7 @@ enum obj_type {
OBJ_TYPE_DGRAM, /* object is a struct quic_dgram */
#endif
OBJ_TYPE_HATERM, /* object is a struct hstream */
OBJ_TYPE_ACME_RSLV, /* object is a struct acme_rslv */
OBJ_TYPE_ENTRIES /* last one : number of entries */
} __attribute__((packed)) ;

View File

@ -22,6 +22,7 @@
#ifndef _HAPROXY_OBJ_TYPE_H
#define _HAPROXY_OBJ_TYPE_H
#include <haproxy/acme_resolvers-t.h>
#include <haproxy/api.h>
#include <haproxy/applet-t.h>
#include <haproxy/check-t.h>
@ -45,17 +46,18 @@ static inline enum obj_type obj_type(const enum obj_type *t)
static inline const char *obj_type_name(const enum obj_type *t)
{
switch (obj_type(t)) {
case OBJ_TYPE_NONE: return "NONE";
case OBJ_TYPE_LISTENER: return "LISTENER";
case OBJ_TYPE_PROXY: return "PROXY";
case OBJ_TYPE_SERVER: return "SERVER";
case OBJ_TYPE_APPLET: return "APPLET";
case OBJ_TYPE_APPCTX: return "APPCTX";
case OBJ_TYPE_CONN: return "CONN";
case OBJ_TYPE_SRVRQ: return "SRVRQ";
case OBJ_TYPE_SC: return "SC";
case OBJ_TYPE_STREAM: return "STREAM";
case OBJ_TYPE_CHECK: return "CHECK";
case OBJ_TYPE_NONE: return "NONE";
case OBJ_TYPE_LISTENER: return "LISTENER";
case OBJ_TYPE_PROXY: return "PROXY";
case OBJ_TYPE_SERVER: return "SERVER";
case OBJ_TYPE_APPLET: return "APPLET";
case OBJ_TYPE_APPCTX: return "APPCTX";
case OBJ_TYPE_CONN: return "CONN";
case OBJ_TYPE_SRVRQ: return "SRVRQ";
case OBJ_TYPE_SC: return "SC";
case OBJ_TYPE_STREAM: return "STREAM";
case OBJ_TYPE_CHECK: return "CHECK";
case OBJ_TYPE_ACME_RSLV: return "ACME_RSLV";
#ifdef USE_QUIC
case OBJ_TYPE_DGRAM: return "DGRAM";
#endif
@ -203,6 +205,18 @@ static inline struct hstream *objt_hstream(enum obj_type *t)
return __objt_hstream(t);
}
static inline struct acme_rslv *__objt_acme_rslv(enum obj_type *t)
{
return container_of(t, struct acme_rslv, obj_type);
}
static inline struct acme_rslv *objt_acme_rslv(enum obj_type *t)
{
if (!t || *t != OBJ_TYPE_ACME_RSLV)
return NULL;
return __objt_acme_rslv(t);
}
#ifdef USE_QUIC
static inline struct quic_dgram *__objt_dgram(enum obj_type *t)
{
@ -220,17 +234,18 @@ static inline struct quic_dgram *objt_dgram(enum obj_type *t)
static inline void *obj_base_ptr(enum obj_type *t)
{
switch (obj_type(t)) {
case OBJ_TYPE_NONE: return NULL;
case OBJ_TYPE_LISTENER: return __objt_listener(t);
case OBJ_TYPE_PROXY: return __objt_proxy(t);
case OBJ_TYPE_SERVER: return __objt_server(t);
case OBJ_TYPE_APPLET: return __objt_applet(t);
case OBJ_TYPE_APPCTX: return __objt_appctx(t);
case OBJ_TYPE_CONN: return __objt_conn(t);
case OBJ_TYPE_SRVRQ: return __objt_resolv_srvrq(t);
case OBJ_TYPE_SC: return __objt_sc(t);
case OBJ_TYPE_STREAM: return __objt_stream(t);
case OBJ_TYPE_CHECK: return __objt_check(t);
case OBJ_TYPE_NONE: return NULL;
case OBJ_TYPE_LISTENER: return __objt_listener(t);
case OBJ_TYPE_PROXY: return __objt_proxy(t);
case OBJ_TYPE_SERVER: return __objt_server(t);
case OBJ_TYPE_APPLET: return __objt_applet(t);
case OBJ_TYPE_APPCTX: return __objt_appctx(t);
case OBJ_TYPE_CONN: return __objt_conn(t);
case OBJ_TYPE_SRVRQ: return __objt_resolv_srvrq(t);
case OBJ_TYPE_SC: return __objt_sc(t);
case OBJ_TYPE_STREAM: return __objt_stream(t);
case OBJ_TYPE_CHECK: return __objt_check(t);
case OBJ_TYPE_ACME_RSLV: return __objt_acme_rslv(t);
#ifdef USE_QUIC
case OBJ_TYPE_DGRAM: return __objt_dgram(t);
#endif

View File

@ -4,6 +4,7 @@
* Implements the ACMEv2 RFC 8555 protocol
*/
#include "haproxy/ticks.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@ -14,6 +15,7 @@
#include <haproxy/acme-t.h>
#include <haproxy/acme_resolvers.h>
#include <haproxy/base64.h>
#include <haproxy/intops.h>
#include <haproxy/cfgparse.h>
@ -24,6 +26,7 @@
#include <haproxy/list.h>
#include <haproxy/log.h>
#include <haproxy/pattern.h>
#include <haproxy/resolvers.h>
#include <haproxy/sink.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_gencert.h>
@ -118,6 +121,9 @@ static void acme_trace(enum trace_level level, uint64_t mask, const struct trace
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_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;
@ -191,6 +197,7 @@ struct acme_cfg *new_acme_cfg(const char *name)
ret->linenum = 0;
ret->challenge = strdup("http-01"); /* default value */
ret->dns_delay = 300; /* default DNS re-trigger delay in seconds */
/* The default generated keys are EC-384 */
ret->key.type = EVP_PKEY_EC;
@ -437,6 +444,49 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
goto out;
}
} else if (strcmp(args[0], "dns-check") == 0) {
if (!*args[1]) {
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out;
if (strcmp(args[1], "on") == 0) {
cur_acme->dns_check = 1;
} else if (strcmp(args[1], "off") == 0) {
cur_acme->dns_check = 0;
} else {
err_code |= ERR_ALERT | ERR_FATAL;
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires either the 'on' or 'off' parameter\n", file, linenum, args[0], cursection);
goto out;
}
} else if (strcmp(args[0], "dns-delay") == 0) {
const char *res;
if (!*args[1]) {
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out;
res = parse_time_err(args[1], &cur_acme->dns_delay, TIME_UNIT_S);
if (res == PARSE_TIME_OVER) {
ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
} else if (res == PARSE_TIME_UNDER) {
ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
} else if (res) {
ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to '%s'\n", file, linenum, *res, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
} else if (strcmp(args[0], "reuse-key") == 0) {
if (!*args[1]) {
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
@ -841,6 +891,8 @@ static struct cfg_kw_list cfg_kws_acme = {ILH, {
{ CFG_ACME, "curves", cfg_parse_acme_cfg_key },
{ CFG_ACME, "map", cfg_parse_acme_kws },
{ CFG_ACME, "reuse-key", cfg_parse_acme_kws },
{ CFG_ACME, "dns-check", cfg_parse_acme_kws },
{ CFG_ACME, "dns-delay", cfg_parse_acme_kws },
{ CFG_ACME, "acme-vars", cfg_parse_acme_vars_provider },
{ CFG_ACME, "provider-name", cfg_parse_acme_vars_provider },
{ CFG_GLOBAL, "acme.scheduler", cfg_parse_global_acme_sched },
@ -879,6 +931,7 @@ static void acme_ctx_destroy(struct acme_ctx *ctx)
istfree(&auth->chall);
istfree(&auth->token);
istfree(&auth->dns);
acme_rslv_free(auth->rslv);
next = auth->next;
free(auth);
auth = next;
@ -2272,13 +2325,115 @@ re:
goto retry;
}
if ((ctx->next_auth = ctx->next_auth->next) == NULL) {
st = ACME_CHALLENGE;
if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0 && ctx->cfg->dns_check)
st = ACME_RSLV_WAIT;
else
st = ACME_CHALLENGE;
ctx->next_auth = ctx->auths;
}
/* call with next auth or do the challenge step */
goto nextreq;
}
break;
case ACME_RSLV_WAIT: {
/* wait dns-delay */
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",
ctx->store->path, ctx->cfg->dns_delay);
task->expire = tick_add(now_ms, ctx->cfg->dns_delay * 1000);
return task;
}
break;
case ACME_RSLV_TRIGGER: {
struct acme_auth *auth;
/* if it was trigger by the CLI, still wait dns_delay if
* not everything is ready, or skip and to to
* ACME_CHALLENGE */
if (!(state & TASK_WOKEN_TIMER)) {
int all_ready = 1;
for (auth = ctx->auths; auth != NULL; auth = auth->next) {
if (auth->ready)
continue;
all_ready = 0;
}
if (all_ready) {
st = ACME_CHALLENGE;
ctx->http_state = ACME_HTTP_REQ;
ctx->state = st;
goto nextreq;
} else {
return task;
}
}
/* on timer expiry, re-trigger resolution for non-ready auths */
for (auth = ctx->auths; auth != NULL; auth = auth->next) {
if (auth->ready)
continue;
HA_ATOMIC_INC(&ctx->dnstasks);
auth->rslv = acme_rslv_start(auth, &ctx->dnstasks, &errmsg);
if (!auth->rslv)
goto abort;
auth->rslv->acme_task = task;
}
st = ACME_RSLV_READY;
goto wait;
}
break;
case ACME_RSLV_READY: {
struct acme_auth *auth;
int all_ready = 1;
/* if triggered by the CLI, wait for the DNS tasks to
* finish
*/
if (HA_ATOMIC_LOAD(&ctx->dnstasks) != 0)
goto wait;
/* triggered by the latest DNS task */
for (auth = ctx->auths; auth != NULL; auth = auth->next) {
if (auth->ready)
continue;
if (auth->rslv->result != RSLV_STATUS_VALID) {
send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: Couldn't get the TXT record for \"_acme-challenge.%.*s\", expected \"%.*s\" (status=%d)\n",
ctx->store->path, (int)auth->dns.len, auth->dns.ptr,
(int)auth->token.len, auth->token.ptr,
auth->rslv->result);
all_ready = 0;
} else {
if (isteq(auth->rslv->txt, auth->token)) {
auth->ready = 1;
} else {
send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: TXT record mismatch for \"_acme-challenge.%.*s\": expected \"%.*s\", got \"%.*s\"\n",
ctx->store->path, (int)auth->dns.len, auth->dns.ptr,
(int)auth->token.len, auth->token.ptr,
(int)auth->rslv->txt.len, auth->rslv->txt.ptr);
all_ready = 0;
}
}
acme_rslv_free(auth->rslv);
auth->rslv = NULL;
}
if (all_ready) {
st = ACME_CHALLENGE;
ctx->next_auth = ctx->auths;
goto nextreq;
}
/* not all ready yet, retry after dns-delay */
st = ACME_RSLV_WAIT;
ctx->http_state = ACME_HTTP_REQ;
ctx->state = st;
goto nextreq;
}
break;
case ACME_CHALLENGE:
if (http_st == ACME_HTTP_REQ) {
/* if challenge is already validated we skip this stage */

166
src/acme_resolvers.c Normal file
View File

@ -0,0 +1,166 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Implements the DNS resolution pre-check for dns-01
*/
#include <haproxy/openssl-compat.h>
#if defined(HAVE_ACME)
#include <stdlib.h>
#include <string.h>
#include <haproxy/acme_resolvers.h>
#include <haproxy/applet.h>
#include <haproxy/obj_type.h>
#include <haproxy/resolvers.h>
#include <haproxy/tools.h>
/* success callback, copy the TXT string to rslv->txt */
static int acme_rslv_success_cb(struct resolv_requester *req, struct dns_counters *counters)
{
struct acme_rslv *rslv = objt_acme_rslv(req->owner);
struct resolv_resolution *res;
struct eb32_node *eb32;
struct resolv_answer_item *item;
if (!rslv)
return 1;
rslv->result = RSLV_STATUS_INVALID;
res = req->resolution;
if (!res)
goto done;
/* XXX: must fail on multiple TXT entries for the same dn */
/* copy the data from the response tree */
for (eb32 = eb32_first(&res->response.answer_tree); eb32 != NULL; eb32 = eb32_next(eb32)) {
item = eb32_entry(eb32, typeof(*item), link);
/* only handle 1 entry */
if (item->type == DNS_RTYPE_TXT) {
int len = item->data_len;
if (len > DNS_MAX_NAME_SIZE)
len = DNS_MAX_NAME_SIZE;
rslv->txt = istdup(ist2(item->data.target, len));
break;
}
}
rslv->result = RSLV_STATUS_VALID;
done:
/* if there's no other DNS task for this acme task, wake up acme_task */
if (HA_ATOMIC_SUB_FETCH(rslv->dnstasks, 1) == 0) {
if (rslv->acme_task)
task_wakeup(rslv->acme_task, TASK_WOKEN_MSG);
}
return 1;
}
/* error callback, set the error code to rslv->result */
static int acme_rslv_error_cb(struct resolv_requester *req, int error_code)
{
struct acme_rslv *rslv = objt_acme_rslv(req->owner);
if (!rslv)
return 0;
rslv->result = error_code;
if (HA_ATOMIC_SUB_FETCH(rslv->dnstasks, 1) == 0) {
if (rslv->acme_task)
task_wakeup(rslv->acme_task, TASK_WOKEN_MSG);
}
return 0;
}
/* unlink from the resolver and free the acme_rslv */
void acme_rslv_free(struct acme_rslv *rslv)
{
if (!rslv)
return;
if (rslv->requester)
resolv_unlink_resolution(rslv->requester);
free(rslv->hostname_dn);
istfree(&rslv->txt);
free(rslv);
}
struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, char **errmsg)
{
struct acme_rslv *rslv = NULL;
struct resolvers *resolvers;
char hostname[DNS_MAX_NAME_SIZE + 1];
char dn[DNS_MAX_NAME_SIZE + 1];
int hostname_len;
int dn_len;
/* XXX: allow to change the resolvers section to use */
resolvers = find_resolvers_by_id("default");
if (!resolvers) {
memprintf(errmsg, "couldn't find the \"default\" resolvers section!\n");
goto error;
}
/* dns-01 TXT record lives at _acme-challenge.<domain> */
hostname_len = snprintf(hostname, sizeof(hostname), "_acme-challenge.%.*s",
(int)auth->dns.len, auth->dns.ptr);
if (hostname_len < 0 || hostname_len >= (int)sizeof(hostname)) {
memprintf(errmsg, "hostname \"_acme-challenge.%.*s\" too long!\n", (int)auth->dns.len, auth->dns.ptr);
goto error;
}
dn_len = resolv_str_to_dn_label(hostname, hostname_len, dn, sizeof(dn));
if (dn_len <= 0) {
memprintf(errmsg, "couldn't convert hostname \"_acme-challenge.%.*s\" into dn label\n", (int)auth->dns.len, auth->dns.ptr);
goto error;
}
rslv = calloc(1, sizeof(*rslv));
if (!rslv) {
memprintf(errmsg, "Could not allocate memory\n");
goto error;
}
rslv->obj_type = OBJ_TYPE_ACME_RSLV;
rslv->resolvers = resolvers;
rslv->hostname_dn = strdup(dn);
rslv->hostname_dn_len = dn_len;
rslv->result = RSLV_STATUS_NONE;
rslv->success_cb = acme_rslv_success_cb;
rslv->error_cb = acme_rslv_error_cb;
rslv->dnstasks = dnstasks;
if (!rslv->hostname_dn) {
memprintf(errmsg, "Could not allocate memory\n");
goto error;
}
if (resolv_link_resolution(rslv, OBJ_TYPE_ACME_RSLV, 0) < 0) {
memprintf(errmsg, "Could not create resolution task for \"%.*s\"\n", hostname_len, hostname);
goto error;
}
resolv_trigger_resolution(rslv->requester);
return rslv;
error:
if (rslv)
free(rslv->hostname_dn);
free(rslv);
return NULL;
}
#endif /* HAVE_ACME */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

View File

@ -21,6 +21,7 @@
#include <import/cebis_tree.h>
#include <haproxy/action.h>
#include <haproxy/acme_resolvers.h>
#include <haproxy/api.h>
#include <haproxy/applet.h>
#include <haproxy/cfgparse.h>
@ -2170,6 +2171,25 @@ int resolv_link_resolution(void *requester, int requester_type, int requester_lo
? DNS_RTYPE_A
: DNS_RTYPE_AAAA;
break;
#if defined(HAVE_ACME)
case OBJ_TYPE_ACME_RSLV: {
struct acme_rslv *acme_rslv = (struct acme_rslv *)requester;
req = resolv_get_requester(&acme_rslv->requester,
&acme_rslv->obj_type,
acme_rslv->success_cb,
acme_rslv->error_cb);
if (!req)
goto err;
hostname_dn = &acme_rslv->hostname_dn;
hostname_dn_len = acme_rslv->hostname_dn_len;
resolvers = acme_rslv->resolvers;
query_type = DNS_RTYPE_TXT;
break;
}
#endif
default:
goto err;
}
@ -2557,6 +2577,11 @@ struct task *process_resolvers(struct task *t, void *context, unsigned int state
/* Always perform the resolution */
must_run = 1;
break;
case OBJ_TYPE_ACME_RSLV:
/* Always perform the resolution */
must_run = 1;
break;
default:
break;
}