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.
This commit is contained in:
Erwan Le Goas 2022-09-14 17:24:22 +02:00 committed by Willy Tarreau
parent fad9da83da
commit 54966dffda
5 changed files with 104 additions and 0 deletions

View File

@ -2191,6 +2191,22 @@ prompt
quit
Close the connection when in interactive mode.
set anon [on|off] [<key>]
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 <backend> <value>
Modify the secret key used to generate the dynamic persistent cookies.
This will break the existing sessions.
@ -2479,6 +2495,10 @@ show acl [[@<ver>] <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

View File

@ -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 */

View File

@ -46,6 +46,7 @@
#include <haproxy/protocol-t.h>
#include <haproxy/tools-t.h>
#include <haproxy/xxhash.h>
#include <haproxy/cli.h>
/****** string-specific macros and functions ******/
/* if a > max, then bound <a> to <max>. The macro returns the new <a> */
@ -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 <size-1> chars from <src> to <dst>. Last char is always

View File

@ -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 <setting> [value] : change the anonymized mode setting", cli_parse_set_anon, NULL, NULL },
{ { "set", "global-key", NULL }, "set global-key <value> : change the global anonymizing key", cli_parse_set_global_key, NULL, NULL },
{ { "set", "maxconn", "global", NULL }, "set maxconn global <value> : change the per-process maxconn setting", cli_parse_set_maxconn_global, NULL },
{ { "set", "rate-limit", NULL }, "set rate-limit <setting> <value> : 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] <delay> : 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},

View File

@ -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