summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chessh/application.ex16
-rw-r--r--lib/chessh/auth/password.ex4
-rw-r--r--lib/chessh/schema/node.ex24
-rw-r--r--lib/chessh/schema/player_session.ex34
-rw-r--r--lib/chessh/ssh/daemon.ex45
-rw-r--r--lib/chessh/ssh/server_key.ex3
6 files changed, 108 insertions, 18 deletions
diff --git a/lib/chessh/application.ex b/lib/chessh/application.ex
index 847dd98..4692489 100644
--- a/lib/chessh/application.ex
+++ b/lib/chessh/application.ex
@@ -1,9 +1,23 @@
defmodule Chessh.Application do
+ alias Chessh.{PlayerSession, Node}
use Application
+ def initialize_player_sessions_on_node() do
+ # If we have more than one node running the ssh daemon, we'd want to ensure
+ # this is restarting after every potential crash. Otherwise the player sessions
+ # on the node would hang.
+ node_id = System.fetch_env!("NODE_ID")
+ Node.boot(node_id)
+ PlayerSession.delete_all_on_node(node_id)
+ end
+
def start(_, _) do
children = [Chessh.Repo, Chessh.SSH.Daemon]
opts = [strategy: :one_for_one, name: Chessh.Supervisor]
- Supervisor.start_link(children, opts)
+
+ with {:ok, pid} <- Supervisor.start_link(children, opts) do
+ initialize_player_sessions_on_node()
+ {:ok, pid}
+ end
end
end
diff --git a/lib/chessh/auth/password.ex b/lib/chessh/auth/password.ex
index c3b03f5..ea2c8fc 100644
--- a/lib/chessh/auth/password.ex
+++ b/lib/chessh/auth/password.ex
@@ -2,8 +2,8 @@ defmodule Chessh.Auth.PasswordAuthenticator do
alias Chessh.{Player, Repo}
def authenticate(username, password) do
- case Repo.get_by(Player, username: String.Chars.to_string(username)) do
- x -> Player.valid_password?(x, String.Chars.to_string(password))
+ case Repo.get_by(Player, username: username) do
+ x -> Player.valid_password?(x, password)
end
end
end
diff --git a/lib/chessh/schema/node.ex b/lib/chessh/schema/node.ex
new file mode 100644
index 0000000..867a6e2
--- /dev/null
+++ b/lib/chessh/schema/node.ex
@@ -0,0 +1,24 @@
+defmodule Chessh.Node do
+ alias Chessh.Repo
+ import Ecto.Changeset
+ use Ecto.Schema
+
+ @primary_key {:id, :string, []}
+ schema "nodes" do
+ field(:last_start, :utc_datetime)
+ end
+
+ def changeset(node, attrs) do
+ node
+ |> cast(attrs, [:last_start])
+ end
+
+ def boot(node_id) do
+ case Repo.get(Chessh.Node, node_id) do
+ nil -> %Chessh.Node{id: node_id}
+ node -> node
+ end
+ |> Chessh.Node.changeset(%{last_start: DateTime.utc_now()})
+ |> Repo.insert_or_update()
+ end
+end
diff --git a/lib/chessh/schema/player_session.ex b/lib/chessh/schema/player_session.ex
new file mode 100644
index 0000000..84f15ee
--- /dev/null
+++ b/lib/chessh/schema/player_session.ex
@@ -0,0 +1,34 @@
+defmodule Chessh.PlayerSession do
+ alias Chessh.Repo
+ use Ecto.Schema
+ import Ecto.{Query, Changeset}
+
+ schema "player_sessions" do
+ field(:login, :utc_datetime)
+
+ belongs_to(:node, Chessh.Node, type: :string)
+ belongs_to(:player, Chessh.Player)
+ end
+
+ def changeset(player_session, attrs) do
+ player_session
+ |> cast(attrs, [:login])
+ end
+
+ def concurrent_sessions(player) do
+ Repo.aggregate(
+ from(p in Chessh.PlayerSession,
+ where: p.player_id == ^player.id
+ ),
+ :count
+ )
+ end
+
+ def delete_all_on_node(node_id) do
+ Repo.delete_all(
+ from(p in Chessh.PlayerSession,
+ where: p.node_id == ^node_id
+ )
+ )
+ end
+end
diff --git a/lib/chessh/ssh/daemon.ex b/lib/chessh/ssh/daemon.ex
index acb6bea..cd0d466 100644
--- a/lib/chessh/ssh/daemon.ex
+++ b/lib/chessh/ssh/daemon.ex
@@ -1,4 +1,5 @@
defmodule Chessh.SSH.Daemon do
+ alias Chessh.Auth.PasswordAuthenticator
use GenServer
def start_link(_) do
@@ -12,24 +13,39 @@ defmodule Chessh.SSH.Daemon do
{:ok, state}
end
- def pwd_authenticate(username, password, _address, attempts) do
- if Chessh.Auth.PasswordAuthenticator.authenticate(username, password) do
- true
- else
- newAttempts =
- case attempts do
- :undefined -> 0
- _ -> attempts
- end
-
- if Application.fetch_env!(:chessh, :max_password_attempts) <= newAttempts do
+ 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
+ [jail_timeout_ms, jail_threshold] =
+ Application.get_env(:chessh, RateLimits)
+ |> Keyword.take([:jail_timeout_ms, :jail_threshold])
+ |> Keyword.values()
+
+ {ip, _port} = inet
+ rateId = "failed_password_attempts:#{Enum.join(Tuple.to_list(ip), ".")}"
+
+ case Hammer.check_rate(rateId, jail_timeout_ms, jail_threshold) do
+ {:allow, _count} ->
+ pwd_authenticate(username, password) ||
+ (fn ->
+ Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_threshold, 1)
+ false
+ end).()
+
+ {:deny, _limit} ->
:disconnect
- else
- {false, newAttempts + 1}
- end
end
end
+ def pwd_authenticate(username, password, inet, _address),
+ do: pwd_authenticate(username, password, inet)
+
def handle_cast(:start, state) do
port = Application.fetch_env!(:chessh, :port)
key_dir = String.to_charlist(Application.fetch_env!(:chessh, :key_dir))
@@ -40,6 +56,7 @@ defmodule Chessh.SSH.Daemon do
system_dir: key_dir,
pwdfun: &pwd_authenticate/4,
key_cb: Chessh.SSH.ServerKey,
+ # disconnectfun:
id_string: :random,
subsystems: [],
parallel_login: true,
diff --git a/lib/chessh/ssh/server_key.ex b/lib/chessh/ssh/server_key.ex
index 1096e09..72a4fbb 100644
--- a/lib/chessh/ssh/server_key.ex
+++ b/lib/chessh/ssh/server_key.ex
@@ -1,8 +1,9 @@
defmodule Chessh.SSH.ServerKey do
+ alias Chessh.Auth.KeyAuthenticator
@behaviour :ssh_server_key_api
def is_auth_key(key, username, _daemon_options) do
- Chessh.Auth.KeyAuthenticator.authenticate(username, key)
+ KeyAuthenticator.authenticate(username, key)
end
def host_key(algorithm, daemon_options) do