166 lines
4.9 KiB
Elixir
166 lines
4.9 KiB
Elixir
defmodule Chessh.SSH.AuthTest do
|
|
use ExUnit.Case, async: false
|
|
alias(Chessh.{Player, Repo, Key, PlayerSession})
|
|
|
|
@localhost '127.0.0.1'
|
|
@localhost_inet {{127, 0, 0, 1}, 1}
|
|
@key_name "The Gamer Machine"
|
|
@valid_user %{username: "logan", password: "password", github_id: 3}
|
|
@client_test_keys_dir Path.join(Application.compile_env!(:chessh, :key_dir), "client_keys")
|
|
@client_pub_key 'id_ed25519.pub'
|
|
|
|
setup_all do
|
|
Ecto.Adapters.SQL.Sandbox.checkout(Repo)
|
|
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
|
|
|
|
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
|
|
|
|
def send_ssh_connection_to_pid(parent, auth_method) do
|
|
send(
|
|
parent,
|
|
{:attempted,
|
|
:ssh.connect(@localhost, Application.fetch_env!(:chessh, :port),
|
|
user: String.to_charlist(@valid_user.username),
|
|
password: String.to_charlist(@valid_user.password),
|
|
auth_methods: auth_method,
|
|
silently_accept_hosts: true,
|
|
user_dir: String.to_charlist(@client_test_keys_dir),
|
|
disconnectfun: fn _reason -> send(parent, {:disconnected, self()}) end
|
|
)}
|
|
)
|
|
end
|
|
|
|
test "Password attempts are rate limited" do
|
|
jail_attempt_threshold =
|
|
Application.get_env(:chessh, RateLimits)
|
|
|> Keyword.get(:jail_attempt_threshold)
|
|
|
|
assert :disconnect ==
|
|
Enum.reduce(
|
|
0..(jail_attempt_threshold + 1),
|
|
fn _, _ ->
|
|
Chessh.SSH.Daemon.pwd_authenticate(
|
|
@valid_user.username,
|
|
"wrong_password",
|
|
@localhost_inet
|
|
)
|
|
end
|
|
)
|
|
|
|
cleanup()
|
|
end
|
|
|
|
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)
|
|
cleanup()
|
|
end
|
|
|
|
test "INTEGRATION - Can ssh into daemon with password or public key" do
|
|
{:ok, sup} = Task.Supervisor.start_link()
|
|
test_pid = self()
|
|
|
|
Task.Supervisor.start_child(sup, fn ->
|
|
{:ok, conn} =
|
|
:ssh.connect(@localhost, Application.fetch_env!(:chessh, :port),
|
|
user: String.to_charlist(@valid_user.username),
|
|
password: String.to_charlist(@valid_user.password),
|
|
auth_methods: 'password',
|
|
silently_accept_hosts: true
|
|
)
|
|
|
|
:ssh.close(conn)
|
|
send(test_pid, :connected_via_password)
|
|
end)
|
|
|
|
assert_receive(:connected_via_password, 2_000)
|
|
|
|
Task.Supervisor.start_child(sup, fn ->
|
|
{:ok, conn} =
|
|
:ssh.connect(@localhost, Application.fetch_env!(:chessh, :port),
|
|
user: String.to_charlist(@valid_user.username),
|
|
auth_methods: 'publickey',
|
|
silently_accept_hosts: true,
|
|
user_dir: String.to_charlist(@client_test_keys_dir)
|
|
)
|
|
|
|
:ssh.close(conn)
|
|
send(test_pid, :connected_via_public_key)
|
|
end)
|
|
|
|
assert_receive(:connected_via_public_key, 2_000)
|
|
|
|
cleanup()
|
|
end
|
|
|
|
test "INTEGRATION - SSH Sessions are closed once player has more than specified concurrent sessions which are tracked by successful authentications and disconnections" do
|
|
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, _ ->
|
|
Task.Supervisor.start_child(
|
|
sup,
|
|
fn ->
|
|
send_ssh_connection_to_pid(
|
|
test_pid,
|
|
if(rem(i, 2) == 0, do: 'publickey', else: 'password')
|
|
)
|
|
end
|
|
)
|
|
end)
|
|
|
|
conns =
|
|
Enum.map(1..max_concurrent_user_sessions, fn _ ->
|
|
assert_receive({:attempted, {:ok, conn}}, 2_000)
|
|
conn
|
|
end)
|
|
|
|
assert_receive({:disconnected, _conn}, 2_000)
|
|
|
|
# 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
|
|
|
|
Enum.map(conns, fn conn -> :ssh.close(conn) end)
|
|
:timer.sleep(100)
|
|
assert PlayerSession.concurrent_sessions(player) == 0
|
|
|
|
cleanup()
|
|
end
|
|
end
|