]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: acme: allow to wait and restart the task for DNS-01
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 1 Aug 2025 15:57:29 +0000 (17:57 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 1 Aug 2025 16:07:12 +0000 (18:07 +0200)
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
src/acme.c

index faef5e7e5dd750f3c4aee2d89eaff9dbf64db9ec..f7ce3ce36780559d4d22e94cac5215903042d27d 100644 (file)
@@ -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;
 };
 
index b67240c0e15ccf666e7ffdea819228dd1a3523c0..b5e947be360c116b06bdfab4b7fc3172c9d8713e 100644 (file)
@@ -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 <certfile>                   : 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 <certfile> domain <domain> : show status of certificates configured with ACME", cli_acme_chall_ready_parse, NULL, NULL, NULL, 0 },
        { { NULL }, NULL, NULL, NULL }
 }};