mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-22 07:52:43 +00:00
Add RackAttack
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
cff939c673
commit
366f6a9187
6 changed files with 151 additions and 11 deletions
42
lib/graphql/middleware/rate_limiter.ex
Normal file
42
lib/graphql/middleware/rate_limiter.ex
Normal 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
|
|
@ -48,6 +48,7 @@ defmodule Mobilizon do
|
||||||
Guardian.DB.Token.SweeperServer,
|
Guardian.DB.Token.SweeperServer,
|
||||||
ActivityPub.Federator,
|
ActivityPub.Federator,
|
||||||
TzWorld.Backend.DetsWithIndexCache,
|
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(:feed, 2500, 60, 60, &Feed.create_cache/1),
|
||||||
cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1),
|
cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1),
|
||||||
cachex_spec(
|
cachex_spec(
|
||||||
|
|
76
lib/web/plugs/plug_attack.ex
Normal file
76
lib/web/plugs/plug_attack.ex
Normal 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
|
|
@ -3,20 +3,31 @@ defmodule Mobilizon.Web.Router do
|
||||||
Router for mobilizon app
|
Router for mobilizon app
|
||||||
"""
|
"""
|
||||||
use Mobilizon.Web, :router
|
use Mobilizon.Web, :router
|
||||||
|
alias Cldr.Plug.AcceptLanguage
|
||||||
|
|
||||||
|
alias Mobilizon.Web.Plugs.{
|
||||||
|
HTTPSecurityPlug,
|
||||||
|
HTTPSignatures,
|
||||||
|
MappedSignatureToIdentity,
|
||||||
|
PlugAttack,
|
||||||
|
SetLocalePlug
|
||||||
|
}
|
||||||
|
|
||||||
import Mobilizon.Web.RequestContext
|
import Mobilizon.Web.RequestContext
|
||||||
|
|
||||||
pipeline :graphql do
|
pipeline :graphql do
|
||||||
# plug(:accepts, ["json"])
|
# plug(:accepts, ["json"])
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
plug(Mobilizon.Web.Auth.Pipeline)
|
plug(Mobilizon.Web.Auth.Pipeline)
|
||||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
plug(SetLocalePlug)
|
||||||
|
plug(PlugAttack)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :graphiql do
|
pipeline :graphiql do
|
||||||
plug(Mobilizon.Web.Auth.Pipeline)
|
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='"],
|
script_src: ["cdn.jsdelivr.net 'sha256-zkCwvTwqwJMew/8TKv7bTLh94XRSNBvT/o/NZCuf5Kc='"],
|
||||||
style_src: ["cdn.jsdelivr.net 'unsafe-inline'"],
|
style_src: ["cdn.jsdelivr.net 'unsafe-inline'"],
|
||||||
font_src: ["cdn.jsdelivr.net"]
|
font_src: ["cdn.jsdelivr.net"]
|
||||||
|
@ -25,40 +36,46 @@ defmodule Mobilizon.Web.Router do
|
||||||
|
|
||||||
pipeline :host_meta do
|
pipeline :host_meta do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["xrd-xml"])
|
plug(:accepts, ["xrd-xml"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :well_known do
|
pipeline :well_known do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["json", "jrd-json"])
|
plug(:accepts, ["json", "jrd-json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activity_pub_signature do
|
pipeline :activity_pub_signature do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
plug(Mobilizon.Web.Plugs.HTTPSignatures)
|
plug(HTTPSignatures)
|
||||||
plug(Mobilizon.Web.Plugs.MappedSignatureToIdentity)
|
plug(MappedSignatureToIdentity)
|
||||||
|
plug(PlugAttack)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :relay do
|
pipeline :relay do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
plug(Mobilizon.Web.Plugs.HTTPSignatures)
|
plug(HTTPSignatures)
|
||||||
plug(Mobilizon.Web.Plugs.MappedSignatureToIdentity)
|
plug(MappedSignatureToIdentity)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["activity-json", "json"])
|
plug(:accepts, ["activity-json", "json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activity_pub do
|
pipeline :activity_pub do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["activity-json"])
|
plug(:accepts, ["activity-json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activity_pub_and_html do
|
pipeline :activity_pub_and_html do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["html", "activity-json"])
|
plug(:accepts, ["html", "activity-json"])
|
||||||
plug(:put_secure_browser_headers)
|
plug(:put_secure_browser_headers)
|
||||||
|
|
||||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
plug(SetLocalePlug)
|
||||||
|
|
||||||
plug(Cldr.Plug.AcceptLanguage,
|
plug(AcceptLanguage,
|
||||||
cldr_backend: Mobilizon.Cldr,
|
cldr_backend: Mobilizon.Cldr,
|
||||||
no_match_log_level: :debug
|
no_match_log_level: :debug
|
||||||
)
|
)
|
||||||
|
@ -67,16 +84,18 @@ defmodule Mobilizon.Web.Router do
|
||||||
pipeline :atom_and_ical do
|
pipeline :atom_and_ical do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
plug(:put_secure_browser_headers)
|
plug(:put_secure_browser_headers)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(:accepts, ["atom", "ics", "html", "xml"])
|
plug(:accepts, ["atom", "ics", "html", "xml"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :browser do
|
pipeline :browser do
|
||||||
plug(:put_request_context)
|
plug(:put_request_context)
|
||||||
|
plug(PlugAttack)
|
||||||
plug(Plug.Static, at: "/", from: "priv/static")
|
plug(Plug.Static, at: "/", from: "priv/static")
|
||||||
|
|
||||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
plug(SetLocalePlug)
|
||||||
|
|
||||||
plug(Cldr.Plug.AcceptLanguage,
|
plug(AcceptLanguage,
|
||||||
cldr_backend: Mobilizon.Cldr,
|
cldr_backend: Mobilizon.Cldr,
|
||||||
no_match_log_level: :debug
|
no_match_log_level: :debug
|
||||||
)
|
)
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -204,6 +204,7 @@ defmodule Mobilizon.Mixfile do
|
||||||
{:tz_world, "~> 1.0"},
|
{:tz_world, "~> 1.0"},
|
||||||
{:tzdata, "~> 1.1"},
|
{:tzdata, "~> 1.1"},
|
||||||
{:codepagex, "~> 0.1.6"},
|
{:codepagex, "~> 0.1.6"},
|
||||||
|
{:plug_attack, "~> 0.4.2"},
|
||||||
# Dev and test dependencies
|
# Dev and test dependencies
|
||||||
{:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
|
{:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
|
||||||
{:ex_machina, "~> 2.3", only: [:dev, :test]},
|
{:ex_machina, "~> 2.3", only: [:dev, :test]},
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -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_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"},
|
"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": {: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_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"},
|
"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"},
|
"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"},
|
||||||
|
|
Loading…
Reference in a new issue