From 9ee14ed2d94ca3894d9927f6628ff488125f8f3d Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Fri, 1 Aug 2025 17:57:29 +0200 Subject: [PATCH] MEDIUM: acme: allow to wait and restart the task for DNS-01 DNS-01 needs a external process which would register a TXT record on a DNS provider, using a REST API or something else. To achieve this, the process should read the dpapi sink and wait for events. With the DNS-01 challenge, HAProxy will put the task to sleep before asking the ACME server to achieve the challenge. The task then need to be woke up, using the command implemented by this patch. This patch implements the "acme challenge_ready" command which should be used by the agent once the challenge was configured in order to wake the task up. Example: echo "@1 acme challenge_ready foobar.pem.rsa domain kikyo" | socat /tmp/master.sock - --- include/haproxy/acme-t.h | 2 ++ src/acme.c | 71 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index faef5e7e5..f7ce3ce36 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -55,6 +55,7 @@ struct acme_auth { struct ist auth; /* auth URI */ struct ist chall; /* challenge URI */ struct ist token; /* token */ + int ready; /* is the challenge ready ? */ void *next; }; @@ -80,6 +81,7 @@ struct acme_ctx { X509_REQ *req; struct ist finalize; struct ist certificate; + struct task *task; struct mt_list el; }; diff --git a/src/acme.c b/src/acme.c index b67240c0e..b5e947be3 100644 --- a/src/acme.c +++ b/src/acme.c @@ -1753,6 +1753,11 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg) goto error; } + /* if the challenge is not DNS-01, consider that the challenge + * is ready because computed by HAProxy */ + if (strcasecmp(ctx->cfg->challenge, "DNS-01") != 0) + auth->ready = 1; + auth->next = ctx->auths; ctx->auths = auth; ctx->next_auth = auth; @@ -2111,6 +2116,11 @@ re: break; case ACME_CHALLENGE: if (http_st == ACME_HTTP_REQ) { + + /* if the challenge is not ready, wait to be wakeup */ + if (!ctx->next_auth->ready) + goto wait; + if (acme_req_challenge(task, ctx, ctx->next_auth, &errmsg) != 0) goto retry; } @@ -2267,8 +2277,16 @@ end: task = NULL; return task; -} +wait: + /* wait for a task_wakeup */ + ctx->http_state = ACME_HTTP_REQ; + ctx->state = st; + task->expire = TICK_ETERNITY; + + MT_LIST_UNLOCK_FULL(&ctx->el, tmp); + return task; +} /* * Return 1 if the certificate must be regenerated * Check if the notAfter date will append in (validity period / 12) or 7 days per default @@ -2534,6 +2552,7 @@ static int acme_start_task(struct ckch_store *store, char **errmsg) ctx->store = newstore; ctx->cfg = cfg; task->context = ctx; + ctx->task = task; MT_LIST_INIT(&ctx->el); MT_LIST_APPEND(&acme_tasks, &ctx->el); @@ -2586,6 +2605,55 @@ err: return cli_dynerr(appctx, errmsg); } +static int cli_acme_chall_ready_parse(char **args, char *payload, struct appctx *appctx, void *private) +{ + char *errmsg = NULL; + const char *crt; + const char *dns; + struct mt_list back; + struct acme_ctx *ctx; + struct acme_auth *auth; + int found = 0; + + if (!*args[2] && !*args[3] && !*args[4]) { + memprintf(&errmsg, ": not enough parameters\n"); + goto err; + } + + crt = args[2]; + dns = args[4]; + + + MT_LIST_FOR_EACH_ENTRY_LOCKED(ctx, &acme_tasks, el, back) { + + if (strcmp(ctx->store->path, crt) != 0) + continue; + + auth = ctx->auths; + while (auth) { + if (strncmp(dns, auth->dns.ptr, auth->dns.len) == 0) { + if (!auth->ready) { + auth->ready = 1; + task_wakeup(ctx->task, TASK_WOKEN_MSG); + found = 1; + } else { + memprintf(&errmsg, "ACME challenge for crt \"%s\" and dns \"%s\" was already READY !\n", crt, dns); + } + break; + } + auth = auth->next; + } + } + if (!found) { + memprintf(&errmsg, "Couldn't find the ACME task using crt \"%s\" and dns \"%s\" !\n", crt, dns); + goto err; + } + + return cli_msg(appctx, LOG_INFO, "Challenge Ready!"); +err: + return cli_dynerr(appctx, errmsg); +} + static int cli_acme_status_io_handler(struct appctx *appctx) { struct ebmb_node *node = NULL; @@ -2668,6 +2736,7 @@ static int cli_acme_ps(char **args, char *payload, struct appctx *appctx, void * static struct cli_kw_list cli_kws = {{ },{ { { "acme", "renew", NULL }, "acme renew : renew a certificate using the ACME protocol", cli_acme_renew_parse, NULL, NULL, NULL, 0 }, { { "acme", "status", NULL }, "acme status : show status of certificates configured with ACME", cli_acme_ps, cli_acme_status_io_handler, NULL, NULL, 0 }, + { { "acme", "challenge_ready", NULL }, "acme challenge_ready domain : show status of certificates configured with ACME", cli_acme_chall_ready_parse, NULL, NULL, NULL, 0 }, { { NULL }, NULL, NULL, NULL } }}; -- 2.47.2