Erlang ssh server #1
@ -8,7 +8,7 @@ config :chessh,
|
|||||||
|
|
||||||
config :chessh, RateLimits,
|
config :chessh, RateLimits,
|
||||||
jail_timeout_ms: 5 * 60 * 1000,
|
jail_timeout_ms: 5 * 60 * 1000,
|
||||||
jail_threshold: 15,
|
jail_attempt_threshold: 15,
|
||||||
max_concurrent_user_sessions: 5
|
max_concurrent_user_sessions: 5
|
||||||
|
|
||||||
config :hammer,
|
config :hammer,
|
||||||
|
@ -2,7 +2,7 @@ import Config
|
|||||||
|
|
||||||
config :chessh, RateLimits,
|
config :chessh, RateLimits,
|
||||||
jail_timeout_ms: 1000,
|
jail_timeout_ms: 1000,
|
||||||
jail_threshold: 2
|
jail_attempt_threshold: 3
|
||||||
|
|
||||||
config :chessh, Chessh.Repo,
|
config :chessh, Chessh.Repo,
|
||||||
database: "chessh-test",
|
database: "chessh-test",
|
||||||
|
@ -22,26 +22,26 @@ defmodule Chessh.SSH.Daemon do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def pwd_authenticate(username, password, inet) do
|
def pwd_authenticate(username, password, inet) do
|
||||||
[jail_timeout_ms, jail_threshold] =
|
[jail_timeout_ms, jail_attempt_threshold] =
|
||||||
Application.get_env(:chessh, RateLimits)
|
Application.get_env(:chessh, RateLimits)
|
||||||
|> Keyword.take([:jail_timeout_ms, :jail_threshold])
|
|> Keyword.take([:jail_timeout_ms, :jail_attempt_threshold])
|
||||||
|> Keyword.values()
|
|> Keyword.values()
|
||||||
|
|
||||||
{ip, _port} = inet
|
{ip, _port} = inet
|
||||||
rateId = "failed_password_attempts:#{Enum.join(Tuple.to_list(ip), ".")}"
|
rateId = "failed_password_attempts:#{Enum.join(Tuple.to_list(ip), ".")}"
|
||||||
|
|
||||||
case Hammer.check_rate(rateId, jail_timeout_ms, jail_threshold) do
|
if pwd_authenticate(username, password) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
case Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_attempt_threshold, 1) do
|
||||||
{:allow, _count} ->
|
{:allow, _count} ->
|
||||||
pwd_authenticate(username, password) ||
|
|
||||||
(fn ->
|
|
||||||
Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_threshold, 1)
|
|
||||||
false
|
false
|
||||||
end).()
|
|
||||||
|
|
||||||
{:deny, _limit} ->
|
{:deny, _limit} ->
|
||||||
:disconnect
|
:disconnect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def pwd_authenticate(username, password, inet, _address),
|
def pwd_authenticate(username, password, inet, _address),
|
||||||
do: pwd_authenticate(username, password, inet)
|
do: pwd_authenticate(username, password, inet)
|
||||||
|
@ -5,7 +5,8 @@ defmodule Chessh.Auth.PasswordAuthenticatorTest do
|
|||||||
@valid_user %{username: "logan", password: "password"}
|
@valid_user %{username: "logan", password: "password"}
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Chessh.Repo)
|
Ecto.Adapters.SQL.Sandbox.checkout(Repo)
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()})
|
||||||
|
|
||||||
{:ok, _user} = Repo.insert(Player.registration_changeset(%Player{}, @valid_user))
|
{:ok, _user} = Repo.insert(Player.registration_changeset(%Player{}, @valid_user))
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ defmodule Chessh.Auth.PublicKeyAuthenticatorTest do
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Chessh.Repo)
|
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, player} = Repo.insert(Player.registration_changeset(%Player{}, @valid_user))
|
||||||
|
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
defmodule Chessh.SSH.AuthTest do
|
|
||||||
use ExUnit.Case
|
|
||||||
alias Chessh.{Player, Repo, Key}
|
|
||||||
|
|
||||||
@localhost '127.0.0.1'
|
|
||||||
@key_name "The Gamer Machine"
|
|
||||||
@valid_user %{username: "logan", password: "password"}
|
|
||||||
@client_test_keys_dir Path.join(Application.compile_env!(:chessh, :key_dir), "client_keys")
|
|
||||||
@client_pub_key 'id_ed25519.pub'
|
|
||||||
|
|
||||||
setup_all do
|
|
||||||
case Ecto.Adapters.SQL.Sandbox.checkout(Repo) do
|
|
||||||
:ok -> nil
|
|
||||||
{:already, :owner} -> nil
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
test "Password attempts are rate limited" do
|
|
||||||
assert :disconnect ==
|
|
||||||
Enum.reduce(
|
|
||||||
1..Application.fetch_env!(:chessh, RateLimits, :jail_threshold),
|
|
||||||
fn _, _ ->
|
|
||||||
Chessh.SSH.Daemon.pwd_authenticate(
|
|
||||||
@valid_user.username,
|
|
||||||
'wrong_password',
|
|
||||||
@localhost
|
|
||||||
) do
|
|
||||||
end
|
|
||||||
)
|
|
||||||
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, _pid} =
|
|
||||||
: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
|
|
||||||
)
|
|
||||||
|
|
||||||
send(test_pid, :connected_via_password)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Task.Supervisor.start_child(sup, fn ->
|
|
||||||
{:ok, _pid} =
|
|
||||||
: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)
|
|
||||||
)
|
|
||||||
|
|
||||||
send(test_pid, :connected_via_public_key)
|
|
||||||
end)
|
|
||||||
|
|
||||||
assert_receive(:connected_via_password, 500)
|
|
||||||
assert_receive(:connected_via_public_key, 500)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "INTEGRATION - User cannot have more than specified concurrent sessions" do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
@ -3,17 +3,14 @@ defmodule Chessh.SSH.AuthTest do
|
|||||||
alias Chessh.{Player, Repo, Key}
|
alias Chessh.{Player, Repo, Key}
|
||||||
|
|
||||||
@localhost '127.0.0.1'
|
@localhost '127.0.0.1'
|
||||||
|
@localhost_inet {{127, 0, 0, 1}, 1}
|
||||||
@key_name "The Gamer Machine"
|
@key_name "The Gamer Machine"
|
||||||
@valid_user %{username: "logan", password: "password"}
|
@valid_user %{username: "logan", password: "password"}
|
||||||
@client_test_keys_dir Path.join(Application.compile_env!(:chessh, :key_dir), "client_keys")
|
@client_test_keys_dir Path.join(Application.compile_env!(:chessh, :key_dir), "client_keys")
|
||||||
@client_pub_key 'id_ed25519.pub'
|
@client_pub_key 'id_ed25519.pub'
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
case Ecto.Adapters.SQL.Sandbox.checkout(Repo) do
|
Ecto.Adapters.SQL.Sandbox.checkout(Repo)
|
||||||
:ok -> nil
|
|
||||||
{:already, :owner} -> nil
|
|
||||||
end
|
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()})
|
Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()})
|
||||||
|
|
||||||
{:ok, player} = Repo.insert(Player.registration_changeset(%Player{}, @valid_user))
|
{:ok, player} = Repo.insert(Player.registration_changeset(%Player{}, @valid_user))
|
||||||
@ -30,15 +27,19 @@ defmodule Chessh.SSH.AuthTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "Password attempts are rate limited" do
|
test "Password attempts are rate limited" do
|
||||||
|
jail_attempt_threshold =
|
||||||
|
Application.get_env(:chessh, RateLimits)
|
||||||
|
|> Keyword.get(:jail_attempt_threshold)
|
||||||
|
|
||||||
assert :disconnect ==
|
assert :disconnect ==
|
||||||
Enum.reduce(
|
Enum.reduce(
|
||||||
1..Application.fetch_env!(:chessh, RateLimits, :jail_threshold),
|
0..(jail_attempt_threshold + 1),
|
||||||
fn _, _ ->
|
fn _, _ ->
|
||||||
Chessh.SSH.Daemon.pwd_authenticate(
|
Chessh.SSH.Daemon.pwd_authenticate(
|
||||||
@valid_user.username,
|
@valid_user.username,
|
||||||
'wrong_password',
|
"wrong_password",
|
||||||
@localhost
|
@localhost_inet
|
||||||
) do
|
)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -75,7 +76,8 @@ defmodule Chessh.SSH.AuthTest do
|
|||||||
assert_receive(:connected_via_public_key, 500)
|
assert_receive(:connected_via_public_key, 500)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "INTEGRATION - User cannot have more than specified concurrent sessions" do
|
# TODO
|
||||||
:ok
|
# test "INTEGRATION - User cannot have more than specified concurrent sessions" do
|
||||||
end
|
# :ok
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user