Skip to content

Commit

Permalink
Backoffice : ajout doc/config pour plug rate limiter (#3652)
Browse files Browse the repository at this point in the history
* Backoffice : ajout doc/config pour plug rate limiter

* Améliorations suite à PR

---------

Co-authored-by: Thibaut Barrère <[email protected]>
  • Loading branch information
AntoineAugusti and thbar authored Dec 12, 2023
1 parent 7a4bdc8 commit abc05ee
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
defmodule TransportWeb.Backoffice.RateLimiterLive do
use Phoenix.LiveView
import TransportWeb.Backoffice.JobsLive, only: [ensure_admin_auth_or_redirect: 3]
import TransportWeb.Router.Helpers, only: [static_path: 2]
import Helpers, only: [format_number: 1]

@impl true
def mount(_params, %{"current_user" => current_user} = _session, socket) do
{:ok,
ensure_admin_auth_or_redirect(socket, current_user, fn socket ->
if connected?(socket), do: schedule_next_update_data()

socket
|> assign(%{
phoenix_ddos_max_2min_requests: env_value_to_int("PHOENIX_DDOS_MAX_2MIN_REQUESTS"),
phoenix_ddos_max_1hour_requests: env_value_to_int("PHOENIX_DDOS_MAX_1HOUR_REQUESTS"),
phoenix_ddos_safelist_ips: env_value_to_list("PHOENIX_DDOS_SAFELIST_IPS"),
phoenix_ddos_blocklist_ips: env_value_to_list("PHOENIX_DDOS_BLOCKLIST_IPS"),
log_user_agent: env_value("LOG_USER_AGENT"),
block_user_agent_keywords: env_value_to_list("BLOCK_USER_AGENT_KEYWORDS"),
allow_user_agents: env_value_to_list("ALLOW_USER_AGENTS")
})
|> update_data()
end)}
end

@impl true
def handle_info(:update_data, socket) do
schedule_next_update_data()
{:noreply, update_data(socket)}
end

defp schedule_next_update_data do
Process.send_after(self(), :update_data, 1000)
end

defp update_data(socket) do
assign(socket,
last_updated_at: (Time.utc_now() |> Time.truncate(:second) |> to_string()) <> " UTC",
ips_in_jail: ips_in_jail()
)
end

@impl true
def handle_event("bail_ip_from_jail", %{"ip" => ip}, socket) do
# See https://github.com/xward/phoenix_ddos/blob/feb07469ce318214cddb8e88ac18b5f94b3e31f2/lib/phoenix_ddos/core/jail.ex#L36
PhoenixDDoS.Jail.bail_out(ip)
{:noreply, socket}
end

defp ips_in_jail do
# See https://github.com/xward/phoenix_ddos/blob/master/lib/phoenix_ddos/core/jail.ex
{:ok, keys} = Cachex.keys(:phoenix_ddos_jail)
keys |> Enum.reject(&String.starts_with?(&1, "suspicious_"))
end

defp env_value(env_value), do: System.get_env(env_value)

defp env_value_to_int(env_name) do
env_name |> System.get_env("500") |> Integer.parse() |> elem(0)
end

defp env_value_to_list(env_name) do
case System.get_env(env_name, "") do
"" -> "<vide>"
value -> value
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<section class="pb-48">
<div class="container">
<h2>Plug</h2>
<p>
Le plug qui gère la logique de rate limiting, bloquer ou autoriser des requêtes est dans <a
href="https://github.com/etalab/transport-site/blob/master/apps/transport/lib/transport_web/plugs/rate_limiter.ex"
target="_blank"
><code>TransportWeb.Plugs.RateLimiter</code></a>.
</p>
<p>
Le comptage des requêtes et le blocage d'une adresse IP est effectué par la librairie <a href="https://github.com/xward/phoenix_ddos"><code>phoenix_ddos</code></a>. Le backend est Cachex, qui utilise la RAM. Ainsi un redémarrage de l'application
<code>prod-site</code>
réinitialise les compteurs et les adresses IPs bloquées.
</p>

<h2>Configuration</h2>
<p>
Plusieurs variables d'environnement permettent de configurer le fonctionnement. Il faut changer ces variables pour ajuster le comportement puis redémarrer l'application.
</p>

<h4>Volume de requêtes</h4>
<ul>
<li>
<code>PHOENIX_DDOS_MAX_2MIN_REQUESTS</code>
: nombre de requêtes max autorisée par IP par période de 2 minutes. Valeur actuelle : <%= format_number(
@phoenix_ddos_max_2min_requests
) %>
</li>
<li>
<code>PHOENIX_DDOS_MAX_1HOUR_REQUESTS</code>
: nombre de requêtes max autorisée par IP par période d'1 heure. Valeur actuelle : <%= format_number(
@phoenix_ddos_max_1hour_requests
) %>
</li>
</ul>

<h4>Adresses IPs autorisées ou bloquées</h4>

<ul>
<li>
<code>PHOENIX_DDOS_SAFELIST_IPS</code>
: liste d'adresses IP <strong>toujours autorisées</strong>. Les valeurs doivent être séparées par des <code>|</code>. Valeur actuelle :
<code><%= @phoenix_ddos_safelist_ips %></code>
</li>
<li>
<code>PHOENIX_DDOS_BLOCKLIST_IPS</code>
: liste d'adresses IP <strong>toujours bloquées</strong>. Les valeurs doivent être séparées par des <code>|</code>. Valeur actuelle :
<code><%= @phoenix_ddos_blocklist_ips %></code>
</li>
</ul>

<h4>User-Agents</h4>

<ul>
<li>
<code>LOG_USER_AGENT</code>
: active ou désactive le fait de logguer les user agents. Valeurs possible : <code>true</code>
ou <code>false</code>. Valeur actuelle : <code><%= @log_user_agent %></code>
</li>
<li>
<code>ALLOW_USER_AGENTS</code>
: user agents <strong>toujours autorisés</strong>. Les valeurs doivent être séparées par des <code>|</code>. Valeur actuelle :
<code><%= @allow_user_agents %></code>
</li>
<li>
<code>BLOCK_USER_AGENT_KEYWORDS</code>
: user agents <strong>toujours bloqués</strong>. Les valeurs doivent être séparées par des <code>|</code>. Valeur actuelle :
<code><%= @block_user_agent_keywords %></code>
</li>
</ul>

<h2>Adresses IPs bloquées</h2>
<p>
<a href="https://github.com/xward/phoenix_ddos"><code>phoenix_ddos</code></a>
est la dépendance qui gère l'ajout/le retrait de la jail.
</p>

<p :if={Enum.empty?(@ips_in_jail)}>
Personne n'est bloqué actuellement.
</p>

<div :if={Enum.count(@ips_in_jail) > 0}>
<p>
Adresses IPs actuellement dans la jail :
</p>

<ul>
<%= for ip <- @ips_in_jail do %>
<li>
<%= ip %>
<button class="button small" phx-click="bail_ip_from_jail" phx-value-ip={ip}>
Retirer de la jail
</button>
</li>
<% end %>
</ul>
</div>
<p class="small">Dernière mise à jour: <%= @last_updated_at %></p>
</div>
</section>
<script defer type="text/javascript" src={static_path(@socket, "/js/app.js")} />
4 changes: 4 additions & 0 deletions apps/transport/lib/transport_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ defmodule TransportWeb.Router do
live("/cache", CacheLive)
end

live_session :rate_limiter, root_layout: {TransportWeb.LayoutView, :app} do
live("/rate_limiter", RateLimiterLive)
end

get("/import_aoms", PageController, :import_all_aoms)

live_session :data_import_batch_report, root_layout: {TransportWeb.LayoutView, :app} do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<%= dgettext("backoffice", "Cache debug") %>
</a>

<a class="button" href={backoffice_live_path(@conn, TransportWeb.Backoffice.RateLimiterLive)}>
<%= dgettext("backoffice", "Rate limiter") %>
</a>

<a class="button" href={backoffice_live_path(@conn, TransportWeb.Backoffice.DataImportBatchReportLive)}>
<%= dgettext("backoffice", "GTFS data import") %>
</a>
Expand Down
4 changes: 4 additions & 0 deletions apps/transport/priv/gettext/backoffice.pot
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "AOM open data status"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Rate limiter"
msgstr ""
4 changes: 4 additions & 0 deletions apps/transport/priv/gettext/en/LC_MESSAGES/backoffice.po
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "AOM open data status"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Rate limiter"
msgstr ""
4 changes: 4 additions & 0 deletions apps/transport/priv/gettext/fr/LC_MESSAGES/backoffice.po
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,7 @@ msgstr "Conversions relancées"
#, elixir-autogen, elixir-format
msgid "AOM open data status"
msgstr "Ouverture des données par AOM"

#, elixir-autogen, elixir-format
msgid "Rate limiter"
msgstr ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule TransportWeb.Backoffice.RateLimiterLiveTest do
use ExUnit.Case, async: true
use TransportWeb.LiveCase
import TransportWeb.ConnCase, only: [setup_admin_in_session: 1]

@endpoint TransportWeb.Endpoint
@url "/backoffice/rate_limiter"

setup do
{:ok, conn: build_conn()}
end

test "requires login", %{conn: conn} do
conn
|> get(@url)
|> html_response(302)
end

test "page can load", %{conn: conn} do
conn
|> setup_admin_in_session()
|> get(@url)
|> html_response(200)
end
end

0 comments on commit abc05ee

Please sign in to comment.