Add RackAttack

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2022-04-06 10:23:49 +02:00
parent cff939c673
commit 366f6a9187
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
6 changed files with 151 additions and 11 deletions

View File

@ -0,0 +1,42 @@
defmodule Mobilizon.GraphQL.Middleware.RateLimiter do
@moduledoc """
Absinthe Error Handler
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Users
alias Mobilizon.Users.User
@behaviour Absinthe.Middleware
@impl Absinthe.Middleware
@spec call(Absinthe.Resolution.t(), any) :: Absinthe.Resolution.t()
def call(
%Absinthe.Resolution{context: %{current_user: %User{id: user_id} = user} = context} =
resolution,
_config
) do
case Cachex.fetch(:default_actors, to_string(user_id), fn -> default(user) end) do
{status, %Actor{preferred_username: preferred_username} = current_actor}
when status in [:ok, :commit] ->
Sentry.Context.set_user_context(%{name: preferred_username})
context = Map.put(context, :current_actor, current_actor)
%Absinthe.Resolution{resolution | context: context}
{_, nil} ->
resolution
end
end
def call(%Absinthe.Resolution{} = resolution, _config), do: resolution
@spec default(User.t()) :: {:commit, Actor.t()} | {:ignore, nil}
defp default(%User{} = user) do
case Users.get_actor_for_user(user) do
%Actor{} = actor ->
{:commit, actor}
nil ->
{:ignore, nil}
end
end
end

View File

@ -48,6 +48,7 @@ defmodule Mobilizon do
Guardian.DB.Token.SweeperServer,
ActivityPub.Federator,
TzWorld.Backend.DetsWithIndexCache,
{PlugAttack.Storage.Ets, name: Mobilizon.Web.Plugs.PlugAttack.Storage, clean_period: 60_000},
cachex_spec(:feed, 2500, 60, 60, &Feed.create_cache/1),
cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1),
cachex_spec(

View File

@ -0,0 +1,76 @@
defmodule Mobilizon.Web.Plugs.PlugAttack do
@moduledoc false
use PlugAttack
import Plug.Conn
@alg :sha512
rule "allow local", conn do
allow(conn.remote_ip == {127, 0, 0, 1})
end
# rule "throttle by ip", conn do
# throttle hash_ip(@alg, convert_ip(conn.remote_ip)),
# period: 60_000, limit: 5,
# storage: {PlugAttack.Storage.Ets, Mobilizon.Web.Plugs.PlugAttack.Storage}
# end
rule "fail2ban by ip", conn do
fail2ban(hash_ip(@alg, convert_ip(conn.remote_ip)),
period: 6_000,
limit: 5,
ban_for: 90_000,
storage: {PlugAttack.Storage.Ets, Mobilizon.Web.Plugs.PlugAttack.Storage}
)
end
def allow_action(conn, {:throttle, data}, opts) do
conn
|> add_throttling_headers(data)
|> allow_action(true, opts)
end
def allow_action(conn, _data, _opts) do
conn
end
def block_action(conn, {:throttle, data}, opts) do
conn
|> add_throttling_headers(data)
|> block_action(false, opts)
end
def block_action(conn, _data, _opts) do
conn
|> send_resp(:forbidden, "Forbidden\n")
# It's important to halt connection once we send a response early
|> halt
end
defp add_throttling_headers(conn, data) do
# The expires_at value is a unix time in milliseconds, we want to return one
# in seconds
reset = div(data[:expires_at], 1_000)
conn
|> put_resp_header("x-ratelimit-limit", to_string(data[:limit]))
|> put_resp_header("x-ratelimit-remaining", to_string(data[:remaining]))
|> put_resp_header("x-ratelimit-reset", to_string(reset))
end
defp hash_ip(alg, ip) do
ip_hash_secret =
:mobilizon
|> Application.get_env(Mobilizon.Web.Endpoint)
|> Keyword.get(:secret_key_base, "HASH_IP_SECRET")
:crypto.mac(:hmac, alg, ip_hash_secret, ip)
end
defp convert_ip(ip) do
ip
|> Tuple.to_list()
|> List.to_charlist()
|> IO.chardata_to_string()
end
end

View File

@ -3,20 +3,31 @@ defmodule Mobilizon.Web.Router do
Router for mobilizon app
"""
use Mobilizon.Web, :router
alias Cldr.Plug.AcceptLanguage
alias Mobilizon.Web.Plugs.{
HTTPSecurityPlug,
HTTPSignatures,
MappedSignatureToIdentity,
PlugAttack,
SetLocalePlug
}
import Mobilizon.Web.RequestContext
pipeline :graphql do
# plug(:accepts, ["json"])
plug(:put_request_context)
plug(Mobilizon.Web.Auth.Pipeline)
plug(Mobilizon.Web.Plugs.SetLocalePlug)
plug(SetLocalePlug)
plug(PlugAttack)
end
pipeline :graphiql do
plug(Mobilizon.Web.Auth.Pipeline)
plug(Mobilizon.Web.Plugs.SetLocalePlug)
plug(SetLocalePlug)
plug(Mobilizon.Web.Plugs.HTTPSecurityPlug,
plug(HTTPSecurityPlug,
script_src: ["cdn.jsdelivr.net 'sha256-zkCwvTwqwJMew/8TKv7bTLh94XRSNBvT/o/NZCuf5Kc='"],
style_src: ["cdn.jsdelivr.net 'unsafe-inline'"],
font_src: ["cdn.jsdelivr.net"]
@ -25,40 +36,46 @@ defmodule Mobilizon.Web.Router do
pipeline :host_meta do
plug(:put_request_context)
plug(PlugAttack)
plug(:accepts, ["xrd-xml"])
end
pipeline :well_known do
plug(:put_request_context)
plug(PlugAttack)
plug(:accepts, ["json", "jrd-json"])
end
pipeline :activity_pub_signature do
plug(:put_request_context)
plug(Mobilizon.Web.Plugs.HTTPSignatures)
plug(Mobilizon.Web.Plugs.MappedSignatureToIdentity)
plug(HTTPSignatures)
plug(MappedSignatureToIdentity)
plug(PlugAttack)
end
pipeline :relay do
plug(:put_request_context)
plug(Mobilizon.Web.Plugs.HTTPSignatures)
plug(Mobilizon.Web.Plugs.MappedSignatureToIdentity)
plug(HTTPSignatures)
plug(MappedSignatureToIdentity)
plug(PlugAttack)
plug(:accepts, ["activity-json", "json"])
end
pipeline :activity_pub do
plug(:put_request_context)
plug(PlugAttack)
plug(:accepts, ["activity-json"])
end
pipeline :activity_pub_and_html do
plug(:put_request_context)
plug(PlugAttack)
plug(:accepts, ["html", "activity-json"])
plug(:put_secure_browser_headers)
plug(Mobilizon.Web.Plugs.SetLocalePlug)
plug(SetLocalePlug)
plug(Cldr.Plug.AcceptLanguage,
plug(AcceptLanguage,
cldr_backend: Mobilizon.Cldr,
no_match_log_level: :debug
)
@ -67,16 +84,18 @@ defmodule Mobilizon.Web.Router do
pipeline :atom_and_ical do
plug(:put_request_context)
plug(:put_secure_browser_headers)
plug(PlugAttack)
plug(:accepts, ["atom", "ics", "html", "xml"])
end
pipeline :browser do
plug(:put_request_context)
plug(PlugAttack)
plug(Plug.Static, at: "/", from: "priv/static")
plug(Mobilizon.Web.Plugs.SetLocalePlug)
plug(SetLocalePlug)
plug(Cldr.Plug.AcceptLanguage,
plug(AcceptLanguage,
cldr_backend: Mobilizon.Cldr,
no_match_log_level: :debug
)

View File

@ -204,6 +204,7 @@ defmodule Mobilizon.Mixfile do
{:tz_world, "~> 1.0"},
{:tzdata, "~> 1.1"},
{:codepagex, "~> 0.1.6"},
{:plug_attack, "~> 0.4.2"},
# Dev and test dependencies
{:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
{:ex_machina, "~> 2.3", only: [:dev, :test]},

View File

@ -107,6 +107,7 @@
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.0.1", "0db6eb6405a6b06cae4fdf4144659b3f4fee4553e2856fe8a53ba12e9fb21a74", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e34890004baec08f0fa12bd8c77bf64bfb4156b84a07fb79da9322fa94bc3781"},
"phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
"plug": {:hex, :plug, "1.13.4", "addb6e125347226e3b11489e23d22a60f7ab74786befb86c14f94fb5f23ca9a4", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "06114c1f2a334212fe3ae567dbb3b1d29fd492c1a09783d52f3d489c1a6f4cf2"},
"plug_attack": {:hex, :plug_attack, "0.4.3", "88e6c464d68b1491aa083a0347d59d58ba71a7e591a7f8e1b675e8c7792a0ba8", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9ed6fb8a6f613a36040f2875130a21187126c5625092f24bc851f7f12a8cbdc1"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"postgrex": {:hex, :postgrex, "0.16.2", "0f83198d0e73a36e8d716b90f45f3bde75b5eebf4ade4f43fa1f88c90a812f74", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "a9ea589754d9d4d076121090662b7afe155b374897a6550eb288f11d755acfa0"},