diff options
Diffstat (limited to '')
-rw-r--r-- | tests/unit/moduleapi/moduleauth.tcl | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/tests/unit/moduleapi/moduleauth.tcl b/tests/unit/moduleapi/moduleauth.tcl new file mode 100644 index 0000000..82f42f5 --- /dev/null +++ b/tests/unit/moduleapi/moduleauth.tcl @@ -0,0 +1,405 @@ +set testmodule [file normalize tests/modules/auth.so] +set testmoduletwo [file normalize tests/modules/moduleauthtwo.so] +set miscmodule [file normalize tests/modules/misc.so] + +proc cmdstat {cmd} { + return [cmdrstat $cmd r] +} + +start_server {tags {"modules"}} { + r module load $testmodule + r module load $testmoduletwo + + set hello2_response [r HELLO 2] + set hello3_response [r HELLO 3] + + test {test registering module auth callbacks} { + assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] + assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] + assert_equal {OK} [r testmoduleone.rm_register_auth_cb] + } + + test {test module AUTH for non existing / disabled users} { + r config resetstat + # Validate that an error is thrown for non existing users. + assert_error {*WRONGPASS*} {r AUTH foo pwd} + assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + # Validate that an error is thrown for disabled users. + r acl setuser foo >pwd off ~* &* +@all + assert_error {*WRONGPASS*} {r AUTH foo pwd} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdstat auth] + } + + test {test non blocking module AUTH} { + r config resetstat + # Test for a fixed password user + r acl setuser foo >pwd on ~* &* +@all + assert_equal {OK} [r AUTH foo allow] + assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + assert_error {*WRONGPASS*} {r AUTH foo nomatch} + assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth] + assert_equal {OK} [r AUTH foo pwd] + # Test for No Pass user + r acl setuser foo on ~* &* +@all nopass + assert_equal {OK} [r AUTH foo allow] + assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} + assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth] + assert_equal {OK} [r AUTH foo nomatch] + + # Validate that the Module added an ACL Log entry. + set entry [lindex [r ACL LOG] 0] + assert {[dict get $entry username] eq {foo}} + assert {[dict get $entry context] eq {module}} + assert {[dict get $entry reason] eq {auth}} + assert {[dict get $entry object] eq {Module Auth}} + assert_match {*cmd=auth*} [dict get $entry client-info] + r ACL LOG RESET + } + + test {test non blocking module HELLO AUTH} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + # Validate proto 2 and 3 in case of success + assert_equal $hello2_response [r HELLO 2 AUTH foo pwd] + assert_equal $hello2_response [r HELLO 2 AUTH foo allow] + assert_equal $hello3_response [r HELLO 3 AUTH foo pwd] + assert_equal $hello3_response [r HELLO 3 AUTH foo allow] + # Validate denying AUTH for the HELLO cmd + assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny} + assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch} + assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello] + assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo deny} + assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch} + assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello] + + # Validate that the Module added an ACL Log entry. + set entry [lindex [r ACL LOG] 1] + assert {[dict get $entry username] eq {foo}} + assert {[dict get $entry context] eq {module}} + assert {[dict get $entry reason] eq {auth}} + assert {[dict get $entry object] eq {Module Auth}} + assert_match {*cmd=hello*} [dict get $entry client-info] + r ACL LOG RESET + } + + test {test non blocking module HELLO AUTH SETNAME} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + # Validate clientname is set on success + assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1] + assert {[r client getname] eq {client1}} + assert_equal $hello2_response [r HELLO 2 AUTH foo allow setname client2] + assert {[r client getname] eq {client2}} + # Validate clientname is not updated on failure + r client setname client0 + assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny setname client1} + assert {[r client getname] eq {client0}} + assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2} + assert {[r client getname] eq {client0}} + assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello] + } + + test {test blocking module AUTH} { + r config resetstat + # Test for a fixed password user + r acl setuser foo >pwd on ~* &* +@all + assert_equal {OK} [r AUTH foo block_allow] + assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + assert_error {*WRONGPASS*} {r AUTH foo nomatch} + assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth] + assert_equal {OK} [r AUTH foo pwd] + # Test for No Pass user + r acl setuser foo on ~* &* +@all nopass + assert_equal {OK} [r AUTH foo block_allow] + assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} + assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth] + assert_equal {OK} [r AUTH foo nomatch] + # Validate that every Blocking AUTH command took at least 500000 usec. + set stats [cmdstat auth] + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 500000} + + # Validate that the Module added an ACL Log entry. + set entry [lindex [r ACL LOG] 0] + assert {[dict get $entry username] eq {foo}} + assert {[dict get $entry context] eq {module}} + assert {[dict get $entry reason] eq {auth}} + assert {[dict get $entry object] eq {Module Auth}} + assert_match {*cmd=auth*} [dict get $entry client-info] + r ACL LOG RESET + } + + test {test blocking module HELLO AUTH} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + # validate proto 2 and 3 in case of success + assert_equal $hello2_response [r HELLO 2 AUTH foo pwd] + assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow] + assert_equal $hello3_response [r HELLO 3 AUTH foo pwd] + assert_equal $hello3_response [r HELLO 3 AUTH foo block_allow] + # validate denying AUTH for the HELLO cmd + assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny} + assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch} + assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello] + assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo block_deny} + assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch} + assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello] + # Validate that every HELLO AUTH command took at least 500000 usec. + set stats [cmdstat hello] + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 500000} + + # Validate that the Module added an ACL Log entry. + set entry [lindex [r ACL LOG] 1] + assert {[dict get $entry username] eq {foo}} + assert {[dict get $entry context] eq {module}} + assert {[dict get $entry reason] eq {auth}} + assert {[dict get $entry object] eq {Module Auth}} + assert_match {*cmd=hello*} [dict get $entry client-info] + r ACL LOG RESET + } + + test {test blocking module HELLO AUTH SETNAME} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + # Validate clientname is set on success + assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1] + assert {[r client getname] eq {client1}} + assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow setname client2] + assert {[r client getname] eq {client2}} + # Validate clientname is not updated on failure + r client setname client0 + assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny setname client1} + assert {[r client getname] eq {client0}} + assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello] + assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2} + assert {[r client getname] eq {client0}} + assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello] + # Validate that every HELLO AUTH SETNAME command took at least 500000 usec. + set stats [cmdstat hello] + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 500000} + } + + test {test AUTH after registering multiple module auth callbacks} { + r config resetstat + + # Register two more callbacks from the same module. + assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] + assert_equal {OK} [r testmoduleone.rm_register_auth_cb] + + # Register another module auth callback from the second module. + assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] + + r acl setuser foo >pwd on ~* &* +@all + + # Case 1 - Non Blocking Success + assert_equal {OK} [r AUTH foo allow] + + # Case 2 - Non Blocking Deny + assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + + r config resetstat + + # Case 3 - Blocking Success + assert_equal {OK} [r AUTH foo block_allow] + + # Case 4 - Blocking Deny + assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + + # Validate that every Blocking AUTH command took at least 500000 usec. + set stats [cmdstat auth] + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 500000} + + r config resetstat + + # Case 5 - Non Blocking Success via the second module. + assert_equal {OK} [r AUTH foo allow_two] + + # Case 6 - Non Blocking Deny via the second module. + assert_error {*Auth denied by Misc Module*} {r AUTH foo deny_two} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + + r config resetstat + + # Case 7 - All four auth callbacks "Skip" by not explicitly allowing or denying. + assert_error {*WRONGPASS*} {r AUTH foo nomatch} + assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + assert_equal {OK} [r AUTH foo pwd] + + # Because we had to attempt all 4 callbacks, validate that the AUTH command took at least + # 1000000 usec (each blocking callback takes 500000 usec). + set stats [cmdstat auth] + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 1000000} + } + + test {module auth during blocking module auth} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + set rd [redis_deferring_client] + set rd_two [redis_deferring_client] + + # Attempt blocking module auth. While this ongoing, attempt non blocking module auth from + # moduleone/moduletwo and start another blocking module auth from another deferring client. + $rd AUTH foo block_allow + wait_for_blocked_clients_count 1 + assert_equal {OK} [r AUTH foo allow] + assert_equal {OK} [r AUTH foo allow_two] + # Validate that the non blocking module auth cmds finished before any blocking module auth. + set info_clients [r info clients] + assert_match "*blocked_clients:1*" $info_clients + $rd_two AUTH foo block_allow + + # Validate that all of the AUTH commands succeeded. + wait_for_blocked_clients_count 0 500 10 + $rd flush + assert_equal [$rd read] "OK" + $rd_two flush + assert_equal [$rd_two read] "OK" + assert_match {*calls=4,*,rejected_calls=0,failed_calls=0} [cmdstat auth] + } + + test {module auth inside MULTI EXEC} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + + # Validate that non blocking module auth inside MULTI succeeds. + r multi + r AUTH foo allow + assert_equal {OK} [r exec] + + # Validate that blocking module auth inside MULTI throws an err. + r multi + r AUTH foo block_allow + assert_error {*ERR Blocking module command called from transaction*} {r exec} + assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + } + + test {Disabling Redis User during blocking module auth} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + set rd [redis_deferring_client] + + # Attempt blocking module auth and disable the Redis user while module auth is in progress. + $rd AUTH foo pwd + wait_for_blocked_clients_count 1 + r acl setuser foo >pwd off ~* &* +@all + + # Validate that module auth failed. + wait_for_blocked_clients_count 0 500 10 + $rd flush + assert_error {*WRONGPASS*} { $rd read } + assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + } + + test {Killing a client in the middle of blocking module auth} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + set rd [redis_deferring_client] + $rd client id + set cid [$rd read] + + # Attempt blocking module auth command on client `cid` and kill the client while module auth + # is in progress. + $rd AUTH foo pwd + wait_for_blocked_clients_count 1 + r client kill id $cid + + # Validate that the blocked client count goes to 0 and no AUTH command is tracked. + wait_for_blocked_clients_count 0 500 10 + $rd flush + assert_error {*I/O error reading reply*} { $rd read } + assert_match {} [cmdstat auth] + } + + test {test RM_AbortBlock Module API during blocking module auth} { + r config resetstat + r acl setuser foo >pwd on ~* &* +@all + + # Attempt module auth. With the "block_abort" as the password, the "testacl.so" module + # blocks the client and uses the RM_AbortBlock API. This should result in module auth + # failing and the client being unblocked with the default AUTH err message. + assert_error {*WRONGPASS*} {r AUTH foo block_abort} + assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] + } + + test {test RM_RegisterAuthCallback Module API during blocking module auth} { + r config resetstat + r acl setuser foo >defaultpwd on ~* &* +@all + set rd [redis_deferring_client] + + # Start the module auth attempt with the standard Redis auth password for the user. This + # will result in all module auth cbs attempted and then standard Redis auth will be tried. + $rd AUTH foo defaultpwd + wait_for_blocked_clients_count 1 + + # Validate that we allow modules to register module auth cbs while module auth is already + # in progress. + assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] + assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] + + # Validate that blocking module auth succeeds. + wait_for_blocked_clients_count 0 500 10 + $rd flush + assert_equal [$rd read] "OK" + set stats [cmdstat auth] + assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} $stats + + # Validate that even the new blocking module auth cb which was registered in the middle of + # blocking module auth is attempted - making it take twice the duration (2x 500000 us). + regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call + assert {$usec_per_call >= 1000000} + } + + test {Module unload during blocking module auth} { + r config resetstat + r module load $miscmodule + set rd [redis_deferring_client] + r acl setuser foo >pwd on ~* &* +@all + + # Start a blocking module auth attempt. + $rd AUTH foo block_allow + wait_for_blocked_clients_count 1 + + # moduleone and moduletwo have module auth cbs registered. Because blocking module auth is + # ongoing, they cannot be unloaded. + catch {r module unload testacl} e + assert_match {*the module has blocked clients*} $e + # The moduleauthtwo module can be unregistered because no client is blocked on it. + assert_equal "OK" [r module unload moduleauthtwo] + + # The misc module does not have module auth cbs registered, so it can be unloaded even when + # blocking module auth is ongoing. + assert_equal "OK" [r module unload misc] + + # Validate that blocking module auth succeeds. + wait_for_blocked_clients_count 0 500 10 + $rd flush + assert_equal [$rd read] "OK" + assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat auth] + + # Validate that unloading the moduleauthtwo module does not unregister module auth cbs of + # of the testacl module. Module based auth should succeed. + assert_equal {OK} [r AUTH foo allow] + + # Validate that the testacl module can be unloaded since blocking module auth is done. + r module unload testacl + + # Validate that since all module auth cbs are unregistered, module auth attempts fail. + assert_error {*WRONGPASS*} {r AUTH foo block_allow} + assert_error {*WRONGPASS*} {r AUTH foo allow_two} + assert_error {*WRONGPASS*} {r AUTH foo allow} + assert_match {*calls=5,*,rejected_calls=0,failed_calls=3} [cmdstat auth] + } +} |