]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: acme: skip doing challenge if it is already valid
authorMia Kanashi <chad@redpilled.dev>
Sun, 22 Feb 2026 23:04:46 +0000 (01:04 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 27 Mar 2026 13:41:11 +0000 (14:41 +0100)
If server returns an auth with status valid it seems that client
needs to always skip it, CA can recycle authorizations, without
this change haproxy fails to obtain certificates in that case.
It is also something that is explicitly allowed and stated
in the dns-persist-01 draft RFC.

Note that it would be better to change how haproxy does status polling,
and implements the state machine, but that will take some thought
and time, this patch is a quick fix of the problem.

See:
https://github.com/letsencrypt/boulder/issues/2125
https://github.com/letsencrypt/pebble/issues/133

This must be backported to 3.2 and later.

include/haproxy/acme-t.h
src/acme.c

index b26682dd01c4de0cb2f5f447b5ba0b3b0005c126..e93b617c7d80563e40641149ac143e84d53ebb70 100644 (file)
@@ -58,6 +58,7 @@ struct acme_auth {
        struct ist auth;   /* auth URI */
        struct ist chall;  /* challenge URI */
        struct ist token;  /* token */
+       int validated;     /* already validated */
        int ready;         /* is the challenge ready ? */
        void *next;
 };
index 406c3cfaef2fc092e788a421d63d80a83ba1bb1b..44b0d79956c0328cb5f9b74acc05b81bd4ff739c 100644 (file)
@@ -1654,6 +1654,19 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
 
        auth->dns = istdup(ist2(t2->area, t2->data));
 
+       ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.status", trash.area, trash.size);
+       if (ret == -1) {
+               memprintf(errmsg, "couldn't get a \"status\" from Authorization URL \"%s\"", auth->auth.ptr);
+               goto error;
+       }
+       trash.data = ret;
+
+       /* if auth is already valid we need to skip solving challenges */
+       if (strncasecmp("valid", trash.area, trash.data) == 0) {
+               auth->validated = 1;
+               goto out;
+       }
+
        /* get the multiple challenges and select the one from the configuration */
        for (i = 0; ; i++) {
                int ret;
@@ -1761,6 +1774,7 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
                break;
        }
 
+out:
        ret = 0;
 
 error:
@@ -2263,6 +2277,14 @@ re:
                break;
                case ACME_CHALLENGE:
                        if (http_st == ACME_HTTP_REQ) {
+                               /* if challenge is already validated we skip this stage */
+                               if (ctx->next_auth->validated) {
+                                       if ((ctx->next_auth = ctx->next_auth->next) == NULL) {
+                                               st = ACME_CHKCHALLENGE;
+                                               ctx->next_auth = ctx->auths;
+                                       }
+                                       goto nextreq;
+                               }
 
                                /* if the challenge is not ready, wait to be wakeup */
                                if (!ctx->next_auth->ready)
@@ -2292,6 +2314,14 @@ re:
                break;
                case ACME_CHKCHALLENGE:
                        if (http_st == ACME_HTTP_REQ) {
+                               /* if challenge is already validated we skip this stage */
+                               if (ctx->next_auth->validated) {
+                                       if ((ctx->next_auth = ctx->next_auth->next) == NULL)
+                                               st = ACME_FINALIZE;
+
+                                       goto nextreq;
+                               }
+
                                if (acme_post_as_get(task, ctx, ctx->next_auth->chall, &errmsg) != 0)
                                        goto retry;
                        }