diff --git a/src/acl.c b/src/acl.c index 4b959433e..090430da2 100644 --- a/src/acl.c +++ b/src/acl.c @@ -2479,6 +2479,21 @@ void ACLFreeLogEntry(void *leptr) { zfree(le); } +/* Update the relevant counter by the reason */ +void ACLUpdateInfoMetrics(int reason){ + if (reason == ACL_DENIED_AUTH) { + server.acl_info.user_auth_failures++; + } else if (reason == ACL_DENIED_CMD) { + server.acl_info.invalid_cmd_accesses++; + } else if (reason == ACL_DENIED_KEY) { + server.acl_info.invalid_key_accesses++; + } else if (reason == ACL_DENIED_CHANNEL) { + server.acl_info.invalid_channel_accesses++; + } else { + serverPanic("Unknown ACL_DENIED encoding"); + } +} + /* Adds a new entry in the ACL log, making sure to delete the old entry * if we reach the maximum length allowed for the log. This function attempts * to find similar entries in the current log in order to bump the counter of @@ -2495,6 +2510,9 @@ void ACLFreeLogEntry(void *leptr) { * If `object` is not NULL, this functions takes over it. */ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object) { + /* Update ACL info metrics */ + ACLUpdateInfoMetrics(reason); + /* Create a new entry. */ struct ACLLogEntry *le = zmalloc(sizeof(*le)); le->count = 1; diff --git a/src/server.c b/src/server.c index d5b3ea690..cb1e0447a 100644 --- a/src/server.c +++ b/src/server.c @@ -2536,6 +2536,12 @@ void initServer(void) { server.repl_good_slaves_count = 0; server.last_sig_received = 0; + /* Initiate acl info struct */ + server.acl_info.invalid_cmd_accesses = 0; + server.acl_info.invalid_key_accesses = 0; + server.acl_info.user_auth_failures = 0; + server.acl_info.invalid_channel_accesses = 0; + /* Create the timer callback, this is our way to process many background * operations incrementally, like clients timeout, eviction of unaccessed * expired keys and so forth. */ @@ -5167,6 +5173,20 @@ sds genRedisInfoStringCommandStats(sds info, dict *commands) { return info; } +/* Writes the ACL metrics to the info */ +sds genRedisInfoStringACLStats(sds info) { + info = sdscatprintf(info, + "acl_access_denied_auth:%lld\r\n" + "acl_access_denied_cmd:%lld\r\n" + "acl_access_denied_key:%lld\r\n" + "acl_access_denied_channel:%lld\r\n", + server.acl_info.user_auth_failures, + server.acl_info.invalid_cmd_accesses, + server.acl_info.invalid_key_accesses, + server.acl_info.invalid_channel_accesses); + return info; +} + sds genRedisInfoStringLatencyStats(sds info, dict *commands) { struct redisCommand *c; dictEntry *de; @@ -5778,6 +5798,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { server.stat_io_writes_processed, server.stat_reply_buffer_shrinks, server.stat_reply_buffer_expands); + info = genRedisInfoStringACLStats(info); } /* Replication */ diff --git a/src/server.h b/src/server.h index c88087164..74e4098db 100644 --- a/src/server.h +++ b/src/server.h @@ -1189,6 +1189,14 @@ typedef struct client { char *buf; } client; +/* ACL information */ +typedef struct aclInfo { + long long user_auth_failures; /* Auth failure counts on user level */ + long long invalid_cmd_accesses; /* Invalid command accesses that user doesn't have permission to */ + long long invalid_key_accesses; /* Invalid key accesses that user doesn't have permission to */ + long long invalid_channel_accesses; /* Invalid channel accesses that user doesn't have permission to */ +} aclInfo; + struct saveparam { time_t seconds; int changes; @@ -1899,6 +1907,7 @@ struct redisServer { the old "requirepass" directive for backward compatibility with Redis <= 5. */ int acl_pubsub_default; /* Default ACL pub/sub channels flag */ + aclInfo acl_info; /* ACL info */ /* Assert & bug reporting */ int watchdog_period; /* Software watchdog period in ms. 0 = off */ /* System hardware info */ @@ -2798,6 +2807,7 @@ void ACLFreeUserAndKillClients(user *u); void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object); const char* getAclErrorMessage(int acl_res); void ACLUpdateDefaultUserPassword(sds password); +sds genRedisInfoStringACLStats(sds info); /* Sorted sets data type */ diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl index 0900d8e03..9160b641a 100644 --- a/tests/unit/acl.tcl +++ b/tests/unit/acl.tcl @@ -751,6 +751,71 @@ start_server {tags {"acl external:skip"}} { catch {r ACL load} err set err } {*Redis instance is not configured to use an ACL file*} + + # If there is an AUTH failure the metric increases + test {ACL-Metrics user AUTH failure} { + set current_auth_failures [s acl_access_denied_auth] + set current_invalid_cmd_accesses [s acl_access_denied_cmd] + set current_invalid_key_accesses [s acl_access_denied_key] + set current_invalid_channel_accesses [s acl_access_denied_channel] + assert_error "*WRONGPASS*" {r AUTH notrealuser 1233456} + assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 1]} + assert_error "*WRONGPASS*" {r HELLO 3 AUTH notrealuser 1233456} + assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 2]} + assert_error "*WRONGPASS*" {r HELLO 2 AUTH notrealuser 1233456} + assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 3]} + assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} + assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} + assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} + } + + # If a user try to access an unauthorized command the metric increases + test {ACL-Metrics invalid command accesses} { + set current_auth_failures [s acl_access_denied_auth] + set current_invalid_cmd_accesses [s acl_access_denied_cmd] + set current_invalid_key_accesses [s acl_access_denied_key] + set current_invalid_channel_accesses [s acl_access_denied_channel] + r ACL setuser invalidcmduser on >passwd nocommands + r AUTH invalidcmduser passwd + assert_error "*no permissions to run the * command*" {r acl list} + r AUTH default "" + assert {[s acl_access_denied_auth] eq $current_auth_failures} + assert {[s acl_access_denied_cmd] eq [expr $current_invalid_cmd_accesses + 1]} + assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} + assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} + } + + # If a user try to access an unauthorized key the metric increases + test {ACL-Metrics invalid key accesses} { + set current_auth_failures [s acl_access_denied_auth] + set current_invalid_cmd_accesses [s acl_access_denied_cmd] + set current_invalid_key_accesses [s acl_access_denied_key] + set current_invalid_channel_accesses [s acl_access_denied_channel] + r ACL setuser invalidkeyuser on >passwd resetkeys allcommands + r AUTH invalidkeyuser passwd + assert_error "*no permissions to access one of the keys*" {r get x} + r AUTH default "" + assert {[s acl_access_denied_auth] eq $current_auth_failures} + assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} + assert {[s acl_access_denied_key] eq [expr $current_invalid_key_accesses + 1]} + assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} + } + + # If a user try to access an unauthorized channel the metric increases + test {ACL-Metrics invalid channels accesses} { + set current_auth_failures [s acl_access_denied_auth] + set current_invalid_cmd_accesses [s acl_access_denied_cmd] + set current_invalid_key_accesses [s acl_access_denied_key] + set current_invalid_channel_accesses [s acl_access_denied_channel] + r ACL setuser invalidchanneluser on >passwd resetchannels allcommands + r AUTH invalidkeyuser passwd + assert_error "*no permissions to access one of the channels*" {r subscribe x} + r AUTH default "" + assert {[s acl_access_denied_auth] eq $current_auth_failures} + assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} + assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} + assert {[s acl_access_denied_channel] eq [expr $current_invalid_channel_accesses + 1]} + } } set server_path [tmpdir "server.acl"]