< Return to Video

RailsConf 2014 - Rack::Attack: Protect your app with this one weird gem! by Aaron Suggs

  • 0:17 - 0:20
    AARON SUGGS: All right. Can people hear OK?
  • 0:20 - 0:21
    I'll go ahead and get started.
  • 0:21 - 0:24
    So this talk is Rack::Attack and
  • 0:24 - 0:27
    how to protect your app with this one weird
    gem.
  • 0:27 - 0:31
    Where does Rack::Attack come from? We built
    it at
  • 0:31 - 0:34
    KickStarter. If you haven't heard of KickStarter,
    it is
  • 0:34 - 0:38
    a funding platform for creative projects.
    So somebody has
  • 0:38 - 0:41
    an idea for a film, a comic book, an
  • 0:41 - 0:44
    open source project, a gadget. They, they
    put their
  • 0:44 - 0:47
    project up on our site. They can offer rewards
  • 0:47 - 0:50
    for various pledge levels. Their friends,
    family, strangers on
  • 0:50 - 0:53
    the internet come and can, can give them money.
  • 0:53 - 0:56
    At the end of the deadline, if they've reached
  • 0:56 - 0:58
    their funding goal and so they have enough
    to
  • 0:58 - 1:01
    reach their project, that's when we process
    the transactions
  • 1:01 - 1:03
    and the creators' get the funds they need
    to,
  • 1:03 - 1:04
    to do the project.
  • 1:04 - 1:07
    To give you a sense of scale for what
  • 1:07 - 1:09
    we do, we, we recently crossed over a billion
  • 1:09 - 1:12
    dollars pledged to the site. It's over a million
  • 1:12 - 1:15
    dollars a day. And it's gone to over 60,000
  • 1:15 - 1:17
    creative projects.
  • 1:17 - 1:22
    Quick introduction. My name's Aaron Suggs.
    I go by
  • 1:22 - 1:25
    ktheory on social media. I love dancing in
    my
  • 1:25 - 1:30
    bear outfit. And I'm the operations engineer
    at KickStarter.
  • 1:30 - 1:33
    We, we have a very dev ops-y style workflow.
  • 1:33 - 1:35
    So, so it means I end up writing a
  • 1:35 - 1:37
    lot of Ruby code, and I love writing Ruby
  • 1:37 - 1:38
    code.
  • 1:38 - 1:42
    So, so Rack::Attack is, is a tool I wrote,
  • 1:42 - 1:46
    and it's Rack middleware for blocking and
    throttling abusive
  • 1:46 - 1:49
    requests. What do we mean by abusive requests?
    These
  • 1:49 - 1:52
    can be things like malicious attackers trying
    to take
  • 1:52 - 1:55
    down your site, doing things like trying to
    crack
  • 1:55 - 1:59
    user accounts or get sensitive information,
    or it can
  • 1:59 - 2:02
    be naively written scrapers, who are just,
    like, people
  • 2:02 - 2:05
    on the internet doing weird things as they
    are
  • 2:05 - 2:08
    prone to do, and that's cool, but sometimes
    it,
  • 2:08 - 2:10
    it is a lot of traffic. It's a lot
  • 2:10 - 2:12
    of resources for your app to try to handle,
  • 2:12 - 2:16
    and Rack::Attack is a very elegant DSL and,
    and
  • 2:16 - 2:19
    way for dealing with these sorts of things.
    Sort
  • 2:19 - 2:22
    of constraining their behavior so your website
    stays up.
  • 2:22 - 2:27
    Rack::Attack is on GitHub at slash kickstarter
    slash rack-attack.
  • 2:27 - 2:30
    It's an open source Ruby gem. There's a README,
  • 2:30 - 2:34
    sort of exactly like what you'd expect.
  • 2:34 - 2:37
    So the big wins that KickStarter has gotten
    from
  • 2:37 - 2:40
    using Rack::Attack, and the reason we developed
    it, was
  • 2:40 - 2:43
    we wanted to increase our performance. So,
    so this
  • 2:43 - 2:46
    is like site performance. We, we had problems
    with
  • 2:46 - 2:50
    sort of abusive requests making our website
    slow because
  • 2:50 - 2:53
    they were using up too many app servers CP.
  • 2:53 - 2:54
    Too much app server CPU, or too much, too
  • 2:54 - 2:58
    many database resources, by sort of constraining
    them we
  • 2:58 - 3:01
    were able to make the website faster for the
  • 3:01 - 3:03
    sort of, the most important requests. Like
    people coming
  • 3:03 - 3:06
    on, wanting to watch videos, wanting to pledge
    money.
  • 3:06 - 3:08
    Not people just trying to scrape down the
    entire
  • 3:08 - 3:08
    site.
  • 3:08 - 3:12
    We also improved our available. Because sometimes
    these requests
  • 3:12 - 3:14
    were, were so much, there were so many that
  • 3:14 - 3:16
    they would take down the site, or there would
  • 3:16 - 3:20
    just be some weird incident and, we, right.
    It,
  • 3:20 - 3:23
    it hurt our availability.
  • 3:23 - 3:26
    But the biggest win that we had was developer
  • 3:26 - 3:30
    happiness. Because dealing with these sort
    of bad actors
  • 3:30 - 3:34
    on the internet especially if it means, like,
    your,
  • 3:34 - 3:36
    your site's going down or like, the, you know,
  • 3:36 - 3:39
    you need to scale up because somebody's doing
    something
  • 3:39 - 3:42
    weird, that can really interrupt a lot of
    developers.
  • 3:42 - 3:44
    It can, it can sort of derail your product
  • 3:44 - 3:47
    road map. We want to be writing cool features
  • 3:47 - 3:50
    and Rack::Attack was a great DSL to let us
  • 3:50 - 3:53
    spend less time thinking about that stuff
    and more
  • 3:53 - 3:55
    stuff doing the stuff that we, that we like
  • 3:55 - 3:55
    doing.
  • 3:55 - 3:59
    So let me talk about the origin story for
  • 3:59 - 4:01
    Rack::Attack. Like, what happened at KickStarter
    that made us
  • 4:01 - 4:05
    realize we, we needed this? Let's rewind to
    the
  • 4:05 - 4:11
    summer of 2012.
  • 4:11 - 4:13
    And this happened. So this is a story in
  • 4:13 - 4:16
    a graph. So the blue line, I hope it
  • 4:16 - 4:20
    shows up pretty well. Cool. Is our regular
    successful
  • 4:20 - 4:22
    logins. People typing in an email and password
    and
  • 4:22 - 4:25
    us being like, OK, you are logged in. You
  • 4:25 - 4:27
    know, it ebs and flows throughout the day.
  • 4:27 - 4:31
    Suddenly, one Sun, one Saturday afternoon,
    we just get
  • 4:31 - 4:34
    so many of these, like, bad login requests,
    and
  • 4:34 - 4:36
    for awhile we're like, what's going on? Did
    we
  • 4:36 - 4:39
    deploy a feature that broke login? No. Somebody
    is
  • 4:39 - 4:42
    trying to, to crack our user accounts. They're
    just
  • 4:42 - 4:45
    like guessing email addresses and passwords
    as fast as
  • 4:45 - 4:49
    they can, from several different IP addresses.
  • 4:49 - 4:52
    So, as the ops guy, this is sort of
  • 4:52 - 4:54
    on my plate. I'm like, OK, well, I gotta
  • 4:54 - 4:56
    stop this. This is bad for the site for
  • 4:56 - 4:59
    this to be going on. So I wrote a
  • 4:59 - 5:02
    pretty nasty before filter for our login action,
    that's
  • 5:02 - 5:05
    like, you know, keep a counter in memcache
    and,
  • 5:05 - 5:07
    you know, if it's too many like, like, give
  • 5:07 - 5:11
    them an error page and it was, it was
  • 5:11 - 5:15
    kind of a sucky experience, because I was
    changing
  • 5:15 - 5:17
    a really critical feature of our site, sort
    of
  • 5:17 - 5:20
    under duress of, of knowing that I needed
    to
  • 5:20 - 5:23
    get it out there quickly. And it was sort
  • 5:23 - 5:24
    of like a big change, and in the pull
  • 5:24 - 5:27
    request I was, I was apologetic, being like,
    I
  • 5:27 - 5:28
    know this is badly tested and it's like a
  • 5:28 - 5:30
    nasty code change, but we've got to get it
  • 5:30 - 5:34
    out fast because this, this event's going
    on.
  • 5:34 - 5:37
    And, so that, so we did that. And then
  • 5:37 - 5:40
    sort of in the cold light of day, I
  • 5:40 - 5:42
    reflected a little bit and I thought, we need
  • 5:42 - 5:47
    a more elegant way to prevent bad requests.
    This
  • 5:47 - 5:50
    is, it's not just gonna be about this login
  • 5:50 - 5:52
    attack. This is gonna be about a whole class
  • 5:52 - 5:54
    of problems that we might have on the site.
  • 5:54 - 5:58
    You know, I should say, too, with that login
  • 5:58 - 6:00
    attack, it was something that we sort of always
  • 6:00 - 6:03
    imagined that, like, oh yeah, of course we
    should,
  • 6:03 - 6:05
    like, throttle login requests. We just hadn't
    ever gotten
  • 6:05 - 6:07
    around to it. You know, it was in our
  • 6:07 - 6:10
    ticketing system as like a low-priority someday
    somebody should
  • 6:10 - 6:13
    do this thing. And having it actually happen
    was
  • 6:13 - 6:15
    like, OK, now we gotta do it right now.
  • 6:15 - 6:19
    So, we realized, like, we need this generic
    tool
  • 6:19 - 6:24
    to stop bad requests. And really, there's
    already, in
  • 6:24 - 6:26
    the Ruby world, a great solution for this,
    and
  • 6:26 - 6:29
    it's Rack middleware. So now we get to the
  • 6:29 - 6:32
    code section of the talk. Here comes some
    code.
  • 6:32 - 6:34
    Get ready.
  • 6:34 - 6:36
    This is an example of, like, the most basic
  • 6:36 - 6:38
    Rack middleware. Just, really quick, for,
    for people who
  • 6:38 - 6:41
    might not be familiar with it. So middleware
    is
  • 6:41 - 6:46
    basically like hugging your application, wrapping
    around so, so
  • 6:46 - 6:47
    you, you have your Rails app or your Sinatra
  • 6:47 - 6:52
    app, that is the app in this case. And
  • 6:52 - 6:54
    you want to do things, you want to sort
  • 6:54 - 6:56
    of be able to do things to the request
  • 6:56 - 6:59
    that's coming in from the client. That's the
    end.
  • 6:59 - 7:02
    So every, every request from a client is gonna
  • 7:02 - 7:03
    do this call method where you pass in the
  • 7:03 - 7:06
    environment, the environment is, like, I don't
    know, what
  • 7:06 - 7:09
    page the client wants or what they're cookie
    is
  • 7:09 - 7:12
    and, and all that information.
  • 7:12 - 7:14
    And so the real magic of Rack middleware is
  • 7:14 - 7:17
    it lets you do stuff here with, with the
  • 7:17 - 7:19
    requests. Like, you can block it in the case
  • 7:19 - 7:23
    of Rack::Attack, potentially. Or you can do
    stuff with
  • 7:23 - 7:26
    the response. You can log it. You can cache
  • 7:26 - 7:28
    it. Stuff like that.
  • 7:28 - 7:29
    So this, so this is just a great pattern
  • 7:29 - 7:34
    for managing, for sort of making easy architectures
    to
  • 7:34 - 7:39
    do stuff with HTTP requests. So in Rack::Attack's
    case,
  • 7:39 - 7:41
    this is a sort of simplified version of the
  • 7:41 - 7:45
    Rack::Attack call method. We say, for this
    request, should
  • 7:45 - 7:48
    we allow it? If so, go ahead and pass
  • 7:48 - 7:52
    it onto your application. Your application
    is gonna do,
  • 7:52 - 7:53
    potentially, a lot of work.
  • 7:53 - 7:56
    Maybe it's gonna spend a couple hundred milliseconds,
    like,
  • 7:56 - 7:59
    querying the database and rendering views
    and stuff like
  • 7:59 - 8:02
    that. So that's the expensive work that we
    want
  • 8:02 - 8:05
    to save if the, if this is an abusive
  • 8:05 - 8:08
    request. So, so if we shouldn't allow it,
    then
  • 8:08 - 8:11
    we just return back this very fast access-denied
    as
  • 8:11 - 8:15
    a very simple and fast response to render.
  • 8:15 - 8:18
    Rack::Attack can do several hundred of these
    access denied
  • 8:18 - 8:22
    requests per, like, thread that you have running.
    So
  • 8:22 - 8:25
    like, per unicorn worker or per Heroku instance
    or
  • 8:25 - 8:27
    something like that.
  • 8:27 - 8:30
    But, so, that's what you get for, when you
  • 8:30 - 8:32
    just use the Rack middleware for free. So,
    so
  • 8:32 - 8:35
    we don't yet know what this should_allow method
    should
  • 8:35 - 8:36
    be. That's code that you sort of have to
  • 8:36 - 8:39
    configure yourself, of what do you want to
    throttle
  • 8:39 - 8:40
    on.
  • 8:40 - 8:43
    So that looks like this. This is sort of
  • 8:43 - 8:46
    a generic throttle that you might put in your,
  • 8:46 - 8:51
    in an initializer to configure Rack::Attack.
    The important stuff
  • 8:51 - 8:53
    that's going on here is we are calling the
  • 8:53 - 8:57
    throttle class method on Rack::Attack, so
    that's just something
  • 8:57 - 9:00
    we expose to let you plug into the middleware.
  • 9:00 - 9:02
    We give it a name, in this case it's
  • 9:02 - 9:05
    the, we, we named the throttle IP. This is
  • 9:05 - 9:08
    gonna determine how we track it. And that
    just
  • 9:08 - 9:11
    has to be unique throughout your application.
    We're gonna
  • 9:11 - 9:13
    give it a limit and a period. And so
  • 9:13 - 9:16
    that's how much, the, the period is how many
  • 9:16 - 9:18
    seconds we're gonna be considering for the
    throttle, and
  • 9:18 - 9:20
    the limit is sort of your quota for how
  • 9:20 - 9:23
    many requests you get to make during that
    time.
  • 9:23 - 9:25
    So in this case, it's ten requests every five
  • 9:25 - 9:31
    seconds. For the arithmetically inclined,
    you'll notice that this
  • 9:31 - 9:34
    is not like a reduced fraction. We could say
  • 9:34 - 9:37
    two requests every one second. The advantage
    of doing
  • 9:37 - 9:39
    a higher multiple is that, like, it allows
    a
  • 9:39 - 9:43
    little burstiness. So these periods are basically
    dividing time
  • 9:43 - 9:46
    up into these, like, five second long buckets.
    So
  • 9:46 - 9:49
    in between zero and, seconds and five seconds
    after
  • 9:49 - 9:52
    the minute, like, in that window, you're allowed
    to
  • 9:52 - 9:54
    make up to ten requests.
  • 9:54 - 9:58
    And so by having bigger multiples in bigger
    windows,
  • 9:58 - 10:01
    you can sort of get around some burstiness
    at,
  • 10:01 - 10:04
    but the long-term average stays the same.
    Like, long
  • 10:04 - 10:07
    term, nobody's gonna make more requests that
    two every
  • 10:07 - 10:09
    one second.
  • 10:09 - 10:12
    OK, so what's going on? We got the, the
  • 10:12 - 10:15
    class method. We got the name. WE have the
  • 10:15 - 10:17
    limit and the period. And then to this block,
  • 10:17 - 10:21
    we are passing along the request. Now, in
    the
  • 10:21 - 10:23
    earlier middleware expample we talked, we
    called this the
  • 10:23 - 10:26
    end, which was just like the, the environment
    hash
  • 10:26 - 10:29
    that comes from the request. Request is just
    like
  • 10:29 - 10:34
    a light little Rack request object wrapped
    around the
  • 10:34 - 10:37
    environment that just sort of gives you methods,
    instance
  • 10:37 - 10:40
    methods to call, like dot IP or dot host
  • 10:40 - 10:41
    or dot path or something like that. It just
  • 10:41 - 10:46
    sort of, you use these in Rails controllers,
    too.
  • 10:46 - 10:50
    So it's just like a lightly-wrapped request.
    And then
  • 10:50 - 10:52
    inside the block, what the block returns is
    the
  • 10:52 - 10:55
    sort of really important part. That's the
    discriminator that
  • 10:55 - 10:58
    determines how we're gonna bucket up these
    throttles. So
  • 10:58 - 11:01
    in this case we are gonna say every IP
  • 11:01 - 11:04
    address, every distinct IP address is going
    to get
  • 11:04 - 11:07
    its own throttle limit. But we could throttle
    by
  • 11:07 - 11:10
    something else. WE could throttle by a parameter
    or
  • 11:10 - 11:14
    a host name or something like that, or an
  • 11:14 - 11:16
    API token.
  • 11:16 - 11:18
    And one thing to note with these discriminators,
    too,
  • 11:18 - 11:21
    is like, if this would, this is returning
    a
  • 11:21 - 11:24
    string, so it's always gonna be a truthy value,
  • 11:24 - 11:27
    and true values sort of enable the, the throttling.
  • 11:27 - 11:29
    Like, we are gonna throttle these requests
    as long
  • 11:29 - 11:32
    as there's an IP address, and there always
    is.
  • 11:32 - 11:35
    If we would return nil or a falsey value,
  • 11:35 - 11:37
    we just sort of let the request go through
  • 11:37 - 11:39
    and we're not gonna throttle it. I'll talk
    about
  • 11:39 - 11:42
    why we might want to do that later. But,
  • 11:42 - 11:44
    so now we have this issue of throttle state.
  • 11:44 - 11:46
    Like, we have these counters per IP address
    that
  • 11:46 - 11:48
    we need to track.
  • 11:48 - 11:51
    And so, so where do we store that? A
  • 11:51 - 11:53
    pretty elegant and simple and obvious place
    for that
  • 11:53 - 11:57
    was our Rails cache. So when you just use
  • 11:57 - 11:59
    Rack::Attack by default, if you have a Rails
    cache,
  • 11:59 - 12:02
    it's gonna use it. But, it really works best
  • 12:02 - 12:06
    with memcache or redis. So, so I hope you're
  • 12:06 - 12:09
    using that as your Rails cache. But if you're
  • 12:09 - 12:11
    not, like, there are ways that you can build
  • 12:11 - 12:13
    your own, or sort of like plug in a,
  • 12:13 - 12:15
    a different cache store.
  • 12:15 - 12:17
    The great advantage about memcache and redis
    is that
  • 12:17 - 12:21
    they have really good support for atomically
    incrementing counters,
  • 12:21 - 12:23
    and that's the sort of key feature we'd need
  • 12:23 - 12:26
    behind the scenes. So now we're imagining
    for, for
  • 12:26 - 12:28
    every request that comes in, we need to sort
  • 12:28 - 12:31
    of increment the counter per IP address.
  • 12:31 - 12:32
    And so how do we do that? Like what's,
  • 12:32 - 12:35
    what's the algorithm? So this is the nitty
    gritty
  • 12:35 - 12:40
    of how Rack::Attack works. How it constructs
    that key.
  • 12:40 - 12:43
    So remember how we divided the minute up into
  • 12:43 - 12:47
    like little buckets depending on our period.
    So, so
  • 12:47 - 12:49
    to do that, we sort of take the current
  • 12:49 - 12:54
    second. We construct a key that is the name
  • 12:54 - 12:57
    of our request, like IP in this case. We
  • 12:57 - 12:59
    take the time divided by the period, so this
  • 12:59 - 13:03
    means that that middle component is going
    to be,
  • 13:03 - 13:05
    is going to increment every five seconds.
    It's gonna,
  • 13:05 - 13:08
    so it's, the key's gonna change.
  • 13:08 - 13:09
    And then the final part is that block return
  • 13:09 - 13:12
    value. So in this case it's the IP address
  • 13:12 - 13:15
    of the request. But maybe it's an API token
  • 13:15 - 13:17
    or something like that.
  • 13:17 - 13:19
    So at the end of it, we have this
  • 13:19 - 13:22
    key that changes every couple seconds. Every
    time, like,
  • 13:22 - 13:25
    the period rotates, and this ends up being
    a
  • 13:25 - 13:27
    very efficient use case, a very efficient
    use of
  • 13:27 - 13:31
    memcache or redis. Like, this is, storing
    all this
  • 13:31 - 13:34
    information is gonna take, like, a couple
    megabytes. It's
  • 13:34 - 13:36
    like, don't worry about the impact on your
    cache
  • 13:36 - 13:39
    store in pretty much every scenario.
  • 13:39 - 13:41
    To make it even more efficient use of your
  • 13:41 - 13:46
    cache store, we set an expire rate, so that
  • 13:46 - 13:48
    in that, like, in that bucket window of, say,
  • 13:48 - 13:50
    zero to five seconds, we're gonna say that
    all
  • 13:50 - 13:53
    those cache keys expire at five seconds. So
    at
  • 13:53 - 13:57
    the same moment that the cache keys change,
    they
  • 13:57 - 14:00
    also expire. So memcache or redis just ends
    up
  • 14:00 - 14:03
    reusing the same memory blocks over and over.
    You
  • 14:03 - 14:06
    don't have, even though there's changing,
    they're changing in
  • 14:06 - 14:08
    memory, you don't have as much churn as you
  • 14:08 - 14:11
    would otherwise.
  • 14:11 - 14:14
    And so then the Rack middleware is really
    doing
  • 14:14 - 14:16
    pretty simple stuff of we're saying, for whatever
    your
  • 14:16 - 14:20
    cache is, increment this key with this expire
    rate.
  • 14:20 - 14:21
    That's gonna give us back the count of how
  • 14:21 - 14:24
    many requests that have been made that, that
    match
  • 14:24 - 14:27
    that throttle. And if it's more than our limit,
  • 14:27 - 14:30
    we're gonna return that access denied response.
  • 14:30 - 14:33
    So, we rolled this out. You know, we're able
  • 14:33 - 14:37
    to have this global throttle per IP address.
    We
  • 14:37 - 14:41
    start making a couple other, other features,
    and it
  • 14:41 - 14:44
    was about a year later when we had a,
  • 14:44 - 14:47
    the sort of redux of, of a new event
  • 14:47 - 14:49
    that put Rack::Attack to the test.
  • 14:49 - 14:52
    So, a new challenger emerges in the summer
    of
  • 14:52 - 14:58
    2013. This was a script called kicksniper
    dot py.
  • 14:58 - 15:02
    And this revealed a pretty interesting behavior
    on KickStarter
  • 15:02 - 15:05
    that we call reward sniping. Actually, kicksniper
    dot py
  • 15:05 - 15:09
    refers to it in the code as reward sniping.
  • 15:09 - 15:12
    And so, this is, this is an, an interesting
  • 15:12 - 15:15
    behavior because. So I told you how KickStarter
    offers
  • 15:15 - 15:18
    these rewards. They can be limited rewards.
    So a
  • 15:18 - 15:20
    creator says, I'm only gonna give away, like,
    a
  • 15:20 - 15:24
    hundred of these, and first come, first serve.
  • 15:24 - 15:27
    So, there's a, a pretty popular project where
    it
  • 15:27 - 15:29
    was like a video game and, and the video
  • 15:29 - 15:32
    game was offering these reward tiers that
    would be,
  • 15:32 - 15:34
    like, for fifty bucks, you get, like, the
    silver
  • 15:34 - 15:36
    level package, and for a hundred bucks you
    get
  • 15:36 - 15:38
    the gold package, and so on and so, like,
  • 15:38 - 15:41
    ever more deluxe and expensive packages. And
    they were
  • 15:41 - 15:43
    all very much in demand.
  • 15:43 - 15:47
    So the early reward tiers like sold-out super
    fast.
  • 15:47 - 15:50
    And then occasionally, somebody in, who had
    those early
  • 15:50 - 15:53
    reward tiers, would decide they're gonna splurge
    and they're
  • 15:53 - 15:55
    gonna upgrade. They're gonna change their
    pledge to a
  • 15:55 - 15:59
    higher one, and now for that moment, like,
    there's
  • 15:59 - 16:01
    now one available of the lower tier. And so
  • 16:01 - 16:05
    people were like hitting refresh, refresh,
    refresh, hoping that
  • 16:05 - 16:08
    they just noticed when somebody, when somebody
    had changed
  • 16:08 - 16:09
    their pledge and now there was one of these
  • 16:09 - 16:12
    highly desirable lower-tier pledges available.
  • 16:12 - 16:18
    Some entrepreneur, enterprising Python developer,
    says, I will make
  • 16:18 - 16:22
    a script that does this for me. Sure enough,
  • 16:22 - 16:25
    so, so he writes kicksniper dot py that's,
    that's
  • 16:25 - 16:27
    in a tight loop, trying to change his pledge
  • 16:27 - 16:29
    on our site. Saying, like, let me get that,
  • 16:29 - 16:32
    that early reward tier. You know, our ActiveRecord
    validations
  • 16:32 - 16:34
    were working fine and we said, no, you can't
  • 16:34 - 16:36
    change your pledge to that the vast majority
    of
  • 16:36 - 16:40
    the time, but, but eventually he got through
    and
  • 16:40 - 16:41
    was able to get the pledge.
  • 16:41 - 16:43
    It was such a great success that he goes
  • 16:43 - 16:46
    on all the forums and says, hey, everybody
    just
  • 16:46 - 16:51
    run this, like, Python script on your laptop
    and
  • 16:51 - 16:53
    you, too, might look, luck out and get one
  • 16:53 - 16:56
    of these highly desirable earlier reward tiers.
  • 16:56 - 17:01
    So let's tell this story in a graph. So,
  • 17:01 - 17:04
    this is our master database CPU over the course
  • 17:04 - 17:06
    of a, of a day or so. We see
  • 17:06 - 17:08
    at the very beginning, it starts off between
    ten
  • 17:08 - 17:11
    or fifteen percent. That's my happy place.
    That's where
  • 17:11 - 17:12
    I like it to be. We have plenty of
  • 17:12 - 17:15
    head room for like, you know, big projects
    to
  • 17:15 - 17:17
    sort of blow up on the site, as they
  • 17:17 - 17:19
    do from time to time.
  • 17:19 - 17:21
    And, I honestly didn't really notice that
    it had
  • 17:21 - 17:24
    been creeping up over the course of the day.
  • 17:24 - 17:28
    Thursday morning, it crossed thirty percent,
    and that's when
  • 17:28 - 17:31
    I get a CPU alert threshold. So it, so
  • 17:31 - 17:33
    in fact, the whole dev team gets this email
  • 17:33 - 17:35
    being like, hey, the master database CPU is
    pretty
  • 17:35 - 17:37
    high. You guys should check that out.
  • 17:37 - 17:41
    So, what do we, you know, we, we spend
  • 17:41 - 17:43
    a little time, we're like, why is the database
  • 17:43 - 17:45
    so high? Well, you know, it looks like there
  • 17:45 - 17:47
    are a crazy number of requests trying to change
  • 17:47 - 17:50
    their pledge for this one project.
  • 17:50 - 17:53
    We, we're able to sort of construct this back
  • 17:53 - 17:55
    story and, like, see what was happening on
    the
  • 17:55 - 17:57
    database CPU. We see the form request where
    everybody's
  • 17:57 - 18:02
    like, thank you for kicksniper dot py. And
    so,
  • 18:02 - 18:04
    and we're like, all right, so, so how are
  • 18:04 - 18:06
    we gonna handle this? Like, is it really that
  • 18:06 - 18:09
    important that people are able to try to change
  • 18:09 - 18:11
    their pledge like multiple times a second?
  • 18:11 - 18:14
    What if they only could change their pledge
    every
  • 18:14 - 18:17
    couple seconds? Right, like, I guess that's
    fair enough
  • 18:17 - 18:19
    to the, like, there's this question of, like,
    what's
  • 18:19 - 18:22
    the fairest way to allocate the scarce resources
    of,
  • 18:22 - 18:24
    of like the pledge as soon as it's available.
  • 18:24 - 18:27
    I kind of don't care about the answer. Anybody
  • 18:27 - 18:28
    can get it.
  • 18:28 - 18:32
    But, but we're like, if we start throttling
    these
  • 18:32 - 18:35
    people, it's like totally fair. They're using
    an inordinate
  • 18:35 - 18:38
    number of resources. And people who are just
    clicking
  • 18:38 - 18:40
    around the site are having a slower experience
    because
  • 18:40 - 18:42
    our database CPU is so high.
  • 18:42 - 18:44
    So we decide, like, OK, you can make a
  • 18:44 - 18:46
    couple requests per minute to change a pledge.
    It
  • 18:46 - 18:50
    was one line of Rack::Attack code. We deploy
    it.
  • 18:50 - 18:52
    The yellow vertical lines here are deploy
    lines, so
  • 18:52 - 18:55
    you can see that right here, about an hour
  • 18:55 - 18:57
    after we get the alert that something was
    going
  • 18:57 - 19:02
    wrong, we deploy and immediately our database
    CPU drops.
  • 19:02 - 19:05
    We're pretty much back to the happy place.
  • 19:05 - 19:08
    And so, for us, that was like, revealing the,
  • 19:08 - 19:09
    the great success that we could have. Like,
    it
  • 19:09 - 19:12
    was so easy, like, once we figured out what
  • 19:12 - 19:14
    was going on, it was so easy for us
  • 19:14 - 19:17
    to write code that just, like, solved that
    problem.
  • 19:17 - 19:19
    We didn't have to think about, like, how do
  • 19:19 - 19:23
    we optimize the edit pledge flow? Which could
    have
  • 19:23 - 19:26
    been, like, a much bigger product change,
    and derail,
  • 19:26 - 19:28
    like, taken up a lot more developer time.
    It
  • 19:28 - 19:30
    was sort of a cut and dry decision of
  • 19:30 - 19:33
    like, most people aren't gonna try to change
    their
  • 19:33 - 19:35
    pledge, like, we're super confused if you're
    actually trying
  • 19:35 - 19:37
    to change your pledge several times a minute.
  • 19:37 - 19:39
    That's a, that's a bug we should fix. But
  • 19:39 - 19:41
    it's really just these scrapers. It's not
    big deal
  • 19:41 - 19:43
    to say they can try a few times a
  • 19:43 - 19:43
    minute.
  • 19:43 - 19:47
    So, that was a big win for Rack::Attack at
  • 19:47 - 19:50
    KickStarter. We feel like we sort of, we sort
  • 19:50 - 19:54
    of cemented that its value in the organization.
    So
  • 19:54 - 19:56
    now I'm gonna shift gears a little bit and
  • 19:56 - 20:00
    I'm gonna tell you pro tips of general things
  • 20:00 - 20:02
    you can do with Rack::Attack that, that are
    probably
  • 20:02 - 20:03
    useful for your application.
  • 20:03 - 20:07
    I just, oh my gosh I'm so glad that
  • 20:07 - 20:08
    I got to use this gif. This gif is
  • 20:08 - 20:13
    like condensed, pure condensed happiness for
    me. OK. Back
  • 20:13 - 20:14
    to the code.
  • 20:14 - 20:17
    So, we talked about how to do, like, a
  • 20:17 - 20:20
    general, a, a log, I'm sorry. We talked about
  • 20:20 - 20:24
    how to do a throttle for all IP addresses.
  • 20:24 - 20:26
    So like each IP has this quota of how
  • 20:26 - 20:29
    many requests you can do. But, in our, in
  • 20:29 - 20:32
    our origin story about the login attack, we
    wanted
  • 20:32 - 20:35
    to be extra careful about login requests.
    Like, those
  • 20:35 - 20:38
    are something that, that you would want to
    throttle
  • 20:38 - 20:41
    even more strictly than you would throttle
    many other
  • 20:41 - 20:44
    things on your, in your application.
  • 20:44 - 20:47
    So this is a new throttle, and so we
  • 20:47 - 20:50
    give it a new name of logins per IP.
  • 20:50 - 20:52
    And this is saying that if you are making
  • 20:52 - 20:56
    a post request to the login url, then we
  • 20:56 - 20:58
    want to throttle you by IP to like this
  • 20:58 - 21:02
    much, this lower limit. And so this is relying
  • 21:02 - 21:05
    on the fact that we mentioned earlier, that
    if
  • 21:05 - 21:07
    the block returns nil, we're not gonna do
    throttle
  • 21:07 - 21:09
    at all. So, so if this is not a
  • 21:09 - 21:12
    post to the login action, like, we're not
    gonna
  • 21:12 - 21:14
    check memcache, we're not gonna increment
    any counters or
  • 21:14 - 21:16
    do anything like that. We're just gonna sort
    of
  • 21:16 - 21:19
    allow this request right through.
  • 21:19 - 21:20
    But if it is, we're gonna hold you, we're
  • 21:20 - 21:23
    gonna say each IP address gets this lower
    quota
  • 21:23 - 21:25
    of how many login requests they can make.
  • 21:25 - 21:27
    Thinking of this same problem from a, from
    a
  • 21:27 - 21:30
    kind of different angle, you might want to
    imagine
  • 21:30 - 21:32
    a, a situation where a, an attacker is using
  • 21:32 - 21:36
    many different IP addresses to try to crack
    passwords
  • 21:36 - 21:39
    for one particular email address, right. Maybe
    it's the
  • 21:39 - 21:42
    founder's email address or something like
    that.
  • 21:42 - 21:44
    So you, so putting on your security hat, you
  • 21:44 - 21:46
    can be like, how am I gonna be safe
  • 21:46 - 21:49
    from those kinds of requests? The only change
    here
  • 21:49 - 21:52
    is what we're returning. Instead of the IP
    address,
  • 21:52 - 21:55
    we're returning the value of the email parameter.
    So
  • 21:55 - 21:58
    this is a, a sort of little different way
  • 21:58 - 22:01
    of thinking about throttles, of saying, whoever
    you are,
  • 22:01 - 22:03
    if you're trying to login with this one particular
  • 22:03 - 22:06
    IP address, you can only do it five times
  • 22:06 - 22:08
    every twenty seconds.
  • 22:08 - 22:11
    So those are two throttles that pretty much
    everybody
  • 22:11 - 22:14
    should, should have that feature on their
    website. If
  • 22:14 - 22:16
    you haven't been bitten by it yet, it's probably
  • 22:16 - 22:19
    just a matter of time.
  • 22:19 - 22:22
    Another pretty cool Rack::Attack feature are
    blacklists. So these
  • 22:22 - 22:24
    are requests that you don't even want to throttle.
  • 22:24 - 22:27
    Like, you're not gonna allow them at all.
    Just,
  • 22:27 - 22:30
    access denied every time they happen. I kind,
    I
  • 22:30 - 22:33
    was gonna call these blocks, but like blocks,
    I
  • 22:33 - 22:35
    can't call them blocks. Because in Ruby the,
    like,
  • 22:35 - 22:37
    that's already a different thing.
  • 22:37 - 22:39
    So hence the term blacklists.
  • 22:39 - 22:42
    Here's an example of a pretty handy blacklist.
    Say
  • 22:42 - 22:45
    you have an admin section of your website,
    and
  • 22:45 - 22:47
    you want to restrict access to the admin section
  • 22:47 - 22:50
    to just like, your one office IP address.
    So
  • 22:50 - 22:53
    this is, again, it's using the, it's using
    the
  • 22:53 - 22:57
    blacklist class method on Rack::Attack to
    sort of configure
  • 22:57 - 22:59
    this in the middleware. You would, you would
    put
  • 22:59 - 23:03
    this in an initializer, saying that, you're
    given a
  • 23:03 - 23:07
    name like bad_admin_ip, and one of the things,
    like,
  • 23:07 - 23:09
    it's different than throttles in that we don't
    have
  • 23:09 - 23:11
    to pass along a limit of a period, because
  • 23:11 - 23:14
    it just like, it doesn't apply to blacklists.
  • 23:14 - 23:16
    But it has the same logic where if the
  • 23:16 - 23:19
    return value of this block is truthy, we're
    gonna,
  • 23:19 - 23:22
    like, just give them the very fast access
    denied
  • 23:22 - 23:24
    message. If it's false, then we're gonna let
    the
  • 23:24 - 23:27
    request through. So this is saying, if you're
    making
  • 23:27 - 23:31
    a request to a url that starts with admin,
  • 23:31 - 23:34
    and you are not from this IP address, we're
  • 23:34 - 23:38
    gonna, we're gonna just give you an access
    denied.
  • 23:38 - 23:41
    This is something that KickStarter uses. We
    call it
  • 23:41 - 23:46
    the starve the trolls feature. So this is,
    if,
  • 23:46 - 23:49
    if you're one of our banned IPs that our
  • 23:49 - 23:52
    customer support team decides which IPs get
    banned, you
  • 23:52 - 23:55
    cannot make any request that's not a get request.
  • 23:55 - 23:58
    Or, put another way, you can only make get
  • 23:58 - 24:00
    requests if you're from these IP addresses.
  • 24:00 - 24:02
    So let's think about what it's like to use
  • 24:02 - 24:05
    a dynamic web application if you're only using
    gets.
  • 24:05 - 24:08
    You can't sign up. You can't log in. You
  • 24:08 - 24:11
    can't post comments. These are, these are,
    we sort
  • 24:11 - 24:15
    of use this as a measure of last resort
  • 24:15 - 24:18
    for people who are, who are bad actors in
  • 24:18 - 24:21
    our community. Any big community has, you
    know, knows
  • 24:21 - 24:23
    that this stuff is sort of inevitable, to
    have
  • 24:23 - 24:27
    a few rotten apples.
  • 24:27 - 24:29
    And this has been like really fast and effective
  • 24:29 - 24:31
    for our community team to be able to just
  • 24:31 - 24:34
    like put these IP addresses into a yaml file.
  • 24:34 - 24:36
    They leave them there for about a week or
  • 24:36 - 24:39
    so, and you know gives that person sort of
  • 24:39 - 24:41
    time to cool off, where they're not gonna
    go
  • 24:41 - 24:43
    around signing up for a bunch of accounts
    and,
  • 24:43 - 24:47
    and maybe doing bad stuff or, like, posting
    messages
  • 24:47 - 24:49
    or stuff like that.
  • 24:49 - 24:52
    So this is, I don't, I was really, I
  • 24:52 - 24:54
    was sort of struck when we started doing this
  • 24:54 - 24:58
    of like how simple this was in code, and
  • 24:58 - 25:01
    how much it helped our CSS, or, our community
  • 25:01 - 25:04
    support team. So this is another example of,
    like,
  • 25:04 - 25:06
    sort of an area where I wouldn't expect Rack::Attack
  • 25:06 - 25:08
    to be very helpful, but it ended up being
  • 25:08 - 25:11
    very helpful.
  • 25:11 - 25:17
    Another Rack::Attack nice to have feature
    is ActiveSupport::Notifications. So,
  • 25:17 - 25:21
    every time, if, if ActiveSupport::Notifications
    are in your app,
  • 25:21 - 25:24
    and so for any Rails app they're already there,
  • 25:24 - 25:29
    we will fire a ActiveSupport notification
    event every time
  • 25:29 - 25:33
    a request gets blocked or throttled. So this
    means
  • 25:33 - 25:35
    you can have a subscriber to these events
    that's
  • 25:35 - 25:38
    gonna log or graph these events and stuff
    like
  • 25:38 - 25:40
    that. There are examples of how to do that
  • 25:40 - 25:44
    in the README on GitHub.
  • 25:44 - 25:48
    So thinking of where Rack::Attack might fall
    in the
  • 25:48 - 25:51
    set of tools you use to keep your site
  • 25:51 - 25:54
    fast and reliable, it is, it's not a silver
  • 25:54 - 25:57
    bullet. Like, it very much compliments things
    like, the
  • 25:57 - 26:04
    iptables firewall, or nginx limit_conn_zone,
    limit conn module to
  • 26:04 - 26:07
    limit the number of concurrent requests per
    IP address.
  • 26:07 - 26:08
    Or if you have, like, a CDN or a
  • 26:08 - 26:11
    web app firewall. So, like, you know, hardware
    to,
  • 26:11 - 26:14
    to keep your website fast and reliable. Like,
    keep
  • 26:14 - 26:15
    doing those.
  • 26:15 - 26:18
    Rack::Attack's not a silver bullet. You know,
    it's, if
  • 26:18 - 26:23
    you have a mtp reflection ddos attack, like,
    it's
  • 26:23 - 26:27
    gonna overwhelm your Unicorn or Heroku processes
    pretty fast.
  • 26:27 - 26:31
    You need something else. But, what Rack::Attack
    really is
  • 26:31 - 26:34
    good at is, it's Ruby. It knows everything
    about
  • 26:34 - 26:36
    your app, like, I mean, because it's in your
  • 26:36 - 26:41
    application, you can use other logic from
    your app.
  • 26:41 - 26:43
    Because it's Ruby, it's easy to test. You
    write
  • 26:43 - 26:46
    integration tests for it the same way you
    write
  • 26:46 - 26:48
    tests for the rest of your application.
  • 26:48 - 26:50
    And it's easy to deploy, because it's Ruby
    code.
  • 26:50 - 26:52
    I don't know how you deploy changes to a
  • 26:52 - 26:55
    CDN or a web app firewall, but it's probably
  • 26:55 - 26:58
    a different process than how you deploy your
    Ruby
  • 26:58 - 27:01
    code. And, and this is something that a lot,
  • 27:01 - 27:06
    everybody on our engineering team is comfortable
    doing.
  • 27:06 - 27:09
    So that, that's why, that's where Rack::Attack
    can fit
  • 27:09 - 27:14
    in into your application security mindset.
  • 27:14 - 27:16
    I also wanted to call out and say thank
  • 27:16 - 27:20
    you to my many GitHub contributors. These
    people are
  • 27:20 - 27:24
    really awesome and they've taken Rack::Att-
    like, added really
  • 27:24 - 27:26
    cool features, like allow to ban and fail
    to
  • 27:26 - 27:29
    ban, and they've cleaned up documentation
    and they've made
  • 27:29 - 27:32
    the tests a lot better. They support, added
    reddis
  • 27:32 - 27:37
    support was, it used to be just memcache.
    But
  • 27:37 - 27:41
    these people are doing fantastic things with
    open source.
  • 27:41 - 27:44
    They're from five different continents, too,
    which, like it
  • 27:44 - 27:47
    feels so cool to put code out there and,
  • 27:47 - 27:50
    like, people from five different continents
    contribute to it
  • 27:50 - 27:52
    because they find it useful.
  • 27:52 - 27:56
    So, more like that please.
  • 27:56 - 28:02
    So, sort of wrapping up, the web, weird stuff
  • 28:02 - 28:07
    happens on the web. It's inevitable. It's
    good in
  • 28:07 - 28:09
    a lot of cases. I, I like that, you
  • 28:09 - 28:12
    know, people write really innovative things
    and, and stuff
  • 28:12 - 28:13
    that I would never would have come up with.
  • 28:13 - 28:16
    Like, that's fantastic. So I hope the web
    stays
  • 28:16 - 28:18
    weird. But I also hope that the website stays
  • 28:18 - 28:21
    up. And Rack::Attack lets you have the best
    of
  • 28:21 - 28:24
    both worlds.
  • 28:24 - 28:28
    So that's all I had. That's, that's Rack::Attack
    at
  • 28:28 - 28:31
    KickStarter. If you have any quest- I'd love
    to
  • 28:31 - 28:35
    answer any questions if people have them.
    And, if
  • 28:35 - 28:36
    you're more comfortable, hit me up on Twitter
    or
  • 28:36 - 28:39
    find me after the talk.
Title:
RailsConf 2014 - Rack::Attack: Protect your app with this one weird gem! by Aaron Suggs
Description:

more » « less
Duration:
29:04

English subtitles

Revisions