From 54966dffdac3fb54885c20970ccccdd78a05ed16 Mon Sep 17 00:00:00 2001 From: Erwan Le Goas Date: Wed, 14 Sep 2022 17:24:22 +0200 Subject: [PATCH] MINOR: anon: store the anonymizing key in the CLI's appctx In order to allow users to dump internal states using a specific key without changing the global one, we're introducing a key in the CLI's appctx. This key is preloaded from the global one when "set anon on" is used (and if none exists, a random one is assigned). And the key can optionally be assigned manually for the whole CLI session. A "show anon" command was also added to show the anon state, and the current key if the users has sufficient permissions. In addition, a "debug dev hash" command was added to test the feature. --- doc/management.txt | 20 +++++++++++ include/haproxy/applet-t.h | 1 + include/haproxy/tools.h | 4 +++ src/cli.c | 68 ++++++++++++++++++++++++++++++++++++++ src/debug.c | 11 ++++++ 5 files changed, 104 insertions(+) diff --git a/doc/management.txt b/doc/management.txt index 28165352c6..9e908a6168 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -2191,6 +2191,22 @@ prompt quit Close the connection when in interactive mode. +set anon [on|off] [] + This command enables or disables the "anonymized mode" for the current CLI + session, which replaces certain fields considered sensitive or confidential + in command outputs with hashes that preserve sufficient consistency between + elements to help developers identify relations between elements when trying + to spot bugs, but a low enough bit count (24) to make them non-reversible due + to the high number of possible matches. When turned on, if no key is + specified, the global key will be used (either specified in the configuration + file by "anonkey" or set via the CLI command "set global-key"). If no such + key was set, a random one will be generated. Otherwise it's possible to + specify the 32-bit key to be used for the current session, for example, to + reuse the key that was used in a previous dump to help compare outputs. + Developers will never need this key and it's recommended never to share it as + it could allow to confirm/infirm some guesses about what certain hashes could + be hiding. + set dynamic-cookie-key backend Modify the secret key used to generate the dynamic persistent cookies. This will break the existing sessions. @@ -2479,6 +2495,10 @@ show acl [[@] ] count of all the ACL entries, not just the active ones, which means that it also includes entries currently being added. +show anon + Display the current state of the anonymized mode (enabled or disabled) and + the current session's key. + show backend Dump the list of backends available in the running process diff --git a/include/haproxy/applet-t.h b/include/haproxy/applet-t.h index b89494eac8..66672de0ec 100644 --- a/include/haproxy/applet-t.h +++ b/include/haproxy/applet-t.h @@ -70,6 +70,7 @@ struct appctx { if the command is terminated or the session released */ int cli_severity_output; /* used within the cli_io_handler to format severity output of informational feedback */ int cli_level; /* the level of CLI which can be lowered dynamically */ + uint32_t cli_anon_key; /* the key to anonymise with the hash in cli */ struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */ struct task *t; /* task associated to the applet */ struct freq_ctr call_rate; /* appctx call rate */ diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h index d3bf1cf571..405d9d55b3 100644 --- a/include/haproxy/tools.h +++ b/include/haproxy/tools.h @@ -46,6 +46,7 @@ #include #include #include +#include /****** string-specific macros and functions ******/ /* if a > max, then bound to . The macro returns the new */ @@ -68,6 +69,9 @@ /* use if you want to return a hash like : PATH('hash'). Key 0 doesn't hash. */ #define HA_ANON_PATH(key, str) hash_anon(key, str, "PATH(", ")") +/* use only in a function that contains an appctx (key comes from appctx). */ +#define HA_ANON_CLI(str) hash_anon(appctx->cli_anon_key, str, "", "") + /* * copies at most chars from to . Last char is always diff --git a/src/cli.c b/src/cli.c index aaf0677ac3..d238a23a1f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1874,6 +1874,50 @@ int cli_parse_default(char **args, char *payload, struct appctx *appctx, void *p return 0; } +/* enable or disable the anonymized mode, it returns 1 when it works or displays an error message if it doesn't. */ +static int cli_parse_set_anon(char **args, char *payload, struct appctx *appctx, void *private) +{ + uint32_t tmp; + long long key; + + if (strcmp(args[2], "on") == 0) { + if (appctx->cli_anon_key != 0) + return cli_err(appctx, "Mode already enabled\n"); + else { + if (*args[3]) { + key = atoll(args[3]); + if (key < 1 || key > UINT_MAX) + return cli_err(appctx, "Value out of range (1 to 4294967295 expected).\n"); + appctx->cli_anon_key = key; + } + else { + tmp = HA_ATOMIC_LOAD(&global.anon_key); + if (tmp != 0) + appctx->cli_anon_key = tmp; + else + appctx->cli_anon_key = ha_random32(); + } + } + } + else if (strcmp(args[2], "off") == 0) { + if (appctx->cli_anon_key == 0) + return cli_err(appctx, "Mode already disabled\n"); + else if (*args[3]) { + return cli_err(appctx, "Key can't be added while disabling anonymized mode\n"); + } + else { + appctx->cli_anon_key = 0; + } + } + else { + return cli_err(appctx, + "'set anon' only supports :\n" + " - 'on' [key] to enable the anonymized mode\n" + " - 'off' to disable the anonymized mode"); + } + return 1; +} + /* This function set the global anonyzing key, restricted to level 'admin' */ static int cli_parse_set_global_key(char **args, char *payload, struct appctx *appctx, void *private) { @@ -1892,6 +1936,28 @@ static int cli_parse_set_global_key(char **args, char *payload, struct appctx *a return 1; } +/* shows the anonymized mode state to everyone, and the key except for users, it always returns 1. */ +static int cli_parse_show_anon(char **args, char *payload, struct appctx *appctx, void *private) +{ + char *msg = NULL; + char *anon_mode = NULL; + uint32_t c_key = appctx->cli_anon_key; + + if (!c_key) + anon_mode = "Anonymized mode disabled"; + else + anon_mode = "Anonymized mode enabled"; + + if ( !((appctx->cli_level & ACCESS_LVL_MASK) < ACCESS_LVL_OPER) && c_key != 0) { + cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%s\nKey : %u\n", anon_mode, c_key)); + } + else { + cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%s\n", anon_mode)); + } + + return 1; +} + /* parse a "set rate-limit" command. It always returns 1. */ static int cli_parse_set_ratelimit(char **args, char *payload, struct appctx *appctx, void *private) { @@ -3200,11 +3266,13 @@ static struct cli_kw_list cli_kws = {{ },{ { { "expert-mode", NULL }, NULL, cli_parse_expert_experimental_mode, NULL, NULL, NULL, ACCESS_MASTER }, // not listed { { "experimental-mode", NULL }, NULL, cli_parse_expert_experimental_mode, NULL, NULL, NULL, ACCESS_MASTER }, // not listed { { "mcli-debug-mode", NULL }, NULL, cli_parse_expert_experimental_mode, NULL, NULL, NULL, ACCESS_MASTER_ONLY }, // not listed + { { "set", "anon", NULL }, "set anon [value] : change the anonymized mode setting", cli_parse_set_anon, NULL, NULL }, { { "set", "global-key", NULL }, "set global-key : change the global anonymizing key", cli_parse_set_global_key, NULL, NULL }, { { "set", "maxconn", "global", NULL }, "set maxconn global : change the per-process maxconn setting", cli_parse_set_maxconn_global, NULL }, { { "set", "rate-limit", NULL }, "set rate-limit : change a rate limiting value", cli_parse_set_ratelimit, NULL }, { { "set", "severity-output", NULL }, "set severity-output [none|number|string]: set presence of severity level in feedback information", cli_parse_set_severity_output, NULL, NULL }, { { "set", "timeout", NULL }, "set timeout [cli] : change a timeout setting", cli_parse_set_timeout, NULL, NULL }, + { { "show", "anon", NULL }, "show anon : display the current state of anonymized mode", cli_parse_show_anon, NULL }, { { "show", "env", NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL }, { { "show", "cli", "sockets", NULL }, "show cli sockets : dump list of cli sockets", cli_parse_default, cli_io_handler_show_cli_sock, NULL, NULL, ACCESS_MASTER }, { { "show", "cli", "level", NULL }, "show cli level : display the level of the current CLI session", cli_parse_show_lvl, NULL, NULL, NULL, ACCESS_MASTER}, diff --git a/src/debug.c b/src/debug.c index 0d06376b9d..1c9f43b868 100644 --- a/src/debug.c +++ b/src/debug.c @@ -701,6 +701,15 @@ static int debug_parse_cli_tkill(char **args, char *payload, struct appctx *appc return 1; } +/* hashes 'word' in "debug dev hash 'word' ". */ +static int debug_parse_cli_hash(char **args, char *payload, struct appctx *appctx, void *private) +{ + char *msg = NULL; + + cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%s\n", HA_ANON_CLI(args[3]))); + return 1; +} + /* parse a "debug dev write" command. It always returns 1. */ static int debug_parse_cli_write(char **args, char *payload, struct appctx *appctx, void *private) { @@ -1627,6 +1636,8 @@ static struct cli_kw_list cli_kws = {{ },{ {{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "warn", NULL }, "debug dev warn : call WARN_ON() and possibly crash", debug_parse_cli_warn, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "write", NULL }, "debug dev write [size] : write that many bytes in return", debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT }, + {{ "debug", "dev", "hash", NULL }, "debug dev hash [msg] : return msg hashed", debug_parse_cli_hash, NULL, NULL, NULL, ACCESS_EXPERT }, + #if defined(HA_HAVE_DUMP_LIBS) {{ "show", "libs", NULL, NULL }, "show libs : show loaded object files and libraries", debug_parse_cli_show_libs, NULL, NULL }, #endif -- 2.39.5