From: William Lallemand Date: Sat, 27 Sep 2025 19:41:39 +0000 (+0200) Subject: MINOR: acme: implement "reuse-key" option X-Git-Tag: v3.3-dev9~26 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b70c7f48fac7de05140ff7ed0cc80e7ded1c9a62;p=thirdparty%2Fhaproxy.git MINOR: acme: implement "reuse-key" option The new "reuse-key" option in the "acme" section, allows to keep the private key instead of generating a new one at each renewal. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 1f7694231..4583653d9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -30699,6 +30699,19 @@ 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 diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index 6babf6d93..a1e25e19e 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -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 { diff --git a/src/acme.c b/src/acme.c index 91da06a1b..a745884ef 100644 --- a/src/acme.c +++ b/src/acme.c @@ -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) {