diff --git a/include/haproxy/acme_resolvers.h b/include/haproxy/acme_resolvers.h index fb3d7dff5..0a90abfa2 100644 --- a/include/haproxy/acme_resolvers.h +++ b/include/haproxy/acme_resolvers.h @@ -10,7 +10,7 @@ #include #include -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 diff --git a/src/acme.c b/src/acme.c index c436b9979..33fb8b6bd 100644 --- a/src/acme.c +++ b/src/acme.c @@ -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; diff --git a/src/acme_resolvers.c b/src/acme_resolvers.c index 65d03a36f..3af73da60 100644 --- a/src/acme_resolvers.c +++ b/src/acme_resolvers.c @@ -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. */ - 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., + * dns-01 TXT record lives at _acme-challenge. */ + 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; }