diff --git a/doc/configuration.txt b/doc/configuration.txt index 1a1f3a13a..900f6635b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -5981,6 +5981,11 @@ keytype or "ECDSA". You can also configure the "curves" for ECDSA and the number of "bits" for RSA. By default EC384 keys are generated. +map + Configure the map which will be used to store token (key) and thumbprint + (value), which is useful to reply to a challenge when there are multiple + account used. The acme task will add entries before validating the challenge + and will remove the entries at the end of the task. Example: @@ -5991,7 +5996,7 @@ Example: frontend in bind *:80 bind *:443 ssl - http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' } + http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].%[path,field(-1,/),map(virt@acme)]\n" if { path_beg '/.well-known/acme-challenge/' } ssl-f-use crt "foo.example.com.pem.rsa" acme LE1 domains "foo.example.com.pem,bar.example.com" ssl-f-use crt "foo.example.com.pem.ecdsa" acme LE2 domains "foo.example.com.pem,bar.example.com" @@ -6002,6 +6007,7 @@ Example: challenge HTTP-01 keytype RSA bits 2048 + map virt@acme acme LE2 directory https://acme-staging-v02.api.letsencrypt.org/directory @@ -6010,6 +6016,7 @@ Example: challenge HTTP-01 keytype ECDSA curves P-384 + map virt@acme 4. Proxies ---------- diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index 468962417..8a3527df4 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -13,6 +13,7 @@ struct acme_cfg { int linenum; /* config linenum */ char *name; /* section name */ char *directory; /* directory URL */ + char *map; /* storage for tokens + thumbprint */ struct { char *contact; /* email associated to account */ char *file; /* account key filename */ diff --git a/src/acme.c b/src/acme.c index 31fc273e0..fe84a2619 100644 --- a/src/acme.c +++ b/src/acme.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -313,6 +314,22 @@ 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], "map") == 0) { + /* save the map name for thumbprint + token storage */ + 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; + + cur_acme->map = strdup(args[1]); + if (!cur_acme->map) { + err_code |= ERR_ALERT | ERR_FATAL; + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + 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; @@ -552,6 +569,7 @@ static struct cfg_kw_list cfg_kws_acme = {ILH, { { CFG_ACME, "keytype", cfg_parse_acme_cfg_key }, { CFG_ACME, "bits", cfg_parse_acme_cfg_key }, { CFG_ACME, "curves", cfg_parse_acme_cfg_key }, + { CFG_ACME, "map", cfg_parse_acme_kws }, { 0, NULL, NULL }, }}; @@ -614,6 +632,80 @@ static void acme_httpclient_end(struct httpclient *hc) task_wakeup(task, TASK_WOKEN_MSG); } +/* + * Add a map entry with as the key, and as value in the virt@acme map. + * Return 0 upon success or 1 otherwise. + */ +static int acme_add_challenge_map(const char *map, const char *challenge, const char *thumbprint, char **errmsg) +{ + int ret = 1; + struct pat_ref *ref; + struct pat_ref_elt *elt; + + /* when no map configured, return without error */ + if (!map) + return 0; + + ref = pat_ref_lookup("virt@acme"); + if (!ref) { + memprintf(errmsg, "Unknown map identifier 'virt@acme'.\n"); + goto out; + } + + HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); + elt = pat_ref_load(ref, ref->curr_gen, challenge, thumbprint, -1, errmsg); + HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); + + if (elt == NULL) + goto out; + + ret = 0; + +out: + return ret; +} + +/* + * Remove the from the virt@acme map + */ +static void acme_del_challenge_map(const char *map, const char *challenge) +{ + struct pat_ref *ref; + + /* when no map configured, return without error */ + if (!map) + return; + + ref = pat_ref_lookup(map); + if (!ref) + goto out; + + HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); + pat_ref_delete(ref, challenge); + HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); + +out: + return; +} + +/* + * Remove all challenges from an acme_ctx from the virt@acme map + */ +static void acme_del_acme_ctx_map(const struct acme_ctx *ctx) +{ + struct acme_auth *auth; + + /* when no map configured, return without error */ + if (!ctx->cfg->map) + return; + + auth = ctx->auths; + while (auth) { + acme_del_challenge_map(ctx->cfg->map, auth->token.ptr); + auth = auth->next; + } + return; +} int acme_http_req(struct task *task, struct acme_ctx *ctx, struct ist url, enum http_meth_t meth, const struct http_hdr *hdrs, struct ist payload) { @@ -1247,6 +1339,11 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut goto error; } + if (acme_add_challenge_map(ctx->cfg->map, auth->token.ptr, ctx->cfg->account.thumbprint, errmsg) != 0) { + memprintf(errmsg, "couldn't add the token to virt@acme: %s", *errmsg); + goto error; + } + /* we only need one challenge, and iteration is only used to found the right one */ break; } @@ -1879,6 +1976,7 @@ abort: ha_free(&errmsg); end: + acme_del_acme_ctx_map(ctx); acme_ctx_destroy(ctx); task_destroy(task); task = NULL;