Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rack::Attack: Protect your app with this one weird gem!

Rack::Attack: Protect your app with this one weird gem!

Mature apps face problems with abusive requests like misbehaving users, malicious hackers, and naive scrapers. Too often they drain developer productivity and happiness.

Rack::Attack is middleware to easily throttle abusive requests.

At Kickstarter, we built it to keep our site fast and reliable with little effort. Learn how Rack::Attack works through examples from kickstarter.com. Spend less time dealing with bad apples, and more time on the fun stuff.

Aaron Suggs

April 22, 2014
Tweet

More Decks by Aaron Suggs

Other Decks in Programming

Transcript

  1. Rack::Attack
    Protect your app with this one weird gem!

    View full-size slide

  2. is a funding platform
    for creative projects

    View full-size slide

  3. Aaron Suggs
    ktheory
    Ops Engineer
    KICKSTARTER

    View full-size slide

  4. Rack::Attack
    Middleware for blocking & throttling

    View full-size slide

  5. /kickstarter/rack-attack

    View full-size slide

  6. > performance

    View full-size slide

  7. > availability

    View full-size slide

  8. The origin story

    View full-size slide

  9. I want to stop
    bad requests.

    View full-size slide

  10. I want rack
    middleware.

    View full-size slide

  11. # Example rack middleware
    class MyMiddleware
    def initialize(app)
    @app = app
    end
    !
    def call(env)
    @app.call(env)
    end
    end

    View full-size slide

  12. # Example rack middleware
    class MyMiddleware
    def initialize(app)
    @app = app
    end
    !
    def call(env)
    # Do stuff with the request
    @app.call(env)
    end
    end

    View full-size slide

  13. # Example rack middleware
    class MyMiddleware
    def initialize(app)
    @app = app
    end
    !
    def call(env)
    # Do stuff with the request
    @app.call(env)
    # Do stuff with the response
    end
    end

    View full-size slide

  14. # Rack::Attack is basically:
    def call(env)
    if should_allow?(env)
    @app.call(env)
    else
    access_denied
    end
    end

    View full-size slide

  15. # In an initializer…
    # Throttle IPs to 10 reqs every 5 seconds
    module Rack::Attack
    throttle("ip", limit: 10, period: 5) {|req|
    req.ip
    }
    end

    View full-size slide

  16. # In an initializer…
    # Throttle IPs to 10 reqs every 5 seconds
    module Rack::Attack
    throttle("ip", limit: 10, period: 5) {|req|
    req.ip
    }
    end
    Rack::Request.new(env)

    View full-size slide

  17. # In an initializer…
    # Throttle IPs to 10 reqs every 5 seconds
    module Rack::Attack
    throttle("ip", limit: 10, period: 5) {|req|
    req.ip
    }
    end
    Discriminator

    View full-size slide

  18. Throttle state
    •Rails.cache by default
    •memcached or redis
    •build your own

    View full-size slide

  19. # Making the cache key
    now = Time.now.to_i
    !
    # Key changes each period
    key = "#{name}:#{now/period}:#{block_return}"
    !
    # => "ip:279569154:127.0.0.1"

    View full-size slide

  20. # Key expires at the end of this period
    expires_in = period - (now % period)

    View full-size slide

  21. # Middleware increments counter and blocks
    count = @cache.increment(key, expires_in)
    !
    access_denied if count > limit

    View full-size slide

  22. kicksniper.py

    View full-size slide

  23. # Throttle logins per IP
    throttle('logins/ip', limit: 5, period: 20) {|req|
    !
    if req.path == '/login' && req.post?
    req.ip
    end
    !
    }

    View full-size slide

  24. # Throttle logins per email
    throttle('logins/email', limit: 5, period: 20) {|req|
    !
    if req.path == '/login' && req.post?
    req.params['email'].presence
    end
    !
    }

    View full-size slide

  25. blacklists
    Not even once.

    View full-size slide

  26. # Easy VPN
    OFFICE_IP = '1.2.3.4'
    !
    blacklist('bad_admin_ip') {|req|
    req.path.start_with?('/admin') && req.ip != OFFICE_IP
    }

    View full-size slide

  27. # Starve the trolls
    # <3 our community support team
    BANNED_IPS = Set.new ['1.2.3.4', '5.6.7.8']
    !
    blacklist('banned_ip') {|req|
    BANNED_IPS.include?(req.ip) && ! req.get?
    }

    View full-size slide

  28. ActiveSupport::Notifications
    for metrics

    View full-size slide

  29. Rack::Attack compliments
    •iptables
    •nginx limit_conn_zone
    •CDN, Web App Firewall

    View full-size slide

  30. Rack::Attack is
    •ruby
    •easy to test
    •easy to deploy

    View full-size slide

  31. Thank you, contributors

    View full-size slide

  32. Julian Doherty @ barelyknown
    Vipul A M Hakan Ensari
    Zach Millman @ alexchee
    Steve Hodgkiss T.J. Schuck
    Carsten Zimmermann Salimane Adjao Moustapha
    Jordan Moncharmont Pedro Nascimento
    Han Richard Schneeman
    Tieg Zaharia Navin
    Tristan Dunn Michael Jelks

    View full-size slide

  33. The web
    stays weird.

    View full-size slide

  34. The web site
    stays up.

    View full-size slide

  35. /kickstarter/rack-attack
    ktheory
    Image CC BY flickr.com/matthamm

    View full-size slide