2022-12-28 01:50:22 -05:00
defmodule Chessh.SSH.AuthTest do
2022-12-30 07:46:35 -05:00
use ExUnit.Case , async : false
alias ( Chessh . { Player , Repo , Key , PlayerSession } )
2022-12-28 01:50:22 -05:00
@localhost ' 127.0.0.1 '
2022-12-29 19:49:42 -05:00
@localhost_inet { { 127 , 0 , 0 , 1 } , 1 }
2022-12-28 01:50:22 -05:00
@key_name " The Gamer Machine "
2023-02-01 16:57:14 -05:00
@valid_user %{ username : " lizzy # 0003 " , password : " password " , discord_id : " 3 " }
2022-12-28 01:50:22 -05:00
@client_test_keys_dir Path . join ( Application . compile_env! ( :chessh , :key_dir ) , " client_keys " )
@client_pub_key ' id_ed25519.pub '
setup_all do
2022-12-29 19:49:42 -05:00
Ecto.Adapters.SQL.Sandbox . checkout ( Repo )
2022-12-28 01:50:22 -05:00
Ecto.Adapters.SQL.Sandbox . mode ( Repo , { :shared , self ( ) } )
{ :ok , player } = Repo . insert ( Player . registration_changeset ( % Player { } , @valid_user ) )
{ :ok , key_text } = File . read ( Path . join ( @client_test_keys_dir , @client_pub_key ) )
{ :ok , _key } =
Repo . insert (
Key . changeset ( % Key { } , %{ key : key_text , name : @key_name } )
|> Ecto.Changeset . put_assoc ( :player , player )
)
:ok
end
2022-12-30 07:46:35 -05:00
def cleanup ( ) do
Process . sleep ( 1_000 )
PlayerSession . delete_all_on_node ( System . fetch_env! ( " NODE_ID " ) )
# Wait for (what I believe to be the) DB Connection queue to clear?
Process . sleep ( 1_000 )
end
2022-12-31 04:29:38 -05:00
def send_ssh_connection_to_pid ( parent , auth_method ) do
send (
parent ,
{ :attempted ,
2023-01-29 01:04:49 -05:00
:ssh . connect ( @localhost , Application . fetch_env! ( :chessh , :ssh_port ) ,
2022-12-31 04:29:38 -05:00
user : String . to_charlist ( @valid_user . username ) ,
password : String . to_charlist ( @valid_user . password ) ,
auth_methods : auth_method ,
silently_accept_hosts : true ,
2022-12-31 18:32:56 -05:00
user_dir : String . to_charlist ( @client_test_keys_dir ) ,
disconnectfun : fn _reason -> send ( parent , { :disconnected , self ( ) } ) end
2022-12-31 04:29:38 -05:00
) }
)
end
2022-12-29 19:21:20 -05:00
test " Password attempts are rate limited " do
2022-12-29 19:49:42 -05:00
jail_attempt_threshold =
Application . get_env ( :chessh , RateLimits )
|> Keyword . get ( :jail_attempt_threshold )
2022-12-28 01:50:22 -05:00
assert :disconnect ==
Enum . reduce (
2022-12-29 19:49:42 -05:00
0 . . ( jail_attempt_threshold + 1 ) ,
2022-12-29 19:21:20 -05:00
fn _ , _ ->
Chessh.SSH.Daemon . pwd_authenticate (
2022-12-29 19:49:42 -05:00
@valid_user . username ,
" wrong_password " ,
@localhost_inet
)
2022-12-28 01:50:22 -05:00
end
)
2022-12-31 18:36:13 -05:00
cleanup ( )
2022-12-28 01:50:22 -05:00
end
2022-12-31 18:32:56 -05:00
test " Player authentications are increased after a successful authentication " do
player_before = Repo . get_by ( Player , username : @valid_user . username )
Chessh.SSH.Daemon . pwd_authenticate (
@valid_user . username ,
@valid_user . password ,
@localhost_inet
)
player_after = Repo . get_by ( Player , username : @valid_user . username )
assert ( player_after . authentications - player_before . authentications == 1 )
2022-12-31 18:36:13 -05:00
cleanup ( )
2022-12-31 18:32:56 -05:00
end
2022-12-29 19:21:20 -05:00
test " INTEGRATION - Can ssh into daemon with password or public key " do
2022-12-28 01:50:22 -05:00
{ :ok , sup } = Task.Supervisor . start_link ( )
test_pid = self ( )
Task.Supervisor . start_child ( sup , fn ->
2022-12-30 07:46:35 -05:00
{ :ok , conn } =
2023-01-29 01:04:49 -05:00
:ssh . connect ( @localhost , Application . fetch_env! ( :chessh , :ssh_port ) ,
2022-12-28 01:50:22 -05:00
user : String . to_charlist ( @valid_user . username ) ,
password : String . to_charlist ( @valid_user . password ) ,
auth_methods : ' password ' ,
silently_accept_hosts : true
)
2022-12-30 07:46:35 -05:00
:ssh . close ( conn )
2022-12-28 01:50:22 -05:00
send ( test_pid , :connected_via_password )
end )
2022-12-31 18:32:56 -05:00
assert_receive ( :connected_via_password , 2_000 )
2022-12-28 01:50:22 -05:00
Task.Supervisor . start_child ( sup , fn ->
2022-12-30 07:46:35 -05:00
{ :ok , conn } =
2023-01-29 01:04:49 -05:00
:ssh . connect ( @localhost , Application . fetch_env! ( :chessh , :ssh_port ) ,
2022-12-28 01:50:22 -05:00
user : String . to_charlist ( @valid_user . username ) ,
auth_methods : ' publickey ' ,
silently_accept_hosts : true ,
user_dir : String . to_charlist ( @client_test_keys_dir )
)
2022-12-30 07:46:35 -05:00
:ssh . close ( conn )
2022-12-28 01:50:22 -05:00
send ( test_pid , :connected_via_public_key )
end )
2022-12-30 07:46:35 -05:00
assert_receive ( :connected_via_public_key , 2_000 )
cleanup ( )
2022-12-28 01:50:22 -05:00
end
2022-12-31 18:32:56 -05:00
test " INTEGRATION - SSH Sessions are closed once player has more than specified concurrent sessions which are tracked by successful authentications and disconnections " do
2022-12-30 07:46:35 -05:00
max_concurrent_user_sessions =
Application . get_env ( :chessh , RateLimits )
|> Keyword . get ( :max_concurrent_user_sessions )
player = Repo . get_by ( Player , username : @valid_user . username )
{ :ok , sup } = Task.Supervisor . start_link ( )
test_pid = self ( )
Enum . reduce ( 0 . . ( max_concurrent_user_sessions + 1 ) , fn i , _ ->
2022-12-31 04:29:38 -05:00
Task.Supervisor . start_child (
sup ,
fn ->
send_ssh_connection_to_pid (
test_pid ,
if ( rem ( i , 2 ) == 0 , do : ' publickey ' , else : ' password ' )
)
2022-12-30 07:46:35 -05:00
end
2022-12-31 04:29:38 -05:00
)
2022-12-30 07:46:35 -05:00
end )
2022-12-31 04:29:38 -05:00
conns =
Enum . map ( 1 . . max_concurrent_user_sessions , fn _ ->
assert_receive ( { :attempted , { :ok , conn } } , 2_000 )
conn
end )
2022-12-30 07:46:35 -05:00
2022-12-31 18:32:56 -05:00
assert_receive ( { :disconnected , _conn } , 2_000 )
2022-12-30 07:46:35 -05:00
# Give it time to send back the disconnection payload after session was opened
# but over threshold
:timer . sleep ( 100 )
assert PlayerSession . concurrent_sessions ( player ) == max_concurrent_user_sessions
2022-12-31 04:29:38 -05:00
Enum . map ( conns , fn conn -> :ssh . close ( conn ) end )
:timer . sleep ( 100 )
assert PlayerSession . concurrent_sessions ( player ) == 0
2022-12-30 07:46:35 -05:00
cleanup ( )
end
2022-12-28 01:50:22 -05:00
end