- increase timing threshold in session activity test (flaky at 50ms) - show org name instead of agent ID in admin agents list - fix agent live tests wiping auth session with init_test_session - add resilient error handling in FourOhFourTracker for missing redis
74 lines
1.9 KiB
Elixir
74 lines
1.9 KiB
Elixir
defmodule Towerops.Security.FourOhFourTracker do
|
|
@moduledoc """
|
|
Tracks unique 404 paths per IP address using Redis.
|
|
|
|
Uses Redis SET data structure to track unique paths with 60-second TTL.
|
|
When an IP hits 5+ unique 404s, a ban is created/escalated.
|
|
"""
|
|
|
|
alias Towerops.Security.BruteForce
|
|
|
|
require Logger
|
|
|
|
@threshold 5
|
|
@window_seconds 60
|
|
|
|
@doc """
|
|
Records a 404 path for an IP address.
|
|
|
|
Stores the path in a Redis SET with 60-second TTL.
|
|
Triggers ban creation/escalation if threshold (5) is reached.
|
|
"""
|
|
def record_404(ip_address, path) do
|
|
key = redis_key(ip_address)
|
|
conn = Towerops.Redix
|
|
|
|
try do
|
|
# Add path to set
|
|
Redix.command(conn, ["SADD", key, path])
|
|
# Set TTL if this is the first path
|
|
Redix.command(conn, ["EXPIRE", key, @window_seconds])
|
|
|
|
# Get unique count
|
|
case Redix.command(conn, ["SCARD", key]) do
|
|
{:ok, count} when count >= @threshold ->
|
|
Logger.warning("IP #{ip_address} hit threshold with #{count} unique 404s, triggering ban")
|
|
BruteForce.create_or_escalate_ban(ip_address)
|
|
:threshold_reached
|
|
|
|
{:ok, count} ->
|
|
Logger.debug("IP #{ip_address} has #{count}/#{@threshold} unique 404s")
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Failed to get 404 count from Redis: #{inspect(reason)}")
|
|
:error
|
|
end
|
|
catch
|
|
:exit, _ ->
|
|
Logger.error("Redis unavailable for 404 tracking")
|
|
:error
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the count of unique 404 paths for an IP address.
|
|
"""
|
|
def get_count(ip_address) do
|
|
key = redis_key(ip_address)
|
|
conn = Towerops.Redix
|
|
|
|
try do
|
|
case Redix.command(conn, ["SCARD", key]) do
|
|
{:ok, count} -> count
|
|
{:error, _} -> 0
|
|
end
|
|
catch
|
|
:exit, _ -> 0
|
|
end
|
|
end
|
|
|
|
defp redis_key(ip_address) do
|
|
"404_scan:#{ip_address}"
|
|
end
|
|
end
|