• Stars
    star
    417
  • Rank 100,328 (Top 3 %)
  • Language
    Elixir
  • Created over 7 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A plug building toolkit for blocking and throttling abusive requests

PlugAttack

A plug building toolkit for blocking and throttling abusive requests.

This is inspired by the Kickstarter's Rack::Attack middleware for Ruby.

Installation

If available in Hex, the package can be installed as:

  1. Add plug_attack to your list of dependencies in mix.exs:
def deps do
  [{:plug_attack, "~> 0.4.2"}]
end
  1. If using Elixir 1.3, ensure that plug_attack is started before your application
    (this step is no longer applicable to Elixir 1.4+):
def application do
  [applications: [:plug_attack]]
end

Basic usage

We first need to construct a plug that will list various rules we want to apply:

defmodule MyApp.PlugAttack do
  use PlugAttack

  rule "allow local", conn do
    allow conn.remote_ip == {127, 0, 0, 1}
  end
end

The MyApp.PlugAttack module is now a regular plug that can be used, for example, in a phoenix endpoint.

WARNING: if you're behind a proxy, like nginx or heroku's router, you need to make sure you have a plug that respects the X-Forwarded-For headers, for example: remote_ip.

Throttling

Before we implement throttling in our attack plug, we need to add a storage to our supervision tree. This can be achieved by adding following to the supervision tree:

children = [
  # other children
  worker(PlugAttack.Storage.Ets, [MyApp.PlugAttack.Storage, [clean_period: 60_000]])
]

# or using child specifications:

children = [
  {PlugAttack.Storage.Ets, name: MyApp.PlugAttack.Storage, clean_period: 60_000}
]

We've configured the table to be cleaned of stale data every minute. The usage patterns of the table by the throttling rules means no stale data will be ever accessed. This is only a measure used to control the memory usage.

Now we can add a rule to our plug allowing 10 requests every minute from a single ip address:

rule "throttle by ip", conn do
  throttle conn.remote_ip,
    period: 60_000, limit: 10,
    storage: {PlugAttack.Storage.Ets, MyApp.PlugAttack.Storage}
end

Rate limiting headers

We can customize the actions taken by PlugAttack on blocked or allowed requests, by adding rate limiting headers for well behaved clients.

To do this, we can define two functions in our plug - allow_action/3 and block_action/3. Those are similar to regular plugs - accepting a connection as the first argument and opts as the last one. The middle argument represents the blocking or allowing data returned by the rule. The throttling rule returns data in the form of {:throttle, data}, where data is a keyword with various useful data we can use to construct rate limiting headers.

import Plug.Conn
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")
  |> halt # It's important to halt connection once we send a response early
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

License

Copyright 2015 Michał Muskała

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

More Repositories

1

jason

A blazing fast JSON parser and generator in pure Elixir.
Elixir
1,561
star
2

decompile

Elixir
151
star
3

persistent_ets

Elixir
128
star
4

phoenix_etag

ETag support for phoenix
Elixir
43
star
5

env

Env is an improved application configuration reader for Elixir.
Elixir
29
star
6

plug_webhook

Simple tool for building plugs that handle wehbooks and verify signature.
Elixir
23
star
7

debounce

A process-based debouncer for Elixir
Elixir
21
star
8

format

A simple string formatter for Elixir
Elixir
17
star
9

xref.rs

Rust reimplementation of subset of Erlang's xref
Rust
14
star
10

horde

Persistent, distributed processes for Elixir
Elixir
8
star
11

fast_beam

Rust library for reading .beam files
Rust
8
star
12

hex_view_old

Elixir
7
star
13

time_format

Fast date/time formatting for elixir
Elixir
7
star
14

better_assert

Erlang
6
star
15

hex_view

Source explorer for hex packages
Elixir
5
star
16

simple_format

Rail's simple_format helper for Phoenix.HTML
Elixir
5
star
17

persist

GenStage-based persistence layer guaranteeing at-least-once delivery of the events.
Elixir
4
star
18

tic-tac-toe-workshop

Elixir
4
star
19

aoc-2020

Advent of code for 2020
Rust
3
star
20

exrb

Ruby-Elixir interoperability
Ruby
3
star
21

manager_supervisor

Elixir
3
star
22

superconsole

Enhancements to rails console based on pry
Ruby
2
star
23

llscm

Scheme to llvm compilter
Haskell
2
star
24

trace

Elixir
1
star
25

infiniq

Elixir
1
star
26

matasano

My solutions to the Matasano challenge in Elixir.
Elixir
1
star
27

dotfiles

Emacs Lisp
1
star
28

file_count_optim

Benchmark for testing implementations of Enumerable.count/1 for File.Stream.
Elixir
1
star
29

janusze

Ruby
1
star
30

jason_native

C++
1
star