2021-09-23 01:52:56 -04:00
|
|
|
set testmodule [file normalize tests/modules/aclcheck.so]
|
|
|
|
|
|
|
|
start_server {tags {"modules acl"}} {
|
|
|
|
r module load $testmodule
|
|
|
|
|
|
|
|
test {test module check acl for command perm} {
|
|
|
|
# by default all commands allowed
|
|
|
|
assert_equal [r aclcheck.rm_call.check.cmd set x 5] OK
|
|
|
|
# block SET command for user
|
|
|
|
r acl setuser default -set
|
|
|
|
catch {r aclcheck.rm_call.check.cmd set x 5} e
|
|
|
|
assert_match {*DENIED CMD*} $e
|
|
|
|
|
|
|
|
# verify that new log entry added
|
|
|
|
set entry [lindex [r ACL LOG] 0]
|
|
|
|
assert {[dict get $entry username] eq {default}}
|
|
|
|
assert {[dict get $entry context] eq {module}}
|
|
|
|
assert {[dict get $entry object] eq {set}}
|
2022-04-12 01:16:17 -04:00
|
|
|
assert {[dict get $entry reason] eq {command}}
|
2021-09-23 01:52:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
test {test module check acl for key perm} {
|
2022-01-26 14:03:21 -05:00
|
|
|
# give permission for SET and block all keys but x(READ+WRITE), y(WRITE), z(READ)
|
2022-01-20 16:05:27 -05:00
|
|
|
r acl setuser default +set resetkeys ~x %W~y %R~z
|
|
|
|
|
|
|
|
assert_equal [r aclcheck.set.check.key "*" x 5] OK
|
|
|
|
catch {r aclcheck.set.check.key "*" v 5} e
|
|
|
|
assert_match "*DENIED KEY*" $e
|
|
|
|
|
2022-02-22 04:00:03 -05:00
|
|
|
assert_equal [r aclcheck.set.check.key "~" x 5] OK
|
|
|
|
assert_equal [r aclcheck.set.check.key "~" y 5] OK
|
|
|
|
assert_equal [r aclcheck.set.check.key "~" z 5] OK
|
|
|
|
catch {r aclcheck.set.check.key "~" v 5} e
|
|
|
|
assert_match "*DENIED KEY*" $e
|
|
|
|
|
2022-01-20 16:05:27 -05:00
|
|
|
assert_equal [r aclcheck.set.check.key "W" y 5] OK
|
|
|
|
catch {r aclcheck.set.check.key "W" v 5} e
|
|
|
|
assert_match "*DENIED KEY*" $e
|
|
|
|
|
|
|
|
assert_equal [r aclcheck.set.check.key "R" z 5] OK
|
|
|
|
catch {r aclcheck.set.check.key "R" v 5} e
|
|
|
|
assert_match "*DENIED KEY*" $e
|
|
|
|
}
|
2021-09-23 01:52:56 -04:00
|
|
|
|
|
|
|
test {test module check acl for module user} {
|
|
|
|
# the module user has access to all keys
|
|
|
|
assert_equal [r aclcheck.rm_call.check.cmd.module.user set y 5] OK
|
|
|
|
}
|
|
|
|
|
|
|
|
test {test module check acl for channel perm} {
|
|
|
|
# block all channels but ch1
|
|
|
|
r acl setuser default resetchannels &ch1
|
|
|
|
assert_equal [r aclcheck.publish.check.channel ch1 msg] 0
|
|
|
|
catch {r aclcheck.publish.check.channel ch2 msg} e
|
|
|
|
set e
|
|
|
|
} {*DENIED CHANNEL*}
|
|
|
|
|
|
|
|
test {test module check acl in rm_call} {
|
2022-01-26 14:03:21 -05:00
|
|
|
# rm call check for key permission (x: READ + WRITE)
|
2021-09-23 01:52:56 -04:00
|
|
|
assert_equal [r aclcheck.rm_call set x 5] OK
|
2022-01-26 14:03:21 -05:00
|
|
|
assert_equal [r aclcheck.rm_call set x 6 get] 5
|
|
|
|
|
|
|
|
# rm call check for key permission (y: only WRITE)
|
|
|
|
assert_equal [r aclcheck.rm_call set y 5] OK
|
|
|
|
assert_error {*NOPERM*} {r aclcheck.rm_call set y 5 get}
|
Unify ACL failure error messaging. (#11160)
Motivation: for applications that use RM ACL verification functions, they would
want to return errors back to the user, in ways that are consistent with Redis.
While investigating how we should return ACL errors to the user, we realized that
Redis isn't consistent, and currently returns ACL error strings in 3 primary ways.
[For the actual implications of this change, see the "Impact" section at the bottom]
1. how it returns an error when calling a command normally
ACL_DENIED_CMD -> "this user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "this user has no permissions to access one of the keys used as arguments"
ACL_DENIED_CHANNEL -> "this user has no permissions to access one of the channels used as arguments"
2. how it returns an error when calling via 'acl dryrun' command
ACL_DENIED_CMD -> "This user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "This user has no permissions to access the '%s' key"
ACL_DENIED_CHANNEL -> "This user has no permissions to access the '%s' channel"
3. how it returns an error via RM_Call (and scripting is similar).
ACL_DENIED_CMD -> "can't run this command or subcommand";
ACL_DENIED_KEY -> "can't access at least one of the keys mentioned in the command arguments";
ACL_DENIED_CHANNEL -> "can't publish to the channel mentioned in the command";
In addition, if one wants to use RM_Call's "dry run" capability instead of the RM ACL
functions directly, one also sees a different problem than it returns ACL errors with a -ERR,
not a -PERM, so it can't be returned directly to the caller.
This PR modifies the code to generate a base message in a common manner with the ability
to set verbose flag for acl dry run errors, and keep it unset for normal/rm_call/script cases
```c
sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose) {
switch (acl_res) {
case ACL_DENIED_CMD:
return sdscatfmt(sdsempty(), "User %S has no permissions to run "
"the '%S' command", user->name, cmd->fullname);
case ACL_DENIED_KEY:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' key", user->name, errored_val);
} else {
return sdsnew("No permissions to access a key");
}
case ACL_DENIED_CHANNEL:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' channel", user->name, errored_val);
} else {
return sdsnew("No permissions to access a channel");
}
}
```
The caller can append/prepend the message (adding NOPERM for normal/RM_Call or indicating it's within a script).
Impact:
- Plain commands, as well as scripts and RM_Call now include the user name.
- ACL DRYRUN remains the only one that's verbose (mentions the offending channel or key name)
- Changes RM_Call ACL errors from being a `-ERR` to being `-NOPERM` (besides for textual changes)
**This somewhat a breaking change, but it only affects the RM_Call with both `C` and `E`, or `D`**
- Changes ACL errors in scripts textually from being
`The user executing the script <old non unified text>`
to
`ACL failure in script: <new unified text>`
2022-10-16 02:01:37 -04:00
|
|
|
assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set y 5 get}
|
2022-01-26 14:03:21 -05:00
|
|
|
|
|
|
|
# rm call check for key permission (z: only READ)
|
|
|
|
assert_error {*NOPERM*} {r aclcheck.rm_call set z 5}
|
Unify ACL failure error messaging. (#11160)
Motivation: for applications that use RM ACL verification functions, they would
want to return errors back to the user, in ways that are consistent with Redis.
While investigating how we should return ACL errors to the user, we realized that
Redis isn't consistent, and currently returns ACL error strings in 3 primary ways.
[For the actual implications of this change, see the "Impact" section at the bottom]
1. how it returns an error when calling a command normally
ACL_DENIED_CMD -> "this user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "this user has no permissions to access one of the keys used as arguments"
ACL_DENIED_CHANNEL -> "this user has no permissions to access one of the channels used as arguments"
2. how it returns an error when calling via 'acl dryrun' command
ACL_DENIED_CMD -> "This user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "This user has no permissions to access the '%s' key"
ACL_DENIED_CHANNEL -> "This user has no permissions to access the '%s' channel"
3. how it returns an error via RM_Call (and scripting is similar).
ACL_DENIED_CMD -> "can't run this command or subcommand";
ACL_DENIED_KEY -> "can't access at least one of the keys mentioned in the command arguments";
ACL_DENIED_CHANNEL -> "can't publish to the channel mentioned in the command";
In addition, if one wants to use RM_Call's "dry run" capability instead of the RM ACL
functions directly, one also sees a different problem than it returns ACL errors with a -ERR,
not a -PERM, so it can't be returned directly to the caller.
This PR modifies the code to generate a base message in a common manner with the ability
to set verbose flag for acl dry run errors, and keep it unset for normal/rm_call/script cases
```c
sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose) {
switch (acl_res) {
case ACL_DENIED_CMD:
return sdscatfmt(sdsempty(), "User %S has no permissions to run "
"the '%S' command", user->name, cmd->fullname);
case ACL_DENIED_KEY:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' key", user->name, errored_val);
} else {
return sdsnew("No permissions to access a key");
}
case ACL_DENIED_CHANNEL:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' channel", user->name, errored_val);
} else {
return sdsnew("No permissions to access a channel");
}
}
```
The caller can append/prepend the message (adding NOPERM for normal/RM_Call or indicating it's within a script).
Impact:
- Plain commands, as well as scripts and RM_Call now include the user name.
- ACL DRYRUN remains the only one that's verbose (mentions the offending channel or key name)
- Changes RM_Call ACL errors from being a `-ERR` to being `-NOPERM` (besides for textual changes)
**This somewhat a breaking change, but it only affects the RM_Call with both `C` and `E`, or `D`**
- Changes ACL errors in scripts textually from being
`The user executing the script <old non unified text>`
to
`ACL failure in script: <new unified text>`
2022-10-16 02:01:37 -04:00
|
|
|
catch {r aclcheck.rm_call_with_errors set z 5} e
|
|
|
|
assert_match {*NOPERM*No permissions to access a key*} $e
|
2022-01-26 14:03:21 -05:00
|
|
|
assert_error {*NOPERM*} {r aclcheck.rm_call set z 6 get}
|
Unify ACL failure error messaging. (#11160)
Motivation: for applications that use RM ACL verification functions, they would
want to return errors back to the user, in ways that are consistent with Redis.
While investigating how we should return ACL errors to the user, we realized that
Redis isn't consistent, and currently returns ACL error strings in 3 primary ways.
[For the actual implications of this change, see the "Impact" section at the bottom]
1. how it returns an error when calling a command normally
ACL_DENIED_CMD -> "this user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "this user has no permissions to access one of the keys used as arguments"
ACL_DENIED_CHANNEL -> "this user has no permissions to access one of the channels used as arguments"
2. how it returns an error when calling via 'acl dryrun' command
ACL_DENIED_CMD -> "This user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "This user has no permissions to access the '%s' key"
ACL_DENIED_CHANNEL -> "This user has no permissions to access the '%s' channel"
3. how it returns an error via RM_Call (and scripting is similar).
ACL_DENIED_CMD -> "can't run this command or subcommand";
ACL_DENIED_KEY -> "can't access at least one of the keys mentioned in the command arguments";
ACL_DENIED_CHANNEL -> "can't publish to the channel mentioned in the command";
In addition, if one wants to use RM_Call's "dry run" capability instead of the RM ACL
functions directly, one also sees a different problem than it returns ACL errors with a -ERR,
not a -PERM, so it can't be returned directly to the caller.
This PR modifies the code to generate a base message in a common manner with the ability
to set verbose flag for acl dry run errors, and keep it unset for normal/rm_call/script cases
```c
sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose) {
switch (acl_res) {
case ACL_DENIED_CMD:
return sdscatfmt(sdsempty(), "User %S has no permissions to run "
"the '%S' command", user->name, cmd->fullname);
case ACL_DENIED_KEY:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' key", user->name, errored_val);
} else {
return sdsnew("No permissions to access a key");
}
case ACL_DENIED_CHANNEL:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' channel", user->name, errored_val);
} else {
return sdsnew("No permissions to access a channel");
}
}
```
The caller can append/prepend the message (adding NOPERM for normal/RM_Call or indicating it's within a script).
Impact:
- Plain commands, as well as scripts and RM_Call now include the user name.
- ACL DRYRUN remains the only one that's verbose (mentions the offending channel or key name)
- Changes RM_Call ACL errors from being a `-ERR` to being `-NOPERM` (besides for textual changes)
**This somewhat a breaking change, but it only affects the RM_Call with both `C` and `E`, or `D`**
- Changes ACL errors in scripts textually from being
`The user executing the script <old non unified text>`
to
`ACL failure in script: <new unified text>`
2022-10-16 02:01:37 -04:00
|
|
|
assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set z 6 get}
|
2021-09-23 01:52:56 -04:00
|
|
|
|
|
|
|
# verify that new log entry added
|
|
|
|
set entry [lindex [r ACL LOG] 0]
|
|
|
|
assert {[dict get $entry username] eq {default}}
|
|
|
|
assert {[dict get $entry context] eq {module}}
|
2022-01-26 14:03:21 -05:00
|
|
|
assert {[dict get $entry object] eq {z}}
|
2022-04-12 01:16:17 -04:00
|
|
|
assert {[dict get $entry reason] eq {key}}
|
2021-09-23 01:52:56 -04:00
|
|
|
|
|
|
|
# rm call check for command permission
|
|
|
|
r acl setuser default -set
|
Unify ACL failure error messaging. (#11160)
Motivation: for applications that use RM ACL verification functions, they would
want to return errors back to the user, in ways that are consistent with Redis.
While investigating how we should return ACL errors to the user, we realized that
Redis isn't consistent, and currently returns ACL error strings in 3 primary ways.
[For the actual implications of this change, see the "Impact" section at the bottom]
1. how it returns an error when calling a command normally
ACL_DENIED_CMD -> "this user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "this user has no permissions to access one of the keys used as arguments"
ACL_DENIED_CHANNEL -> "this user has no permissions to access one of the channels used as arguments"
2. how it returns an error when calling via 'acl dryrun' command
ACL_DENIED_CMD -> "This user has no permissions to run the '%s' command"
ACL_DENIED_KEY -> "This user has no permissions to access the '%s' key"
ACL_DENIED_CHANNEL -> "This user has no permissions to access the '%s' channel"
3. how it returns an error via RM_Call (and scripting is similar).
ACL_DENIED_CMD -> "can't run this command or subcommand";
ACL_DENIED_KEY -> "can't access at least one of the keys mentioned in the command arguments";
ACL_DENIED_CHANNEL -> "can't publish to the channel mentioned in the command";
In addition, if one wants to use RM_Call's "dry run" capability instead of the RM ACL
functions directly, one also sees a different problem than it returns ACL errors with a -ERR,
not a -PERM, so it can't be returned directly to the caller.
This PR modifies the code to generate a base message in a common manner with the ability
to set verbose flag for acl dry run errors, and keep it unset for normal/rm_call/script cases
```c
sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose) {
switch (acl_res) {
case ACL_DENIED_CMD:
return sdscatfmt(sdsempty(), "User %S has no permissions to run "
"the '%S' command", user->name, cmd->fullname);
case ACL_DENIED_KEY:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' key", user->name, errored_val);
} else {
return sdsnew("No permissions to access a key");
}
case ACL_DENIED_CHANNEL:
if (verbose) {
return sdscatfmt(sdsempty(), "User %S has no permissions to access "
"the '%S' channel", user->name, errored_val);
} else {
return sdsnew("No permissions to access a channel");
}
}
```
The caller can append/prepend the message (adding NOPERM for normal/RM_Call or indicating it's within a script).
Impact:
- Plain commands, as well as scripts and RM_Call now include the user name.
- ACL DRYRUN remains the only one that's verbose (mentions the offending channel or key name)
- Changes RM_Call ACL errors from being a `-ERR` to being `-NOPERM` (besides for textual changes)
**This somewhat a breaking change, but it only affects the RM_Call with both `C` and `E`, or `D`**
- Changes ACL errors in scripts textually from being
`The user executing the script <old non unified text>`
to
`ACL failure in script: <new unified text>`
2022-10-16 02:01:37 -04:00
|
|
|
assert_error {*NOPERM*} {r aclcheck.rm_call set x 5}
|
|
|
|
assert_error {*NOPERM*has no permissions to run the 'set' command*} {r aclcheck.rm_call_with_errors set x 5}
|
2021-09-23 01:52:56 -04:00
|
|
|
|
|
|
|
# verify that new log entry added
|
|
|
|
set entry [lindex [r ACL LOG] 0]
|
|
|
|
assert {[dict get $entry username] eq {default}}
|
|
|
|
assert {[dict get $entry context] eq {module}}
|
|
|
|
assert {[dict get $entry object] eq {set}}
|
2022-04-12 01:16:17 -04:00
|
|
|
assert {[dict get $entry reason] eq {command}}
|
2021-09-23 01:52:56 -04:00
|
|
|
}
|
2022-01-23 03:05:06 -05:00
|
|
|
|
2023-03-21 13:07:11 -04:00
|
|
|
test {test blocking of Commands outside of OnLoad} {
|
|
|
|
assert_equal [r block.commands.outside.onload] OK
|
|
|
|
}
|
|
|
|
|
|
|
|
test {test users to have access to module commands having acl categories} {
|
|
|
|
r acl SETUSER j1 on >password -@all +@WRITE
|
|
|
|
r acl SETUSER j2 on >password -@all +@READ
|
|
|
|
assert_equal [r acl DRYRUN j1 aclcheck.module.command.aclcategories.write] OK
|
|
|
|
assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.write.function.read.category] OK
|
|
|
|
assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.read.only.category] OK
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:01:24 -04:00
|
|
|
test {Unload the module - aclcheck} {
|
|
|
|
assert_equal {OK} [r module unload aclcheck]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
start_server {tags {"modules acl"}} {
|
2023-03-21 13:07:11 -04:00
|
|
|
test {test existing users to have access to module commands loaded on runtime} {
|
|
|
|
r acl SETUSER j3 on >password -@all +@WRITE
|
|
|
|
assert_equal [r module load $testmodule] OK
|
|
|
|
assert_equal [r acl DRYRUN j3 aclcheck.module.command.aclcategories.write] OK
|
2023-08-30 16:01:24 -04:00
|
|
|
assert_equal {OK} [r module unload aclcheck]
|
2023-03-21 13:07:11 -04:00
|
|
|
}
|
2023-08-30 16:01:24 -04:00
|
|
|
}
|
2023-03-21 13:07:11 -04:00
|
|
|
|
2023-08-30 16:01:24 -04:00
|
|
|
start_server {tags {"modules acl"}} {
|
2023-03-21 13:07:11 -04:00
|
|
|
test {test existing users without permissions, do not have access to module commands loaded on runtime.} {
|
|
|
|
r acl SETUSER j4 on >password -@all +@READ
|
|
|
|
r acl SETUSER j5 on >password -@all +@WRITE
|
|
|
|
assert_equal [r module load $testmodule] OK
|
|
|
|
catch {r acl DRYRUN j4 aclcheck.module.command.aclcategories.write} e
|
|
|
|
assert_equal {User j4 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
|
|
|
|
catch {r acl DRYRUN j5 aclcheck.module.command.aclcategories.write.function.read.category} e
|
|
|
|
assert_equal {User j5 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
|
|
|
|
}
|
|
|
|
|
|
|
|
test {test users without permissions, do not have access to module commands.} {
|
|
|
|
r acl SETUSER j6 on >password -@all +@READ
|
|
|
|
catch {r acl DRYRUN j6 aclcheck.module.command.aclcategories.write} e
|
|
|
|
assert_equal {User j6 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
|
|
|
|
r acl SETUSER j7 on >password -@all +@WRITE
|
|
|
|
catch {r acl DRYRUN j7 aclcheck.module.command.aclcategories.write.function.read.category} e
|
|
|
|
assert_equal {User j7 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:01:24 -04:00
|
|
|
test {test if foocategory acl categories is added} {
|
|
|
|
r acl SETUSER j8 on >password -@all +@foocategory
|
|
|
|
assert_equal [r acl DRYRUN j8 aclcheck.module.command.test.add.new.aclcategories] OK
|
|
|
|
}
|
|
|
|
|
|
|
|
test {test permission compaction and simplification for categories added by a module} {
|
|
|
|
r acl SETUSER j9 on >password -@all +@foocategory -@foocategory
|
|
|
|
catch {r ACL GETUSER j9} res
|
|
|
|
assert_equal {-@all -@foocategory} [lindex $res 5]
|
2022-01-23 03:05:06 -05:00
|
|
|
assert_equal {OK} [r module unload aclcheck]
|
|
|
|
}
|
2021-09-23 01:52:56 -04:00
|
|
|
}
|
2023-08-30 16:01:24 -04:00
|
|
|
|
|
|
|
start_server {tags {"modules acl"}} {
|
|
|
|
test {test module load fails if exceeds the maximum number of adding acl categories} {
|
|
|
|
assert_error {ERR Error loading the extension. Please check the server logs.} {r module load $testmodule 1}
|
|
|
|
}
|
|
|
|
}
|