From 42425b02260d279cd9c12fb3e625282979b9e308 Mon Sep 17 00:00:00 2001 From: Simponic Date: Fri, 30 Dec 2022 05:46:35 -0700 Subject: Add scalable session thresholds --- lib/chessh/ssh/cli.ex | 26 +++++++++++++++ lib/chessh/ssh/daemon.ex | 75 ++++++++++++++++++++++++++++++++------------ lib/chessh/ssh/server_key.ex | 7 ++++- 3 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 lib/chessh/ssh/cli.ex (limited to 'lib/chessh/ssh') diff --git a/lib/chessh/ssh/cli.ex b/lib/chessh/ssh/cli.ex new file mode 100644 index 0000000..7ab0433 --- /dev/null +++ b/lib/chessh/ssh/cli.ex @@ -0,0 +1,26 @@ +defmodule Chessh.SSH.Cli do + @behaviour :ssh_server_channel + + def init() do + {:ok, %{}} + end + + def handle_msg(message, state) do + {:ok, state} + end + + def handle_ssh_msg(message, state) do + {:ok, state} + end + + def handle_ssh_msg( + {:ssh_cm, _connection_handler, {:exit_signal, channel_id, signal, err, lang}}, + state + ) do + {:stop, channel_id, state} + end + + def terminate(reason, state) do + :ok + end +end diff --git a/lib/chessh/ssh/daemon.ex b/lib/chessh/ssh/daemon.ex index 9f17f75..9ffc538 100644 --- a/lib/chessh/ssh/daemon.ex +++ b/lib/chessh/ssh/daemon.ex @@ -1,6 +1,10 @@ defmodule Chessh.SSH.Daemon do + alias Chessh.{Repo, PlayerSession, Player, Utils} alias Chessh.Auth.PasswordAuthenticator use GenServer + import Ecto.Query + + require Logger def start_link(_) do GenServer.start_link(__MODULE__, %{ @@ -13,33 +17,33 @@ defmodule Chessh.SSH.Daemon do {:ok, state} end - def pwd_authenticate(username, password) do - # TODO - check concurrent sessions - PasswordAuthenticator.authenticate( - String.Chars.to_string(username), - String.Chars.to_string(password) - ) - end - - def pwd_authenticate(username, password, inet) do + def pwd_authenticate(username, password, {ip, _port}) do [jail_timeout_ms, jail_attempt_threshold] = Application.get_env(:chessh, RateLimits) |> Keyword.take([:jail_timeout_ms, :jail_attempt_threshold]) |> Keyword.values() - {ip, _port} = inet rateId = "failed_password_attempts:#{Enum.join(Tuple.to_list(ip), ".")}" - if pwd_authenticate(username, password) do - true - else - case Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_attempt_threshold, 1) do - {:allow, _count} -> - false + case PasswordAuthenticator.authenticate( + String.Chars.to_string(username), + String.Chars.to_string(password) + ) do + false -> + case Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_attempt_threshold, 1) do + {:allow, _count} -> + false + + {:deny, _limit} -> + :disconnect + end - {:deny, _limit} -> - :disconnect - end + x -> + if PlayerSession.player_within_concurrent_sessions_and_satisfies(username, fn _player -> + x + end), + do: true, + else: :disconnect end end @@ -53,10 +57,13 @@ defmodule Chessh.SSH.Daemon do case :ssh.daemon( port, + # shell: fn _username, _peer -> Process.sleep(5000) end, system_dir: key_dir, pwdfun: &pwd_authenticate/4, key_cb: Chessh.SSH.ServerKey, - # disconnectfun: + ssh_cli: {Chessh.SSH.Cli, []}, + # connectfun: &on_connect/3, + disconnectfun: &on_disconnect/1, id_string: :random, subsystems: [], parallel_login: true, @@ -74,4 +81,32 @@ defmodule Chessh.SSH.Daemon do end def handle_info(_, state), do: {:noreply, state} + + # defp on_connect(username, _inet, _method) do + # Logger.debug("#{inspect(self())} connected and is authenticated as #{username}") + # + # case Repo.get_by(Player, username: String.Chars.to_string(username)) do + # nil -> + # nil + # + # player -> + # Repo.insert(%PlayerSession{ + # login: DateTime.utc_now(), + # node_id: System.fetch_env!("NODE_ID"), + # player: player, + # process: pid_to_str(self()) + # }) + # end + # end + + defp on_disconnect(_reason) do + Logger.debug("#{inspect(self())} disconnected") + + Repo.delete_all( + from(p in PlayerSession, + where: p.node_id == ^System.fetch_env!("NODE_ID"), + where: p.process == ^Utils.pid_to_str(self()) + ) + ) + end end diff --git a/lib/chessh/ssh/server_key.ex b/lib/chessh/ssh/server_key.ex index 72a4fbb..5252624 100644 --- a/lib/chessh/ssh/server_key.ex +++ b/lib/chessh/ssh/server_key.ex @@ -1,9 +1,14 @@ defmodule Chessh.SSH.ServerKey do + alias Chessh.PlayerSession alias Chessh.Auth.KeyAuthenticator + @behaviour :ssh_server_key_api def is_auth_key(key, username, _daemon_options) do - KeyAuthenticator.authenticate(username, key) + PlayerSession.player_within_concurrent_sessions_and_satisfies( + username, + &KeyAuthenticator.authenticate(&1, key) + ) end def host_key(algorithm, daemon_options) do -- cgit v1.2.3-70-g09d2 From 9add1557b30c630cf45a1018cefc9a4a202db113 Mon Sep 17 00:00:00 2001 From: Simponic Date: Fri, 30 Dec 2022 05:49:40 -0700 Subject: Ignore unused cli vars --- lib/chessh/ssh/cli.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/chessh/ssh') diff --git a/lib/chessh/ssh/cli.ex b/lib/chessh/ssh/cli.ex index 7ab0433..c7befe0 100644 --- a/lib/chessh/ssh/cli.ex +++ b/lib/chessh/ssh/cli.ex @@ -5,22 +5,22 @@ defmodule Chessh.SSH.Cli do {:ok, %{}} end - def handle_msg(message, state) do - {:ok, state} - end - - def handle_ssh_msg(message, state) do + def handle_msg(_message, state) do {:ok, state} end def handle_ssh_msg( - {:ssh_cm, _connection_handler, {:exit_signal, channel_id, signal, err, lang}}, + {:ssh_cm, _connection_handler, {:exit_signal, channel_id, _signal, _err, _lang}}, state ) do {:stop, channel_id, state} end - def terminate(reason, state) do + def handle_ssh_msg(_message, state) do + {:ok, state} + end + + def terminate(_reason, _state) do :ok end end -- cgit v1.2.3-70-g09d2 From e7d8c61487815bd90ec5834dad5e6f64dd951b53 Mon Sep 17 00:00:00 2001 From: Simponic Date: Fri, 30 Dec 2022 05:51:47 -0700 Subject: More warning squashing --- lib/chessh/auth/password.ex | 1 - lib/chessh/ssh/cli.ex | 2 +- lib/chessh/ssh/daemon.ex | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/chessh/ssh') diff --git a/lib/chessh/auth/password.ex b/lib/chessh/auth/password.ex index a01291d..0986169 100644 --- a/lib/chessh/auth/password.ex +++ b/lib/chessh/auth/password.ex @@ -8,7 +8,6 @@ defmodule Chessh.Auth.PasswordAuthenticator do def authenticate(username, password) do case Repo.get_by(Player, username: username) do player -> authenticate(player, password) - nil -> false end end end diff --git a/lib/chessh/ssh/cli.ex b/lib/chessh/ssh/cli.ex index c7befe0..71d789b 100644 --- a/lib/chessh/ssh/cli.ex +++ b/lib/chessh/ssh/cli.ex @@ -1,7 +1,7 @@ defmodule Chessh.SSH.Cli do @behaviour :ssh_server_channel - def init() do + def init(_args) do {:ok, %{}} end diff --git a/lib/chessh/ssh/daemon.ex b/lib/chessh/ssh/daemon.ex index 9ffc538..b341833 100644 --- a/lib/chessh/ssh/daemon.ex +++ b/lib/chessh/ssh/daemon.ex @@ -1,5 +1,5 @@ defmodule Chessh.SSH.Daemon do - alias Chessh.{Repo, PlayerSession, Player, Utils} + alias Chessh.{Repo, PlayerSession, Utils} alias Chessh.Auth.PasswordAuthenticator use GenServer import Ecto.Query -- cgit v1.2.3-70-g09d2