MEDIUM: acme: add a basic scheduler

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.
This commit is contained in:
William Lallemand 2025-05-02 14:55:08 +02:00
parent a279625a5a
commit 2e81a95948
2 changed files with 81 additions and 3 deletions

View File

@ -5933,9 +5933,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:

View 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
*/