mirror of
https://github.com/benbusby/farside.git
synced 2025-04-20 10:58:42 +00:00
Throttle incoming requests to 1/sec per ip
This introduces a way of throttling requests in a way that makes sense for the purpose of the app. The app only supports redirecting to one particular service when browsing, which would seldom be required more than once per second for normal "human" browsing. Without this, the service could easily be used to DOS multiple instances at once. That being said, anyone concerned about someone DOS-ing multiple instances at once should be aware that this would be trivial to do with a simple bash script. This is simply a preventative measure to hopefully deter people from trying to attack all public instances of private frontends using farside.link. Note that this throttling applies to all routes in the app, including the homepage. This could be updated to exclude the homepage I guess, but I'm not really sure what the use case would be for that.
This commit is contained in:
parent
8ee4f308a4
commit
2d988a1239
6 changed files with 38 additions and 24 deletions
|
@ -8,7 +8,8 @@ defmodule Farside.Application do
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
Plug.Cowboy.child_spec(scheme: :http, plug: Farside.Router, options: [port: 4001]),
|
Plug.Cowboy.child_spec(scheme: :http, plug: Farside.Router, options: [port: 4001]),
|
||||||
{Redix, {@redis_conn, [name: :redix]}}
|
{Redix, {@redis_conn, [name: :redix]}},
|
||||||
|
{PlugAttack.Storage.Ets, name: Farside.Throttle.Storage, clean_period: 60_000}
|
||||||
]
|
]
|
||||||
|
|
||||||
opts = [strategy: :one_for_one, name: Farside.Supervisor]
|
opts = [strategy: :one_for_one, name: Farside.Supervisor]
|
||||||
|
|
|
@ -3,6 +3,7 @@ defmodule Farside.Router do
|
||||||
|
|
||||||
use Plug.Router
|
use Plug.Router
|
||||||
|
|
||||||
|
plug(Farside.Throttle)
|
||||||
plug(:match)
|
plug(:match)
|
||||||
plug(:dispatch)
|
plug(:dispatch)
|
||||||
|
|
||||||
|
|
19
lib/farside/throttle.ex
Normal file
19
lib/farside/throttle.ex
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Farside.Throttle do
|
||||||
|
import Plug.Conn
|
||||||
|
use PlugAttack
|
||||||
|
|
||||||
|
rule "throttle per ip", conn do
|
||||||
|
# throttle to 1 request per second
|
||||||
|
throttle conn.remote_ip,
|
||||||
|
period: 1_000, limit: 1,
|
||||||
|
storage: {PlugAttack.Storage.Ets, Farside.Throttle.Storage}
|
||||||
|
end
|
||||||
|
|
||||||
|
def allow_action(conn, _data, _opts), do: conn
|
||||||
|
|
||||||
|
def block_action(conn, _data, _opts) do
|
||||||
|
conn
|
||||||
|
|> send_resp(:forbidden, "Exceeded rate limit\n")
|
||||||
|
|> halt
|
||||||
|
end
|
||||||
|
end
|
5
mix.exs
5
mix.exs
|
@ -22,11 +22,12 @@ defmodule Farside.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
|
{:httpoison, "~> 1.8"},
|
||||||
{:jason, "~> 1.1"},
|
{:jason, "~> 1.1"},
|
||||||
|
{:plug_attack, "~> 0.4.2"},
|
||||||
{:plug_cowboy, "~> 2.0"},
|
{:plug_cowboy, "~> 2.0"},
|
||||||
{:poison, "~> 5.0"},
|
{:poison, "~> 5.0"},
|
||||||
{:httpoison, "~> 1.8"},
|
{:redix, "~> 1.1"},
|
||||||
{:redix, "~> 1.1"}
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -14,6 +14,7 @@
|
||||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||||
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
||||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||||
|
"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"},
|
||||||
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
||||||
|
|
|
@ -8,22 +8,21 @@ defmodule FarsideTest do
|
||||||
|
|
||||||
@opts Router.init([])
|
@opts Router.init([])
|
||||||
|
|
||||||
test "/" do
|
def test_conn(path) do
|
||||||
conn =
|
:timer.sleep(1000)
|
||||||
:get
|
:get
|
||||||
|> conn("/", "")
|
|> conn(path, "")
|
||||||
|> Router.call(@opts)
|
|> Router.call(@opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/" do
|
||||||
|
conn = test_conn("/")
|
||||||
assert conn.state == :sent
|
assert conn.state == :sent
|
||||||
assert conn.status == 200
|
assert conn.status == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
test "/ping" do
|
test "/ping" do
|
||||||
conn =
|
conn = test_conn("/ping")
|
||||||
:get
|
|
||||||
|> conn("/ping", "")
|
|
||||||
|> Router.call(@opts)
|
|
||||||
|
|
||||||
assert conn.state == :sent
|
assert conn.state == :sent
|
||||||
assert conn.status == 200
|
assert conn.status == 200
|
||||||
assert conn.resp_body == "PONG"
|
assert conn.resp_body == "PONG"
|
||||||
|
@ -42,24 +41,16 @@ defmodule FarsideTest do
|
||||||
IO.puts("")
|
IO.puts("")
|
||||||
|
|
||||||
Enum.map(service_names, fn service_name ->
|
Enum.map(service_names, fn service_name ->
|
||||||
|
conn = test_conn("/#{service_name}")
|
||||||
conn =
|
|
||||||
:get
|
|
||||||
|> conn("/#{service_name}", "")
|
|
||||||
|> Router.call(@opts)
|
|
||||||
|
|
||||||
first_redirect = elem(List.last(conn.resp_headers), 1)
|
first_redirect = elem(List.last(conn.resp_headers), 1)
|
||||||
|
|
||||||
IO.puts(" /#{service_name} (#1) -- #{first_redirect}")
|
IO.puts(" /#{service_name} (#1) -- #{first_redirect}")
|
||||||
assert conn.state == :set
|
assert conn.state == :set
|
||||||
assert conn.status == 302
|
assert conn.status == 302
|
||||||
|
|
||||||
|
conn = test_conn("/#{service_name}")
|
||||||
conn =
|
|
||||||
:get
|
|
||||||
|> conn("/#{service_name}", "")
|
|
||||||
|> Router.call(@opts)
|
|
||||||
|
|
||||||
second_redirect = elem(List.last(conn.resp_headers), 1)
|
second_redirect = elem(List.last(conn.resp_headers), 1)
|
||||||
|
|
||||||
IO.puts(" /#{service_name} (#2) -- #{second_redirect}")
|
IO.puts(" /#{service_name} (#2) -- #{second_redirect}")
|
||||||
assert conn.state == :set
|
assert conn.state == :set
|
||||||
assert conn.status == 302
|
assert conn.status == 302
|
||||||
|
|
Loading…
Add table
Reference in a new issue