MINOR: ssl: add the section parser for 'crt-store'

'crt-store' is a new section useful to define the struct ckch_store.

The "load" keyword in the "crt-store" section allows to define which
files you want to load for a specific certificate definition.

Ex:
    crt-store
        load crt "site1.crt" key "site1.key"
        load crt "site2.crt" key "site2.key"

    frontend in
        bind *:443 ssl crt "site1.crt" crt "site2.crt"

This is part of the certificate loading which was discussed in #785.
This commit is contained in:
William Lallemand 2024-02-09 15:12:15 +01:00
parent aaa72e06e5
commit 00eb44864b
4 changed files with 262 additions and 0 deletions

View File

@ -36,6 +36,7 @@ struct acl_cond;
#define CFG_USERLIST 3
#define CFG_PEERS 4
#define CFG_CRTLIST 5
#define CFG_CRTSTORE 6
/* various keyword modifiers */
enum kw_mod {

View File

@ -58,6 +58,15 @@ struct ckch_data {
int ocsp_update_mode;
};
/* configuration for the ckch_store */
struct ckch_conf {
char *crt;
char *key;
char *ocsp;
char *issuer;
char *sctl;
};
/*
* this is used to store 1 to SSL_SOCK_NUM_KEYTYPES cert_key_and_chain and
* metadata.
@ -71,6 +80,7 @@ struct ckch_store {
struct ckch_data *data;
struct list ckch_inst; /* list of ckch_inst which uses this ckch_node */
struct list crtlist_entry; /* list of entries which use this store */
struct ckch_conf conf;
struct ebmb_node node;
char path[VAR_ARRAY];
};
@ -157,5 +167,21 @@ struct cert_exts {
/* add a parsing callback */
};
/* argument types */
enum parse_type_t {
PARSE_TYPE_INT = 0,
PARSE_TYPE_STR, /* string which is strdup() */
PARSE_TYPE_ONOFF, /* "on" or "off" keyword */
};
struct ckch_conf_kws {
const char *name;
size_t offset;
enum parse_type_t type;
int (*func)(const char *path, char *buf, struct ckch_data *d, char **err);
};
extern struct ckch_conf_kws ckch_conf_kws[];
#endif /* USE_OPENSSL */
#endif /* _HAPROXY_SSL_CKCH_T_H */

View File

@ -43,6 +43,7 @@ struct ckch_store *ckchs_dup(const struct ckch_store *src);
struct ckch_store *ckch_store_new(const char *filename);
void ckch_store_free(struct ckch_store *store);
void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs);
int ckch_store_load_files(struct ckch_conf *f, struct ckch_store *c, char **err);
/* ckch_inst functions */
void ckch_inst_free(struct ckch_inst *inst);

View File

@ -28,6 +28,7 @@
#include <haproxy/applet.h>
#include <haproxy/base64.h>
#include <haproxy/cfgparse.h>
#include <haproxy/channel.h>
#include <haproxy/cli.h>
#include <haproxy/errors.h>
@ -911,6 +912,13 @@ void ckch_store_free(struct ckch_store *store)
ssl_sock_free_cert_key_and_chain_contents(store->data);
ha_free(&store->data);
/* free the ckch_conf content */
free(store->conf.crt);
free(store->conf.key);
free(store->conf.ocsp);
free(store->conf.issuer);
free(store->conf.sctl);
free(store);
}
@ -3988,3 +3996,229 @@ static struct cli_kw_list cli_kws = {{ },{
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
struct ckch_conf_kws ckch_conf_kws[] = {
{ "crt", offsetof(struct ckch_conf, crt), PARSE_TYPE_STR, ssl_sock_load_pem_into_ckch },
{ "key", offsetof(struct ckch_conf, key), PARSE_TYPE_STR, ssl_sock_load_key_into_ckch },
{ "ocsp", offsetof(struct ckch_conf, ocsp), PARSE_TYPE_STR, ssl_sock_load_ocsp_response_from_file },
{ "issuer", offsetof(struct ckch_conf, issuer), PARSE_TYPE_STR, ssl_sock_load_issuer_file_into_ckch },
{ "sctl", offsetof(struct ckch_conf, sctl), PARSE_TYPE_STR, ssl_sock_load_sctl_from_file },
{ NULL, 0, PARSE_TYPE_STR, NULL }
};
/* crt-store does not try to find files, but use the stored filename */
int ckch_store_load_files(struct ckch_conf *f, struct ckch_store *c, char **err)
{
int i;
int err_code = 0;
int rc = 0;
struct ckch_data *d = c->data;
/* crt */
if (!f->crt) {
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
for (i = 0; ckch_conf_kws[i].name; i++) {
if (*(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset)) {
if (!ckch_conf_kws[i].func)
continue;
rc = ckch_conf_kws[i].func(*(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset), NULL, d, err);
if (rc) {
err_code |= ERR_ALERT | ERR_FATAL;
memprintf(err, "%s '%s' cannot be read or parsed.",
err && *err ? *err : "", *(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset));
goto out;
}
}
}
out:
if (err_code & ERR_FATAL)
ssl_sock_free_cert_key_and_chain_contents(d);
ERR_clear_error();
return err_code;
}
static int crtstore_parse_load(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx,
const char *file, int linenum, char **err)
{
int i;
int err_code = 0;
int cur_arg = 0;
struct ckch_conf f = {};
struct ckch_store *c = NULL;
cur_arg++; /* skip "load" */
while (*(args[cur_arg])) {
int found = 0;
for (i = 0; ckch_conf_kws[i].name != NULL; i++) {
if (strcmp(ckch_conf_kws[i].name, args[cur_arg]) == 0) {
void *target;
found = 1;
target = (char **)((intptr_t)&f + (ptrdiff_t)ckch_conf_kws[i].offset);
if (ckch_conf_kws[i].type == PARSE_TYPE_STR) {
char **t = target;
*t = strdup(args[cur_arg + 1]);
if (!*t)
goto alloc_error;
} else if (ckch_conf_kws[i].type == PARSE_TYPE_INT) {
int *t = target;
char *stop;
*t = strtol(args[cur_arg + 1], &stop, 10);
if (*stop != '\0') {
memprintf(err, "parsing [%s:%d] : cannot parse '%s' value '%s', an integer is expected.\n",
file, linenum, args[cur_arg], args[cur_arg + 1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
} else if (ckch_conf_kws[i].type == PARSE_TYPE_ONOFF) {
int *t = target;
if (strcmp(args[cur_arg + 1], "on") == 0) {
*t = 1;
} else if (strcmp(args[cur_arg + 1], "off") == 0) {
*t = 0;
} else {
memprintf(err, "parsing [%s:%d] : cannot parse '%s' value '%s', 'on' or 'off' is expected.\n",
file, linenum, args[cur_arg], args[cur_arg + 1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
break;
}
}
if (!found) {
memprintf(err,"parsing [%s:%d] : '%s %s' in section 'crt-store': unknown keyword '%s'.",
file, linenum, args[0], args[cur_arg],args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
cur_arg += 2;
}
if (!f.crt) {
memprintf(err,"parsing [%s:%d] : '%s' in section 'crt-store': mandatory 'crt' parameter not found.",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
/* process and insert the ckch_store */
c = ckch_store_new(f.crt);
if (!c)
goto alloc_error;
err_code |= ckch_store_load_files(&f, c, err);
if (err_code & ERR_FATAL)
goto out;
c->conf = f;
if (ebst_insert(&ckchs_tree, &c->node) != &c->node) {
memprintf(err,"parsing [%s:%d] : '%s' in section 'crt-store': store '%s' was already defined.",
file, linenum, args[0], c->path);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
out:
/* free ckch_conf content */
if (err_code & ERR_FATAL)
ckch_store_free(c);
return err_code;
alloc_error:
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
/*
* Parse "crt-store" section and create corresponding ckch_stores.
*
* The function returns 0 in success case, otherwise, it returns error
* flags.
*/
static int cfg_parse_crtstore(const char *file, int linenum, char **args, int kwm)
{
struct cfg_kw_list *kwl;
const char *best;
int index;
int rc = 0;
int err_code = 0;
char *errmsg = NULL;
if (strcmp(args[0], "crt-store") == 0) { /* new crt-store section */
if (*args[1]) {
ha_alert("parsing [%s:%d] : 'crt-store' section does not support an argument.\n", file, linenum);
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
goto out;
}
list_for_each_entry(kwl, &cfg_keywords.list, list) {
for (index = 0; kwl->kw[index].kw != NULL; index++) {
if (kwl->kw[index].section != CFG_CRTSTORE)
continue;
if (strcmp(kwl->kw[index].kw, args[0]) == 0) {
if (check_kw_experimental(&kwl->kw[index], file, linenum, &errmsg)) {
ha_alert("%s\n", errmsg);
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
/* prepare error message just in case */
rc = kwl->kw[index].parse(args, CFG_CRTSTORE, NULL, NULL, file, linenum, &errmsg);
if (rc & ERR_ALERT) {
ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg);
err_code |= rc;
goto out;
}
else if (rc & ERR_WARN) {
ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg);
err_code |= rc;
goto out;
}
goto out;
}
}
}
best = cfg_find_best_match(args[0], &cfg_keywords.list, CFG_CRTSTORE, NULL);
if (best)
ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section; did you mean '%s' maybe ?\n", file, linenum, args[0], cursection, best);
else
ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
out:
if (err_code & ERR_FATAL)
err_code |= ERR_ABORT;
free(errmsg);
return err_code;
alloc_error:
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
REGISTER_CONFIG_SECTION("crt-store", cfg_parse_crtstore, NULL);
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_CRTSTORE, "load", crtstore_parse_load },
{ 0, NULL, NULL },
}};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);