]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: acme: publish ACME_DEPLOY event via event_hdl
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 11 Jun 2026 16:39:51 +0000 (18:39 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 11 Jun 2026 17:14:52 +0000 (19:14 +0200)
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
include/haproxy/acme-t.h
include/haproxy/event_hdl-t.h
src/acme.c
src/event_hdl.c

index ad6dd0b14f6f7834d11063e99b9054098720154a..e850cedb3421d3ae733504aa22d63650021344d5 100644 (file)
@@ -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.<domain>``.
+
+  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
 ======================
index 87c2ce306d56506b9d7989c493e8b57a5674d1d3..fe7e95d3e48ad0e92efb20a417eb9c7291b7647f 100644 (file)
@@ -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.
  */
index d5d8dc32feecaa0fd3b1eaeb089efc1fa41da738..d969291447b6fbb11e54acdfa879e6508fbd463b 100644 (file)
@@ -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)
 
 /*     ---------------------------------------        */
 
index 5aea3146b42cd81f7100d4c2f2ee836995446e0a..6e67587660b7671a8377c37984e1a7f8165ac41a 100644 (file)
@@ -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 <crtname> 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;
index 6e6358e1ac3a5ebb9932b5221835ef5521392121..a149c747158b5a60e688c3f199306f8842340b79 100644 (file)
@@ -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) */