From: Willy Tarreau Date: Mon, 26 Oct 2026 17:21:33 +0000 (+0100) Subject: MINOR: cli/http: add the "show http-hardening" CLI command X-Git-Tag: v3.5-dev11~32 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=060586bf68f2c9f7c4b9e2ca9d200eaa49ae7808;p=haproxy.git MINOR: cli/http: add the "show http-hardening" CLI command Without argument, "show http-hardening" lists all known profiles, including the implicit "untrusted" and "trusted" ones when they were not declared (they are then marked "(implicit)"). With a profile name, it shows each known single-bit hardening option of this profile, along with its number (bit position plus 1), direction among "req", "res", "-" (for bidir) and the status ("on" or "off"), so that it is easy to know what is enabled. Keywords covering a set of options such as "all" are not shown. --- diff --git a/doc/management.txt b/doc/management.txt index b9826cbd0..33d7b1939 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -3328,6 +3328,26 @@ show filter instances [global|request|response] section 9 of the configuration manual for details about the filters declaration modes. +show http-hardening [] + Without argument, lists the names of all known http-hardening profiles, the + implicit "untrusted" and "trusted" ones being marked "(implicit)" when they were + not declared in the configuration. When a profile name is passed, shows each + known hardening option of this profile, followed by the direction it applies + to ("req" for requests, "res" for responses, "-" for both), and by "on" or + "off" depending on whether it is enabled. Options that cover a set of several + options, such as "all", are not shown. + + Example : + $ echo "show http-hardening" | socat - /tmp/haproxy.sock + custom + trusted + untrusted + + $ echo "show http-hardening custom" | socat - /tmp/haproxy.sock + 1 reject-duplicate-host-header req on + 2 reject-status-out-of-range res off + 3 delete-underscore-in-header-name - on + show info [typed|json] [desc] [float] Dump info about haproxy status on current process. If "typed" is passed as an optional argument, field numbers, names and types are emitted as well so that diff --git a/src/http.c b/src/http.c index 143b5eec1..daec94fe3 100644 --- a/src/http.c +++ b/src/http.c @@ -13,7 +13,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -2034,6 +2037,95 @@ static void http_hardening_deinit() } } +/* CLI context used during "show http-hardening" */ +struct show_http_hardening_ctx { + struct http_hdng_prf *profile; /* profile to dump, NULL to list all */ + struct http_hdng_prf *last; /* last profile listed */ + const struct http_hardening_kw *kw; /* last kw dumped or NULL before first */ + int stage; /* 0: implicit profiles, 1: tree */ +}; + +/* parses "show http-hardening []", and if a name is given, sets the + * profile to dump. + */ +static int cli_parse_show_http_hardening(char **args, char *payload, struct appctx *appctx, void *trusted) +{ + struct show_http_hardening_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + if (*args[2]) { + ctx->profile = http_hardening_lookup(args[2]); + if (!ctx->profile) + return cli_err(appctx, "Unknown http-hardening profile.\n"); + } + return 0; +} + +/* Lists the declared profiles, preceded by the implicit "untrusted" and "trusted" + * ones if they were not declared, or dumps the state of each single-bit + * option of the selected profile. Returns 0 if the output buffer is full and + * it needs to be called again, otherwise non-zero. + */ +static int cli_io_handler_show_http_hardening(struct appctx *appctx) +{ + struct show_http_hardening_ctx *ctx = appctx->svcctx; + struct http_hdng_prf *prf; + const struct http_hardening_kw *kw = ctx->kw; + + chunk_reset(&trash); + + if (ctx->profile) { + /* dump everything at once */ + if (!kw) + kw = http_hardening_kws; + + for (; kw->name; kw++) { + /* only report single-bit options, not sets */ + if (!kw->mask || (kw->mask & (kw->mask - 1)) != 0) + continue; + chunk_appendf(&trash, "%2u %-40s %-3s %s\n", my_flsl(kw->mask), kw->name, + kw->dir == HTTP_DIR_REQ ? "req" : kw->dir == HTTP_DIR_RES ? "res" : "-", + (ctx->profile->mask & kw->mask) ? "on" : "off"); + } + if (applet_putchk(appctx, &trash) == -1) + return 0; + ctx->kw = kw; + return 1; + } + + if (ctx->stage == 0) { + if (!cebuis_item_lookup(&http_hardening_by_name, name_node, name, http_hardening_untrusted.name, struct http_hdng_prf)) + chunk_appendf(&trash, "%s (implicit)\n", http_hardening_untrusted.name); + if (!cebuis_item_lookup(&http_hardening_by_name, name_node, name, http_hardening_trusted.name, struct http_hdng_prf)) + chunk_appendf(&trash, "%s (implicit)\n", http_hardening_trusted.name); + if (b_data(&trash) && applet_putchk(appctx, &trash) == -1) + return 0; + ctx->stage = 1; + } + + /* the tree doesn't change once started so the last one is still valid */ + if (ctx->last) + prf = cebuis_item_next(&http_hardening_by_name, name_node, name, ctx->last); + else + prf = cebuis_item_first(&http_hardening_by_name, name_node, name, struct http_hdng_prf); + + while (prf) { + chunk_printf(&trash, "%s\n", prf->name); + if (applet_putchk(appctx, &trash) == -1) + return 0; + ctx->last = prf; + prf = cebuis_item_next(&http_hardening_by_name, name_node, name, prf); + } + return 1; +} + +static struct cli_kw_list http_hardening_cli_kws = {{ }, { + { { "show", "http-hardening", NULL }, "show http-hardening [name] : lists the profiles, or dumps one of them's rules", + cli_parse_show_http_hardening, cli_io_handler_show_http_hardening }, + {{},} +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &http_hardening_cli_kws); + REGISTER_CONFIG_SECTION("http-hardening", cfg_parse_http_hardening, cfg_post_parse_http_hardening); REGISTER_POST_DEINIT(http_hardening_deinit);