MINOR: acme: extend resolver-based DNS pre-check to dns-persist-01

Add challenge_type parameter to acme_rslv_start() to select the correct
DNS lookup prefix: _validation-persist.<domain> for dns-persist-01 and
_acme-challenge.<domain> for dns-01.

Default cond_ready to ACME_RDY_DNS|ACME_RDY_DELAY for dns-persist-01.
Extend ACME_CLI_WAIT to cover dns-persist-01 alongside dns-01.

In ACME_RSLV_READY, check only TXT record existence for dns-persist-01
since the resolver cannot parse multiple strings within a single TXT entry.
This commit is contained in:
William Lallemand 2026-04-13 18:44:11 +02:00
parent 0d3689959d
commit 39476040ec
3 changed files with 45 additions and 23 deletions

View File

@ -10,7 +10,7 @@
#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);
struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, const char *challenge_type, char **errmsg);
void acme_rslv_free(struct acme_rslv *rslv);
#endif

View File

@ -441,6 +441,11 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
cur_acme->cond_ready = ACME_RDY_CLI;
}
/* dns-persist-01: wait then check for DNS propagation by default */
if ((strcasecmp("dns-persist-01", args[1]) == 0) && (cur_acme->cond_ready == 0)) {
cur_acme->cond_ready = ACME_RDY_DNS | ACME_RDY_DELAY;
}
if ((strcasecmp("http-01", args[1]) == 0) && (cur_acme->cond_ready != 0)) {
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section, \"http-01\" is not compatible with the \"challenge-ready\" option\n", file, linenum, args[0], cursection);
err_code |= ERR_ALERT | ERR_FATAL;
@ -2451,7 +2456,8 @@ re:
goto retry;
}
if ((ctx->next_auth = ctx->next_auth->next) == NULL) {
if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0 && ctx->cfg->cond_ready)
if ((strcasecmp(ctx->cfg->challenge, "dns-01") == 0 ||
strcasecmp(ctx->cfg->challenge, "dns-persist-01") == 0) && ctx->cfg->cond_ready)
st = ACME_CLI_WAIT;
else
st = ACME_CHALLENGE;
@ -2572,7 +2578,7 @@ re:
HA_ATOMIC_INC(&ctx->dnstasks);
auth->rslv = acme_rslv_start(auth, &ctx->dnstasks, &errmsg);
auth->rslv = acme_rslv_start(auth, &ctx->dnstasks, ctx->cfg->challenge, &errmsg);
if (!auth->rslv)
goto abort;
auth->rslv->acme_task = task;
@ -2595,22 +2601,32 @@ re:
for (auth = ctx->auths; auth != NULL; auth = auth->next) {
if (auth->ready == ctx->cfg->cond_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,
/* for dns-01, verify the TXT record content matches the
* expected token. for dns-persist-01, only check that
* the record exists since the resolver cannot read
* multiple strings within a single TXT entry */
if (auth->rslv->result == RSLV_STATUS_VALID) {
if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0) {
if (isteq(auth->rslv->txt, auth->token)) {
auth->ready |= ACME_RDY_DNS;
} 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;
}
} else if (strcasecmp(ctx->cfg->challenge, "dns-persist-01") == 0) {
auth->ready |= ACME_RDY_DNS;
}
} else {
send_log(NULL, LOG_NOTICE, "acme: %s: %s: Couldn't get the TXT record for \"%s.%.*s\" (status=%d)\n",
ctx->store->path, ctx->cfg->challenge,
strcasecmp(ctx->cfg->challenge, "dns-persist-01") == 0 ? "_validation-persist" : "_acme-challenge",
(int)auth->dns.len, auth->dns.ptr,
auth->rslv->result);
all_ready = 0;
} else {
if (isteq(auth->rslv->txt, auth->token)) {
auth->ready |= ACME_RDY_DNS;
} 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;

View File

@ -90,12 +90,13 @@ void acme_rslv_free(struct acme_rslv *rslv)
free(rslv);
}
struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, char **errmsg)
struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, const char *challenge_type, char **errmsg)
{
struct acme_rslv *rslv = NULL;
struct resolvers *resolvers;
char hostname[DNS_MAX_NAME_SIZE + 1];
char dn[DNS_MAX_NAME_SIZE + 1];
const char *prefix;
int hostname_len;
int dn_len;
@ -106,17 +107,22 @@ struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks
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);
/* dns-persist-01 TXT record lives at _validation-persist.<domain>,
* dns-01 TXT record lives at _acme-challenge.<domain> */
prefix = (strcasecmp(challenge_type, "dns-persist-01") == 0)
? "_validation-persist"
: "_acme-challenge";
hostname_len = snprintf(hostname, sizeof(hostname), "%s.%.*s",
prefix, (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);
memprintf(errmsg, "hostname \"%s.%.*s\" too long!\n", prefix, (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);
memprintf(errmsg, "couldn't convert hostname \"%s.%.*s\" into dn label\n", prefix, (int)auth->dns.len, auth->dns.ptr);
goto error;
}