From 4bb21dae2f75ffb237b1f9a487c3cbdcf7336e8c Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Thu, 11 Jun 2026 18:39:51 +0200 Subject: [PATCH] MINOR: acme: publish ACME_DEPLOY event via event_hdl Add EVENT_HDL_SUB_ACME_DEPLOY to the ACME family. It is published in the dns-01 challenge path after the TXT record information has been prepared, carrying the certificate store name, domain, account thumbprint, dns_record value, and optionally the provider and vars strings. Lua subscribers using core.event_sub() receive the event data as an AcmeEvent object, which is the same class used for ACME_NEWCERT and carries the fields relevant to the event type. --- doc/lua-api/index.rst | 39 ++++++++++++++++++- include/haproxy/acme-t.h | 14 +++++++ include/haproxy/event_hdl-t.h | 5 ++- src/acme.c | 71 ++++++++++++++++++++++++++++++++++- src/event_hdl.c | 1 + 5 files changed, 126 insertions(+), 4 deletions(-) diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst index ad6dd0b14..e850cedb3 100644 --- a/doc/lua-api/index.rst +++ b/doc/lua-api/index.rst @@ -1033,6 +1033,8 @@ Core class **ACME** Family: + * **ACME_DEPLOY**: when a dns-01 challenge TXT record must be deployed + externally before HAProxy can proceed with the ACME challenge * **ACME_NEWCERT**: when a new certificate is successfully installed .. Note:: @@ -4772,13 +4774,46 @@ AcmeEvent class .. js:class:: AcmeEvent -This class is provided with **ACME_NEWCERT** events. +This class is provided with every **ACME** event. See :js:func:`core.event_sub()` for more info. .. js:attribute:: AcmeEvent.crtname - Contains the certificate store name of the newly installed certificate. + Contains the certificate store name. + +.. js:attribute:: AcmeEvent.domain + + Contains the domain being challenged. + + Only available for **ACME_DEPLOY** events. + +.. js:attribute:: AcmeEvent.thumbprint + + Contains the account key JWK thumbprint. + + Only available for **ACME_DEPLOY** events. + +.. js:attribute:: AcmeEvent.dns_record + + Contains the DNS TXT record value that must be set at + ``_acme-challenge.``. + + Only available for **ACME_DEPLOY** events. + +.. js:attribute:: AcmeEvent.provider + + Contains the DNS provider name configured in the ACME section. + Only set if a provider was configured. + + Only available for **ACME_DEPLOY** events. + +.. js:attribute:: AcmeEvent.vars + + Contains the ACME vars string configured in the ACME section. + Only set if vars were configured. + + Only available for **ACME_DEPLOY** events. External Lua libraries ====================== diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index 87c2ce306..fe7e95d3e 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -135,6 +135,20 @@ struct acme_ctx { #define ACME_VERB_ADVANCED 4 #define ACME_VERB_COMPLETE 5 +/* event data for EVENT_HDL_SUB_ACME_DEPLOY: + * published when a dns-01 challenge TXT record must be deployed externally. + */ +struct event_hdl_cb_data_acme_deploy { + struct { + char *crtname; /* certificate store name (heap-allocated) */ + char *domain; /* domain being challenged (heap-allocated) */ + char *thumbprint; /* account key JWK thumbprint (heap-allocated) */ + char *dns_record; /* DNS TXT record value to set (heap-allocated) */ + char *provider; /* DNS provider name (heap-allocated, may be NULL) */ + char *vars; /* ACME vars string (heap-allocated, may be NULL) */ + } safe; +}; + /* event data for EVENT_HDL_SUB_ACME_NEWCERT: * published when a new certificate was successfully installed. */ diff --git a/include/haproxy/event_hdl-t.h b/include/haproxy/event_hdl-t.h index d5d8dc32f..d96929144 100644 --- a/include/haproxy/event_hdl-t.h +++ b/include/haproxy/event_hdl-t.h @@ -291,11 +291,14 @@ struct event_hdl_sub { #define EVENT_HDL_SUB_PAT_REF_CLEAR EVENT_HDL_SUB_TYPE(2,5) /* ACME family, published in global subscription list. - * Provides event_hdl_cb_data_acme_newcert struct (defined in haproxy/acme-t.h). + * Provides event_hdl_cb_data_acme_deploy and event_hdl_cb_data_acme_newcert + * structs (defined in haproxy/acme-t.h). */ #define EVENT_HDL_SUB_ACME EVENT_HDL_SUB_FAMILY(3) /* a new certificate was successfully installed */ #define EVENT_HDL_SUB_ACME_NEWCERT EVENT_HDL_SUB_TYPE(3,1) +/* dns-01 challenge must be deployed externally */ +#define EVENT_HDL_SUB_ACME_DEPLOY EVENT_HDL_SUB_TYPE(3,2) /* --------------------------------------- */ diff --git a/src/acme.c b/src/acme.c index 5aea3146b..6e6758766 100644 --- a/src/acme.c +++ b/src/acme.c @@ -1457,6 +1457,19 @@ error: return ret; } +/* mfree callback for EVENT_HDL_SUB_ACME_DEPLOY: frees heap-allocated fields */ +static void acme_deploy_event_mfree(const void *data) +{ + struct event_hdl_cb_data_acme_deploy *e = (struct event_hdl_cb_data_acme_deploy *)data; + + ha_free(&e->safe.crtname); + ha_free(&e->safe.domain); + ha_free(&e->safe.thumbprint); + ha_free(&e->safe.dns_record); + ha_free(&e->safe.provider); + ha_free(&e->safe.vars); +} + /* mfree callback for EVENT_HDL_SUB_ACME_NEWCERT: frees the heap-allocated path */ static void acme_newcert_event_mfree(const void *data) { @@ -2206,6 +2219,22 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut dpapi = sink_find("dpapi"); if (dpapi) sink_write(dpapi, LOG_HEADER_NONE, 0, line, nmsg); + + { + struct event_hdl_cb_data_acme_deploy cb_data = { }; + + cb_data.safe.crtname = strdup(ctx->store->path); + cb_data.safe.domain = isttest(auth->dns) ? strndup(auth->dns.ptr, auth->dns.len) : NULL; + cb_data.safe.thumbprint = ctx->cfg->account.thumbprint ? strdup(ctx->cfg->account.thumbprint) : NULL; + cb_data.safe.dns_record = strndup(dns_record->area, dns_record->data); + cb_data.safe.provider = ctx->cfg->provider ? strdup(ctx->cfg->provider) : NULL; + cb_data.safe.vars = ctx->cfg->vars ? strdup(ctx->cfg->vars) : NULL; + if (cb_data.safe.crtname && cb_data.safe.dns_record) + event_hdl_publish(NULL, EVENT_HDL_SUB_ACME_DEPLOY, + EVENT_HDL_CB_DATA_DM(&cb_data, acme_deploy_event_mfree)); + else + acme_deploy_event_mfree(&cb_data); + } } else if (strcasecmp(ctx->cfg->challenge, "http-01") == 0) { /* only useful for http-01 */ @@ -3721,6 +3750,40 @@ INITCALL0(STG_REGISTER, __acme_init); #define CLASS_ACME_EVENT "AcmeEvent" static int class_acme_event_ref; +/* Push a new AcmeEvent object for an ACME_DEPLOY event onto the Lua stack. + * The object exposes crtname, domain, thumbprint, dns_record fields, and + * optionally provider and vars if they were configured. + */ +static void hlua_fcn_new_acme_event_deploy(lua_State *L, const struct event_hdl_cb_data_acme_deploy *e) +{ + lua_newtable(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, class_acme_event_ref); + lua_setmetatable(L, -2); + + lua_pushstring(L, e->safe.crtname ? e->safe.crtname : ""); + lua_setfield(L, -2, "crtname"); + + lua_pushstring(L, e->safe.domain ? e->safe.domain : ""); + lua_setfield(L, -2, "domain"); + + lua_pushstring(L, e->safe.thumbprint ? e->safe.thumbprint : ""); + lua_setfield(L, -2, "thumbprint"); + + lua_pushstring(L, e->safe.dns_record ? e->safe.dns_record : ""); + lua_setfield(L, -2, "dns_record"); + + if (e->safe.provider) { + lua_pushstring(L, e->safe.provider); + lua_setfield(L, -2, "provider"); + } + + if (e->safe.vars) { + lua_pushstring(L, e->safe.vars); + lua_setfield(L, -2, "vars"); + } +} + /* Push a new AcmeEvent object for an ACME_NEWCERT event onto the Lua stack. * The object exposes a field with the certificate store name. */ @@ -3787,7 +3850,13 @@ void acme_hlua_event_push_args(struct hlua *hlua, struct event_hdl_sub_type even if (!lua_checkstack(hlua->T, 3)) WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); - if (event_hdl_sub_type_equal(EVENT_HDL_SUB_ACME_NEWCERT, event)) { + if (event_hdl_sub_type_equal(EVENT_HDL_SUB_ACME_DEPLOY, event)) { + struct event_hdl_cb_data_acme_deploy *e_acme = data; + + hlua->nargs += 1; + MAY_LJMP(hlua_fcn_new_acme_event_deploy(hlua->T, e_acme)); + } + else if (event_hdl_sub_type_equal(EVENT_HDL_SUB_ACME_NEWCERT, event)) { struct event_hdl_cb_data_acme_newcert *e_acme = data; hlua->nargs += 1; diff --git a/src/event_hdl.c b/src/event_hdl.c index 6e6358e1a..a149c7471 100644 --- a/src/event_hdl.c +++ b/src/event_hdl.c @@ -50,6 +50,7 @@ static struct event_hdl_sub_type_map event_hdl_sub_type_map[] = { {"PAT_REF_CLEAR", EVENT_HDL_SUB_PAT_REF_CLEAR}, {"ACME", EVENT_HDL_SUB_ACME}, {"ACME_NEWCERT", EVENT_HDL_SUB_ACME_NEWCERT}, + {"ACME_DEPLOY", EVENT_HDL_SUB_ACME_DEPLOY}, }; /* internal types (only used in this file) */