]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: ssl/cli: "show ssl sni" list the loaded SNI in frontends
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 6 Dec 2024 16:44:53 +0000 (17:44 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 9 Dec 2024 17:29:35 +0000 (18:29 +0100)
The "show ssl sni" command, allows one to dump the list of SNI in an
haproxy process, or a designated frontend.

It lists the SNI with the type, filename, and dates of expiration and
activation

doc/management.txt
src/ssl_ckch.c

index b9e3502fad674589893f4cc8f1ab5e2c449a0e25..250cb6cbb6623c6d2c55ed29436f5b711637503c 100644 (file)
@@ -3761,6 +3761,41 @@ show ssl providers
         - fips
         - base
 
+show ssl sni [-f <frontend>]
+  Dump every SNI configured for the designated frontend, or all frontends if no
+  frontend was specified. It allows to see what SNI are offered for a frontend,
+  and to identify if a SNI is defined multiple time by multiple certificates for
+  the same frontend.
+
+  Columns are separated by a single \t, allowing to parse it simply.
+
+  The frontend/bind column shows the frontend name followed by the bind line
+  position in the configuration (file:linenum).
+
+  The SNI column shows the SNI, it can be either a CN, a SAN or a positive
+  filter from a crt-list. Negative filters are not displayed.
+
+  The 'type' column shows the encryption algorithm type, it can be "rsa", "ecdsa" or "dsa".
+
+  The default certificates of a bind line, (which are either declared
+  explicitely by 'default-crt' or is implicitely the first certificate of a bind
+  line when no 'strict-sni' is used) shows the '*' character in the SNI column.
+
+  The 'filename' column can be either a filename from the configuration, or an
+  alias declarated in a crt-store.
+
+  The 'NotAfter' and 'NotBefore' columns are directly extracted from the X509
+  leaf certificate.
+
+  Example:
+    $ echo "@1 show ssl sni" | socat /var/run/haproxy-master.sock - | column -t -s $'\t'
+    # Frontend/Bind        SNI        Type   Filename             NotAfter                  NotBefore
+    li1/haproxy.cfg:10021  machine10  rsa    machine10.pem.rsa    Jun 13 13:37:21 2024 GMT  May 14 13:37:21 2024 GMT
+    li1/haproxy.cfg:10021  machine10  ecdsa  machine10.pem.ecdsa  Jun 13 13:37:21 2024 GMT  May 14 13:37:21 2024 GMT
+    li1/haproxy.cfg:10021  localhost  rsa    localhost.pem.rsa    Jun 13 13:37:11 2024 GMT  May 14 13:37:11 2024 GMT
+    li1/haproxy.cfg:10021  localhost  ecdsa  localhost.pem.ecdsa  Jun 13 13:37:10 2024 GMT  May 14 13:37:10 2024 GMT
+    li1/haproxy.cfg:10021  *          rsa    localhost.pem.rsa    Jun 13 13:37:11 2024 GMT  May 14 13:37:11 2024 GMT
+
 show startup-logs
   Dump all messages emitted during the startup of the current haproxy process,
   each startup-logs buffer is unique to its haproxy worker.
index 92ed13e65d95cc01139b29bc6274f2c109fa7c85..2966ba9675c29d9e934c479e1635496e34f2d277 100644 (file)
@@ -32,6 +32,7 @@
 #include <haproxy/channel.h>
 #include <haproxy/cli.h>
 #include <haproxy/errors.h>
+#include <haproxy/proxy.h>
 #include <haproxy/sc_strm.h>
 #include <haproxy/ssl_ckch.h>
 #include <haproxy/ssl_sock.h>
@@ -86,6 +87,15 @@ struct show_cert_ctx {
        int transaction;
 };
 
+/* CLI context used by "show ssl sni" */
+struct show_sni_ctx {
+       struct proxy *px;
+       struct bind_conf *bind;
+       struct ebmb_node *n;
+       int nodetype;
+       int onefrontend;
+};
+
 /* CLI context used by "dump ssl cert" */
 struct dump_cert_ctx {
        struct ckch_store *ckchs;
@@ -1535,6 +1545,177 @@ struct cert_exts cert_exts[] = {
        { NULL,      CERT_TYPE_MAX,      NULL },
 };
 
+/* release function of the  `show ssl sni' command */
+static void cli_release_show_sni(struct appctx *appctx)
+{
+       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+}
+
+/* IO handler of "show ssl sni [<frontend>]".
+ * It makes use of a show_sni_ctx context
+ *
+ * The fonction does loop over the frontend, the bind_conf and the sni_ctx.
+ */
+static int cli_io_handler_show_sni(struct appctx *appctx)
+{
+       struct show_sni_ctx *ctx = appctx->svcctx;
+       struct buffer *trash = alloc_trash_chunk();
+       struct ebmb_node *n = NULL;
+       int type = 0;
+       struct bind_conf *bind = NULL;
+       struct proxy *px = NULL;
+
+       if (trash == NULL)
+               return 1;
+
+       /* ctx->bind is NULL only once we finished dumping a frontend or when starting
+        * so let's dump the header in these cases*/
+       if (ctx->bind == NULL && (ctx->onefrontend == 1 || (ctx->onefrontend == 0 && ctx->px == proxies_list)))
+               chunk_appendf(trash, "# Frontend/Bind\tSNI\tType\tFilename\tNotAfter\tNotBefore\n");
+       if (applet_putchk(appctx, trash) == -1)
+               goto yield;
+
+       for (px = ctx->px; px; px = px->next) {
+
+               /* only check the frontends which are not internal proxies */
+               if (!(px->cap & PR_CAP_FE) || (px->cap & PR_CAP_INT))
+                       continue;
+
+               bind = ctx->bind;
+               /* if we didn't get a bind from the previous yield */
+               if (!bind)
+                       bind = LIST_ELEM(px->conf.bind.n, typeof(bind), by_fe);
+
+               list_for_each_entry_from(bind, &px->conf.bind, by_fe) {
+
+                       HA_RWLOCK_RDLOCK(SNI_LOCK, &bind->sni_lock);
+
+                       /* do this twice: once for wildcards and once for standard SNI */
+                       for (type = ctx->nodetype; type < 2; type++) {
+
+                               n = ctx->n; /* get the node from previous yield */
+
+                               if (!n) {
+                                       if (type == 0)
+                                               n = ebmb_first(&bind->sni_ctx);
+                                       else
+                                               n = ebmb_first(&bind->sni_w_ctx);
+                               }
+                               /* emty SNI tree, skip */
+                               if (!n)
+                                       continue;
+
+                               while (n) {
+                                       struct sni_ctx *sni;
+                                       const char *name;
+                                       const char *certalg;
+
+                                       chunk_appendf(trash, "%s/%s:%d\t", bind->frontend->id, bind->file, bind->line);
+
+                                       sni = ebmb_entry(n, struct sni_ctx, name);
+
+                                       name = (char *)sni->name.key;
+
+                                       chunk_appendf(trash, "%s%s%s\t", sni->neg ? "!" : "", type ? "*" : "",  name);
+
+                                       switch (sni->kinfo.sig) {
+                                               case TLSEXT_signature_ecdsa:
+                                                       certalg = "ecdsa";
+                                                       break;
+                                               case TLSEXT_signature_rsa:
+                                                       certalg = "rsa";
+                                                       break;
+                                               default: /* TLSEXT_signature_anonymous|dsa */
+                                                       certalg = "dsa";
+                                                       break;
+                                       }
+
+                                       chunk_appendf(trash, "%s\t", certalg);
+
+                                       /* we need to lock so the certificates in the ckch are not modified during the listing */
+                                       chunk_appendf(trash, "%s\t", sni->ckch_inst->ckch_store->path);
+                                       chunk_appendf(trash, "%s\t", x509_get_notafter(sni->ckch_inst->ckch_store->data->cert));
+                                       chunk_appendf(trash, "%s\n", x509_get_notbefore(sni->ckch_inst->ckch_store->data->cert));
+
+                                       if (applet_putchk(appctx, trash) == -1) {
+                                               HA_RWLOCK_RDUNLOCK(SNI_LOCK, &bind->sni_lock);
+                                               goto yield;
+                                       }
+
+                                       n = ebmb_next(n);
+                               }
+                               ctx->n = NULL;
+                       }
+                       ctx->nodetype = 0;
+                       HA_RWLOCK_RDUNLOCK(SNI_LOCK, &bind->sni_lock);
+
+               }
+               ctx->bind = NULL;
+               /* only want to display the specified frontend */
+               if (ctx->onefrontend)
+                       break;
+       }
+       ctx->px = NULL;
+
+       free_trash_chunk(trash);
+       return 1;
+yield:
+
+       ctx->px = px;
+       ctx->bind = bind;
+       ctx->n = n;
+       ctx->nodetype = type;
+
+       free_trash_chunk(trash);
+       return 0; /* should come back */
+}
+
+
+/* parsing function for 'show ssl sni [-f <frontend>]' */
+static int cli_parse_show_sni(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       struct show_sni_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+
+
+       ctx->px = proxies_list;
+
+       /* look for the right <frontend> to display */
+       if (*args[3]) {
+               struct proxy *px;
+
+               if (strcmp(args[3], "-f") != 0)
+                       return cli_err(appctx, "'show ssl sni' only supports a '-f' option!\n");
+
+               if (*args[4] == '\0')
+                       return cli_err(appctx, "'-f' requires a frontend name !\n");
+
+               for (px = proxies_list; px; px = px->next) {
+
+                       /* only check the frontends */
+                       if (!(px->cap & PR_CAP_FE))
+                               continue;
+
+                       /* skip the internal proxies */
+                       if (px->cap & PR_CAP_INT)
+                               continue;
+
+                       if (strcmp(px->id, args[3]) == 0) {
+                               ctx->px = px;
+                               ctx->onefrontend = 1;
+                       }
+               }
+               if (ctx->px == NULL)
+                       goto error;
+       }
+
+       if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+               return cli_err(appctx, "Can't list SNIs\nOperations on certificates are currently locked!\n");
+
+       return 0;
+
+error:
+       return cli_err(appctx, "Couldn't find the specified frontend!\n");
+}
 
 /* release function of the  `show ssl cert' command */
 static void cli_release_show_cert(struct appctx *appctx)
@@ -4182,6 +4363,8 @@ void ckch_deinit()
 
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
+       { { "show", "ssl", "sni", NULL },       "show ssl sni [-f <frontend>]            : display the list of SNI and their corresponding filename",              cli_parse_show_sni, cli_io_handler_show_sni, cli_release_show_sni },
+
        { { "new", "ssl", "cert", NULL },       "new ssl cert <certfile>                 : create a new certificate file to be used in a crt-list or a directory", cli_parse_new_cert, NULL, NULL },
        { { "set", "ssl", "cert", NULL },       "set ssl cert <certfile> <payload>       : replace a certificate file",                                            cli_parse_set_cert, NULL, NULL },
        { { "commit", "ssl", "cert", NULL },    "commit ssl cert <certfile>              : commit a certificate file",                                             cli_parse_commit_cert, cli_io_handler_commit_cert, cli_release_commit_cert },