diff --git a/src/module.c b/src/module.c index 64c3f1e9a2..ff928b950f 100644 --- a/src/module.c +++ b/src/module.c @@ -8611,6 +8611,24 @@ int RM_DeauthenticateAndCloseClient(RedisModuleCtx *ctx, uint64_t client_id) { return REDISMODULE_OK; } +/* Redact the client command argument specified at the given position. Redacted arguments + * are obfuscated in user facing commands such as SLOWLOG or MONITOR, as well as + * never being written to server logs. This command may be called multiple times on the + * same position. + * + * Note that the command name, position 0, can not be redacted. + * + * Returns REDISMODULE_OK if the argument was redacted and REDISMODULE_ERR if there + * was an invalid parameter passed in or the position is outside the client + * argument range. */ +int RM_RedactClientCommandArgument(RedisModuleCtx *ctx, int pos) { + if (!ctx || !ctx->client || pos <= 0 || ctx->client->argc <= pos) { + return REDISMODULE_ERR; + } + redactClientCommandArgument(ctx->client, pos); + return REDISMODULE_OK; +} + /* Return the X.509 client-side certificate used by the client to authenticate * this connection. * @@ -11808,6 +11826,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(IsSubEventSupported); REGISTER_API(GetServerVersion); REGISTER_API(GetClientCertificate); + REGISTER_API(RedactClientCommandArgument); REGISTER_API(GetCommandKeys); REGISTER_API(GetCommandKeysWithFlags); REGISTER_API(GetCurrentCommandName); diff --git a/src/networking.c b/src/networking.c index c301009003..a96a2e492c 100644 --- a/src/networking.c +++ b/src/networking.c @@ -3471,7 +3471,11 @@ static void retainOriginalCommandVector(client *c) { * original_argv array. */ void redactClientCommandArgument(client *c, int argc) { retainOriginalCommandVector(c); - decrRefCount(c->argv[argc]); + if (c->original_argv[argc] == shared.redacted) { + /* This argument has already been redacted */ + return; + } + decrRefCount(c->original_argv[argc]); c->original_argv[argc] = shared.redacted; } diff --git a/src/redismodule.h b/src/redismodule.h index 1f16708c07..a40d6eba0a 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -1143,6 +1143,7 @@ REDISMODULE_API void (*RedisModule_ACLAddLogEntry)(RedisModuleCtx *ctx, RedisMod REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RedactClientCommandArgument)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR; @@ -1466,6 +1467,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(DeauthenticateAndCloseClient); REDISMODULE_GET_API(AuthenticateClientWithACLUser); REDISMODULE_GET_API(AuthenticateClientWithUser); + REDISMODULE_GET_API(RedactClientCommandArgument); REDISMODULE_GET_API(GetClientCertificate); REDISMODULE_GET_API(GetCommandKeys); REDISMODULE_GET_API(GetCommandKeysWithFlags); diff --git a/tests/modules/auth.c b/tests/modules/auth.c index 040a447ec5..612320dbcf 100644 --- a/tests/modules/auth.c +++ b/tests/modules/auth.c @@ -54,6 +54,16 @@ int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id); } +/* This command redacts every other arguments and returns OK */ +int Auth_RedactedAPI(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + for(int i = argc - 1; i > 0; i -= 2) { + int result = RedisModule_RedactClientCommandArgument(ctx, i); + RedisModule_Assert(result == REDISMODULE_OK); + } + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argc); @@ -87,6 +97,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"auth.redact", + Auth_RedactedAPI,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + return REDISMODULE_OK; } diff --git a/tests/unit/moduleapi/auth.tcl b/tests/unit/moduleapi/auth.tcl index 6d8c3bd6a7..32ace69583 100644 --- a/tests/unit/moduleapi/auth.tcl +++ b/tests/unit/moduleapi/auth.tcl @@ -68,6 +68,22 @@ start_server {tags {"modules"}} { assert_equal [r acl whoami] "default" } + test {modules can redact arguments} { + r config set slowlog-log-slower-than 0 + r slowlog reset + r auth.redact 1 2 3 4 + r auth.redact 1 2 3 + r config set slowlog-log-slower-than 10000 + set slowlog_resp [r slowlog get] + + # There will be 3 records, slowlog reset and the + # two auth redact calls. + assert_equal 3 [llength $slowlog_resp] + assert_equal {slowlog reset} [lindex [lindex [r slowlog get] 2] 3] + assert_equal {auth.redact 1 (redacted) 3 (redacted)} [lindex [lindex [r slowlog get] 1] 3] + assert_equal {auth.redact (redacted) 2 (redacted)} [lindex [lindex [r slowlog get] 0] 3] + } + test "Unload the module - testacl" { assert_equal {OK} [r module unload testacl] }