From 92c31a6fb7a2ce80e1ca136b489eed2f836a19c9 Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Thu, 18 Sep 2025 17:54:27 +0200 Subject: [PATCH] MINOR: acme: acme-vars allow to pass data to the dpapi sink In the case of the dns-01 challenge, the agent that handles the challenge might need some extra information which depends on the DNS provider. This patch introduces the "acme-vars" option in the acme section, which allows to pass these data to the dpapi sink. The double quotes will be escaped when printed in the sink. Example: global setenv VAR1 'foobar"toto"' acme LE directory https://acme-staging-v02.api.letsencrypt.org/directory challenge DNS-01 acme-vars "var1=${VAR1},var2=var2" Would output: $ ( echo "@@1 show events dpapi -w -0"; cat - ) | socat /tmp/master.sock - | cat -e <0>2025-09-18T17:53:58.831140+02:00 acme deploy foobpar.pem thumbprint gDvbPL3w4J4rxb8gj20mGEgtuicpvltnTl6j1kSZ3vQ$ acme-vars "var1=foobar\"toto\",var2=var2"$ {$ "identifier": {$ "type": "dns",$ "value": "example.com"$ },$ "status": "pending",$ "expires": "2025-09-25T14:41:57Z",$ [...] --- include/haproxy/acme-t.h | 1 + src/acme.c | 62 ++++++++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index f7ce3ce36..62121ef1d 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -27,6 +27,7 @@ struct acme_cfg { int curves; /* NID of curves */ } key; char *challenge; /* HTTP-01, DNS-01, etc */ + char *vars; /* variables put in the dpapi sink */ struct acme_cfg *next; }; diff --git a/src/acme.c b/src/acme.c index 2d8319cdc..bc16ced4b 100644 --- a/src/acme.c +++ b/src/acme.c @@ -439,6 +439,42 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); goto out; } + } else if (strcmp(args[0], "acme-vars") == 0) { + /* save acme-vars */ + char *src = args[1]; + char *dst = NULL; + int i = 0; + int len; + + if (!*args[1]) { + ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + len = strlen(src); + dst = realloc(dst, len + 1); + + /* escape the " character */ + while (*src) { + if (*src == '"') { + len++; + dst = realloc(dst, len + 1); + dst[i++] = '\\'; /* add escaping */ + } + dst[i++] = *src; + src++; + } + dst[i] = '\0'; + + cur_acme->vars = dst; + if (!cur_acme->vars) { + err_code |= ERR_ALERT | ERR_FATAL; + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + goto out; + } } else if (*args[0] != 0) { ha_alert("parsing [%s:%d]: unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection); err_code |= ERR_ALERT | ERR_FATAL; @@ -707,6 +743,7 @@ void deinit_acme() ha_free(&acme_cfgs->account.contact); ha_free(&acme_cfgs->account.file); ha_free(&acme_cfgs->account.thumbprint); + ha_free(&acme_cfgs->vars); free(acme_cfgs); acme_cfgs = next; @@ -722,6 +759,7 @@ static struct cfg_kw_list cfg_kws_acme = {ILH, { { CFG_ACME, "bits", cfg_parse_acme_cfg_key }, { CFG_ACME, "curves", cfg_parse_acme_cfg_key }, { CFG_ACME, "map", cfg_parse_acme_kws }, + { CFG_ACME, "acme-vars", cfg_parse_acme_kws }, { CFG_GLOBAL, "acme.scheduler", cfg_parse_global_acme_sched }, { 0, NULL, NULL }, }}; @@ -1584,7 +1622,8 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut /* compute a response for the TXT entry */ if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0) { struct sink *dpapi; - struct ist line[7]; + struct ist line[10]; + int nmsg = 0; if (acme_txt_record(ist(ctx->cfg->account.thumbprint), auth->token, &trash) == 0) { memprintf(errmsg, "couldn't compute the dns-01 challenge"); @@ -1595,18 +1634,23 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut ctx->store->path, (int)auth->dns.len, auth->dns.ptr, (int)trash.data, trash.area); /* dump to the "dpapi" sink */ + line[nmsg++] = ist("acme deploy "); + line[nmsg++] = ist(ctx->store->path); + line[nmsg++] = ist(" thumbprint "); + line[nmsg++] = ist(ctx->cfg->account.thumbprint); + line[nmsg++] = ist("\n"); - line[0] = ist("acme deploy "); - line[1] = ist(ctx->store->path); - line[2] = ist(" thumbprint "); - line[3] = ist(ctx->cfg->account.thumbprint); - line[4] = ist("\n"); - line[5] = ist2( hc->res.buf.area, hc->res.buf.data); /* dump the HTTP response */ - line[6] = ist("\n\0"); + if (ctx->cfg->vars) { + line[nmsg++] = ist("acme-vars \""); + line[nmsg++] = ist(ctx->cfg->vars); + line[nmsg++] = ist("\"\n"); + } + line[nmsg++] = ist2( hc->res.buf.area, hc->res.buf.data); /* dump the HTTP response */ + line[nmsg++] = ist("\n\0"); dpapi = sink_find("dpapi"); if (dpapi) - sink_write(dpapi, LOG_HEADER_NONE, 0, line, 7); + sink_write(dpapi, LOG_HEADER_NONE, 0, line, nmsg); } /* only useful for http-01 */