]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: acme: add a basic scheduler
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 2 May 2025 12:55:08 +0000 (14:55 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 2 May 2025 14:01:32 +0000 (16:01 +0200)
This patch implements a very basic scheduler for the ACME tasks.

The scheduler is a task which is started from the postparser function
when at least one acme section was configured.

The scheduler will loop over the certificates in the ckchs_tree, and for
each certificate will start an ACME task if the notAfter date is past
curtime + (notAfter - notBefore) / 12, or 7 days if notBefore is not
available.

Once the lookup over all certificates is terminated, the task will sleep
and will wakeup after 12 hours.

doc/configuration.txt
src/acme.c

index 9abe29476c6b3ee987f7009cadbf1573cee7cffc..e0a20dcbfc1df277fc875488243422a83ce79522 100644 (file)
@@ -5936,9 +5936,14 @@ the disk is not supposed to be done after the configuration is loaded, because
 it could block the event loop, blocking the traffic on the same thread. Meaning
 that the certificates and keys generated from HAProxy will need to be dumped
 from outside HAProxy using "dump ssl cert" on the stats socket.
-The generation is not scheduled and must be triggered using the CLI command
-"acme renew". See also "acme ps" in the management guide.
-External Account Biding (EAB) is not supported.
+External Account Binding (EAB) is not supported.
+
+The ACME scheduler starts at HAProxy startup, it will loop over the
+certificates and start an ACME renewal task when the notAfter task is past
+curtime + (notAfter - notBefore) / 12, or 7 days if notBefore is not defined.
+The scheduler will then sleep and wakeup after 12 hours.
+It is possible to start manually a renewal task with "acme renew'.
+See also "acme ps" in the management guide.
 
 The following keywords are usable in the ACME section:
 
index e9c1cd5f7b9e057f777ce8e77699df8bc5ffcb33..e59d4017e8c6103bd31cb4f4a0b9cc8ffa55137f 100644 (file)
@@ -52,6 +52,7 @@ enum acme_ret {
 
 static EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg);
 static int acme_start_task(struct ckch_store *store, char **errmsg);
+static struct task *acme_scheduler(struct task *task, void *context, unsigned int state);
 
 /* Return an existing acme_cfg section */
 struct acme_cfg *get_acme_cfg(const char *name)
@@ -528,6 +529,7 @@ out:
 static int cfg_postparser_acme()
 {
        struct acme_cfg *tmp_acme = acme_cfgs;
+       struct task *task = NULL;
        int ret = 0;
 
         /* first check if the ID was already used */
@@ -543,6 +545,18 @@ static int cfg_postparser_acme()
        }
 
 
+       if (acme_cfgs) {
+               task = task_new_anywhere();
+               if (!task) {
+                       ret++;
+                       ha_alert("acme: couldn't start the scheduler!\n");
+               }
+               task->nice = 0;
+               task->process = acme_scheduler;
+
+               task_wakeup(task, TASK_WOKEN_INIT);
+       }
+
        return ret;
 }
 
@@ -2001,6 +2015,65 @@ end:
        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
+ */
+int acme_will_expire(struct ckch_store *store)
+{
+       int diff = 0;
+       time_t notAfter = 0;
+       time_t notBefore = 0;
+
+       /* compute the validity period of the leaf certificate */
+       if (!store->data || !store->data->cert)
+               return 0;
+
+       notAfter = x509_get_notafter_time_t(store->data->cert);
+       notBefore = x509_get_notbefore_time_t(store->data->cert);
+
+       if (notAfter >= 0 && notBefore >= 0) {
+               diff = (notAfter - notBefore) / 12; /* validity period / 12 */
+       } else {
+               diff = 7 * 24 * 60 * 60; /* default to 7 days */
+       }
+
+       if (date.tv_sec + diff > notAfter)
+               return 1;
+
+       return 0;
+}
+
+/* Does the scheduling of the ACME tasks
+ */
+struct task *acme_scheduler(struct task *task, void *context, unsigned int state)
+{
+       struct ebmb_node *node = NULL;
+       struct ckch_store *store = NULL;
+
+       if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+               return task;
+
+       node = ebmb_first(&ckchs_tree);
+       while (node) {
+               store = ebmb_entry(node, struct ckch_store, node);
+
+               if (store->conf.acme.id) {
+
+                       if (acme_will_expire(store)) {
+                               acme_start_task(store, NULL);
+                       }
+               }
+               node = ebmb_next(node);
+       }
+end:
+       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+       /* call the task again in 12h */
+       /* XXX: need to be configured */
+       task->expire = tick_add(now_ms, 12 * 60 * 60 * 1000);
+       return task;
+}
+
 /*
  * Generate a X509_REQ using a PKEY and a list of SAN finished by a NULL entry
  */