]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: acme: implement "reuse-key" option
authorWilliam Lallemand <wlallemand@irq6.net>
Sat, 27 Sep 2025 19:41:39 +0000 (21:41 +0200)
committerWilliam Lallemand <wlallemand@irq6.net>
Sat, 27 Sep 2025 19:41:39 +0000 (21:41 +0200)
The new "reuse-key" option in the "acme" section, allows to keep the
private key instead of generating a new one at each renewal.

doc/configuration.txt
include/haproxy/acme-t.h
src/acme.c

index 1f76942311395160eb083bf755f0f601211d4867..4583653d970f174f67e565d14651858d698a19ca 100644 (file)
@@ -30699,6 +30699,19 @@ map <map>
   account used. The acme task will add entries before validating the challenge
   and will remove the entries at the end of the task.
 
+reuse-key { on | off }
+  If set to "on", HAProxy won't generate a new private key and will keep the
+  previous one. Rotating private keys is recommended, when enabling this option
+  it is recommended to regenerate manually the keys regularly.
+
+  This option might be useful when using RSA keys bigger than 2048 that can
+  take time to generate and might slow down one thread doing so.
+
+  Using the same key can be useful when using the cache of your ACME server, it
+  can help to retrieve a valid certificate corresponding to the current key.
+
+  The default setting is "off".
+
 Example:
 
   global
index 6babf6d93e1d54ab1db7e960d23f6575ba2ad370..a1e25e19eeaf2b35fa985357c5ba3bbc921abc2d 100644 (file)
@@ -12,6 +12,7 @@ struct acme_cfg {
        char *filename;             /* config filename */
        int linenum;                /* config linenum */
        char *name;                 /* section name */
+       int reuse_key;              /* do we need to renew the private key */
        char *directory;            /* directory URL */
        char *map;                  /* storage for tokens + thumbprint */
        struct {
index 91da06a1bf6b8d19cc47fa9b3594d44952b30057..a745884ef488d0956837897ab3ad3db89657a0b0 100644 (file)
@@ -439,6 +439,24 @@ 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], "reuse-key") == 0) {
+               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;
+
+               if (strcmp(args[1], "on") == 0) {
+                       cur_acme->reuse_key = 1;
+               } else if (strcmp(args[1], "off") == 0) {
+                       cur_acme->reuse_key = 0;
+               } else {
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires either the 'on' or 'off' parameter\n", file, linenum, args[0], cursection);
+                       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;
@@ -794,6 +812,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, "reuse-key",  cfg_parse_acme_kws },
        { CFG_ACME, "acme-vars",  cfg_parse_acme_vars_provider },
        { CFG_ACME, "provider-name",  cfg_parse_acme_vars_provider },
        { CFG_GLOBAL, "acme.scheduler", cfg_parse_global_acme_sched },
@@ -2621,12 +2640,14 @@ static int acme_start_task(struct ckch_store *store, char **errmsg)
        /* set the number of remaining retries when facing an error */
        ctx->retries = ACME_RETRY;
 
-       if ((pkey = acme_EVP_PKEY_gen(cfg->key.type, cfg->key.curves, cfg->key.bits, errmsg)) == NULL)
-               goto err;
+       if (!cfg->reuse_key) {
+               if ((pkey = acme_EVP_PKEY_gen(cfg->key.type, cfg->key.curves, cfg->key.bits, errmsg)) == NULL)
+                       goto err;
 
-       EVP_PKEY_free(newstore->data->key);
-       newstore->data->key = pkey;
-       pkey = NULL;
+               EVP_PKEY_free(newstore->data->key);
+               newstore->data->key = pkey;
+               pkey = NULL;
+       }
 
        ctx->req = acme_x509_req(newstore->data->key, store->conf.acme.domains);
        if (!ctx->req) {