mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-21 15:32:32 +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,
|
||||
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(
|
||||
|
|
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
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -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]},
|
||||
|
|
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_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"},
|
||||
|
|
Loading…
Reference in a new issue