mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-05-01 03:01:00 +02:00
MINOR: acme: implement draft-ietf-acme-profiles
The ACME Profiles extension (draft-ietf-acme-profiles) allows a client to request a specific certificate profile by including a "profile" field in the newOrder request. This lets the CA select the appropriate certificate issuance policy (e.g. "classic", "shortlived") for a given order. A new "profile" keyword is added to the acme section. When set, its value is included in the newOrder JSON payload sent to the CA.
This commit is contained in:
parent
1ddda8eb3b
commit
0d14bb7473
@ -32538,6 +32538,22 @@ 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.
|
||||
|
||||
profile <string>
|
||||
Request a specific certificate profile from the CA by including a "profile"
|
||||
field in the newOrder request. This implements draft-ietf-acme-profiles.
|
||||
|
||||
Profile names are CA-specific short identifiers (e.g. "classic",
|
||||
"shortlived"). When set, the profile name is sent as-is in the newOrder
|
||||
JSON payload. The CA is free to ignore the request or return an error if
|
||||
the profile is not supported. When not set, no profile field is included
|
||||
and the CA uses its default issuance policy.
|
||||
|
||||
See https://letsencrypt.org/docs/profiles/ for letsencrypt profiles.
|
||||
|
||||
Example:
|
||||
# Request short-lived certificates
|
||||
profile shortlived
|
||||
|
||||
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
|
||||
|
||||
@ -41,6 +41,7 @@ struct acme_cfg {
|
||||
int curves; /* NID of curves */
|
||||
} key;
|
||||
char *challenge; /* HTTP-01, DNS-01, etc */
|
||||
char *profile; /* ACME profile */
|
||||
char *vars; /* variables put in the dpapi sink */
|
||||
char *provider; /* DNS provider put in the dpapi sink */
|
||||
struct acme_cfg *next;
|
||||
|
||||
37
src/acme.c
37
src/acme.c
@ -4,6 +4,7 @@
|
||||
* Implements the ACMEv2 RFC 8555 protocol
|
||||
* Implements the following extensions to the protocol:
|
||||
* draft-ietf-acme-dns-persist - DNS-PERSIST-01 challenge
|
||||
* draft-ietf-acme-profiles - Profiles Extension
|
||||
*/
|
||||
|
||||
#include "haproxy/ticks.h"
|
||||
@ -454,6 +455,35 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
|
||||
goto out;
|
||||
}
|
||||
|
||||
} else if (strcmp(args[0], "profile") == 0) {
|
||||
/* save the profile name */
|
||||
const char *p;
|
||||
|
||||
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;
|
||||
|
||||
/* profile names are used verbatim in a JSON string; only allow
|
||||
* alphanumeric characters, hyphens and underscores */
|
||||
for (p = args[1]; *p; p++) {
|
||||
if (!isalnum((uchar)*p) && *p != '-' && *p != '_') {
|
||||
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section contains unauthorized character '%c'\n", file, linenum, args[0], cursection, *p);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ha_free(&cur_acme->profile);
|
||||
cur_acme->profile = strdup(args[1]);
|
||||
if (!cur_acme->profile) {
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
|
||||
goto out;
|
||||
}
|
||||
} else if (strcmp(args[0], "map") == 0) {
|
||||
/* save the map name for thumbprint + token storage */
|
||||
if (!*args[1]) {
|
||||
@ -957,6 +987,7 @@ void deinit_acme()
|
||||
ha_free(&acme_cfgs->provider);
|
||||
ha_free(&acme_cfgs->challenge);
|
||||
ha_free(&acme_cfgs->map);
|
||||
ha_free(&acme_cfgs->profile);
|
||||
|
||||
free(acme_cfgs);
|
||||
acme_cfgs = next;
|
||||
@ -974,6 +1005,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, "profile", cfg_parse_acme_kws },
|
||||
{ CFG_ACME, "reuse-key", cfg_parse_acme_kws },
|
||||
{ CFG_ACME, "challenge-ready", cfg_parse_acme_kws },
|
||||
{ CFG_ACME, "dns-delay", cfg_parse_acme_kws },
|
||||
@ -2014,7 +2046,10 @@ int acme_req_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
|
||||
chunk_appendf(req_in, "%s{ \"type\": \"dns\", \"value\": \"%s\" }", (*san == *ctx->store->conf.acme.domains) ? "" : ",", *san);
|
||||
}
|
||||
|
||||
chunk_appendf(req_in, " ] }");
|
||||
chunk_appendf(req_in, " ]");
|
||||
if (ctx->cfg->profile)
|
||||
chunk_appendf(req_in, ", \"profile\": \"%s\"", ctx->cfg->profile);
|
||||
chunk_appendf(req_in, " }");
|
||||
|
||||
TRACE_DATA("NewOrder Decode", ACME_EV_REQ, ctx, &ctx->resources.newOrder, req_in);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user