diff options
author | Madelyn Olson <34459052+madolson@users.noreply.github.com> | 2022-05-29 23:42:56 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-29 23:42:56 -0700 |
commit | ed29d634b3844d2c6af099e22f3bd6bf316edab9 (patch) | |
tree | be47eb25b78dd02ce76837bece5d4413acd3b0be | |
parent | 7a550c8bbbf55bdc56a45b9c728b45408d620bc5 (diff) | |
download | redis-ed29d634b3844d2c6af099e22f3bd6bf316edab9.tar.gz |
Add readonly flag to EVAL_RO, EVALSHA_RO and FCALL_RO (#10728)
* Add readonly flag to EVAL_RO, EVALSHA_RO and FCALL_RO
* Require users to explicitly declare @scripting to get access to lua scripting.
-rw-r--r-- | src/commands.c | 6 | ||||
-rw-r--r-- | src/commands/eval_ro.json | 3 | ||||
-rw-r--r-- | src/commands/evalsha_ro.json | 3 | ||||
-rw-r--r-- | src/commands/fcall_ro.json | 3 | ||||
-rw-r--r-- | src/server.c | 11 | ||||
-rw-r--r-- | tests/unit/acl.tcl | 7 | ||||
-rw-r--r-- | tests/unit/tracking.tcl | 27 |
7 files changed, 51 insertions, 9 deletions
diff --git a/src/commands.c b/src/commands.c index 425b64351..2e694076c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -7279,10 +7279,10 @@ struct redisCommand redisCommandTable[] = { /* scripting */ {"eval","Execute a Lua script server side","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVAL_History,EVAL_tips,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVAL_Args}, {"evalsha","Execute a Lua script server side","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVALSHA_History,EVALSHA_tips,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVALSHA_Args}, -{"evalsha_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVALSHA_RO_History,EVALSHA_RO_tips,evalShaRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVALSHA_RO_Args}, -{"eval_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVAL_RO_History,EVAL_RO_tips,evalRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVAL_RO_Args}, +{"evalsha_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVALSHA_RO_History,EVALSHA_RO_tips,evalShaRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVALSHA_RO_Args}, +{"eval_ro","Execute a read-only Lua script server side","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,EVAL_RO_History,EVAL_RO_tips,evalRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},evalGetKeys,.args=EVAL_RO_Args}, {"fcall","Invoke a function","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_History,FCALL_tips,fcallCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_Args}, -{"fcall_ro","Invoke a read-only function","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_RO_History,FCALL_RO_tips,fcallroCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_RO_Args}, +{"fcall_ro","Invoke a read-only function","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FCALL_RO_History,FCALL_RO_tips,fcallroCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,{{"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},functionGetKeys,.args=FCALL_RO_Args}, {"function","A container for function commands","Depends on subcommand.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_History,FUNCTION_tips,NULL,-2,0,0,.subcommands=FUNCTION_Subcommands}, {"script","A container for Lua scripts management commands","Depends on subcommand.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_History,SCRIPT_tips,NULL,-2,0,0,.subcommands=SCRIPT_Subcommands}, /* sentinel */ diff --git a/src/commands/eval_ro.json b/src/commands/eval_ro.json index 952303d39..65fd866a0 100644 --- a/src/commands/eval_ro.json +++ b/src/commands/eval_ro.json @@ -11,7 +11,8 @@ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", - "STALE" + "STALE", + "READONLY" ], "acl_categories": [ "SCRIPTING" diff --git a/src/commands/evalsha_ro.json b/src/commands/evalsha_ro.json index d876c5d76..d76313540 100644 --- a/src/commands/evalsha_ro.json +++ b/src/commands/evalsha_ro.json @@ -11,7 +11,8 @@ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", - "STALE" + "STALE", + "READONLY" ], "acl_categories": [ "SCRIPTING" diff --git a/src/commands/fcall_ro.json b/src/commands/fcall_ro.json index 092915700..46085ebb0 100644 --- a/src/commands/fcall_ro.json +++ b/src/commands/fcall_ro.json @@ -11,7 +11,8 @@ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", - "STALE" + "STALE", + "READONLY" ], "acl_categories": [ "SCRIPTING" diff --git a/src/server.c b/src/server.c index c031716eb..8f4654ad8 100644 --- a/src/server.c +++ b/src/server.c @@ -2716,7 +2716,8 @@ void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subc void setImplicitACLCategories(struct redisCommand *c) { if (c->flags & CMD_WRITE) c->acl_categories |= ACL_CATEGORY_WRITE; - if (c->flags & CMD_READONLY) + /* Exclude scripting commands from the RO category. */ + if (c->flags & CMD_READONLY && !(c->acl_categories & ACL_CATEGORY_SCRIPTING)) c->acl_categories |= ACL_CATEGORY_READ; if (c->flags & CMD_ADMIN) c->acl_categories |= ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS; @@ -3392,8 +3393,12 @@ void call(client *c, int flags) { (CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP); /* If the client has keys tracking enabled for client side caching, - * make sure to remember the keys it fetched via this command. */ - if (c->cmd->flags & CMD_READONLY) { + * make sure to remember the keys it fetched via this command. Scripting + * works a bit differently, where if the scripts executes any read command, it + * remembers all of the declared keys from the script. */ + if ((c->cmd->flags & CMD_READONLY) && (c->cmd->proc != evalRoCommand) + && (c->cmd->proc != evalShaRoCommand) && (c->cmd->proc != fcallroCommand)) + { client *caller = (c->flags & CLIENT_SCRIPT && server.script_caller) ? server.script_caller : c; if (caller->flags & CLIENT_TRACKING && diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl index c252de031..11c871bb6 100644 --- a/tests/unit/acl.tcl +++ b/tests/unit/acl.tcl @@ -529,6 +529,13 @@ start_server {tags {"acl external:skip"}} { assert_equal [lsearch [r acl cat stream] "get"] -1 } + test "ACL requires explicit permission for scripting for EVAL_RO, EVALSHA_RO and FCALL_RO" { + r ACL SETUSER scripter on nopass +readonly + assert_equal "This user has no permissions to run the 'eval_ro' command" [r ACL DRYRUN scripter EVAL_RO "" 0] + assert_equal "This user has no permissions to run the 'evalsha_ro' command" [r ACL DRYRUN scripter EVALSHA_RO "" 0] + assert_equal "This user has no permissions to run the 'fcall_ro' command" [r ACL DRYRUN scripter FCALL_RO "" 0] + } + test {ACL #5998 regression: memory leaks adding / removing subcommands} { r AUTH default "" r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c diff --git a/tests/unit/tracking.tcl b/tests/unit/tracking.tcl index 140656acf..6270ce472 100644 --- a/tests/unit/tracking.tcl +++ b/tests/unit/tracking.tcl @@ -208,6 +208,33 @@ start_server {tags {"tracking network"}} { assert {$res eq {key1}} } + test {Tracking only occurs for scripts when a command calls a read-only command} { + r CLIENT TRACKING off + r CLIENT TRACKING on + $rd_sg MSET key2{t} 1 key2{t} 1 + + # If a script doesn't call any read command, don't track any keys + r EVAL "redis.call('set', 'key3{t}', 'bar')" 2 key1{t} key2{t} + $rd_sg MSET key2{t} 2 key1{t} 2 + + # If a script calls a read command, track all declared keys + r EVAL "redis.call('get', 'key3{t}')" 2 key1{t} key2{t} + $rd_sg MSET key2{t} 2 key1{t} 2 + assert_equal {invalidate key2{t}} [r read] + assert_equal {invalidate key1{t}} [r read] + + # RO variants work like the normal variants + r EVAL_RO "redis.call('ping')" 2 key1{t} key2{t} + $rd_sg MSET key2{t} 2 key1{t} 2 + + r EVAL_RO "redis.call('get', 'key1{t}')" 2 key1{t} key2{t} + $rd_sg MSET key2{t} 3 key1{t} 3 + assert_equal {invalidate key2{t}} [r read] + assert_equal {invalidate key1{t}} [r read] + + assert_equal "PONG" [r ping] + } + test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} { r CLIENT TRACKING on REDIRECT $redir_id $rd_sg SET key1 1 |