]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl: add the section parser for 'crt-store'
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 9 Feb 2024 14:12:15 +0000 (15:12 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 12 Apr 2024 13:38:54 +0000 (15:38 +0200)
'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.

include/haproxy/cfgparse.h
include/haproxy/ssl_ckch-t.h
include/haproxy/ssl_ckch.h
src/ssl_ckch.c

index adcabb397ee9762bdf94ee404f774398356a5d3d..3a769d5d8feca18578ac42a582f0f0f6899f36d3 100644 (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 {
index 0002b84d9d5b1c28471c9f1eb0b4622b92bd990d..6562232ee349bdb5f1725a15d63b61aa1e57b1af 100644 (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 */
index 94c53b3016232e2e9ea1a3976978c5bd7a46d0ca..809ad8f08fba63b91a92784d411441dbd6fc381e 100644 (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);
index b69caacb3523de840939ff3e7ce618968e48b075..7a33765692a23537effed6229b0af53ece7e2252 100644 (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);