Class: Rack::Deflect

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/contrib/deflect.rb

Overview

Rack middleware for protecting against Denial-of-service attacks en.wikipedia.org/wiki/Denial-of-service_attack.

This middleware is designed for small deployments, which most likely are not utilizing load balancing from other software or hardware. Deflect current supports the following functionality:

  • Saturation prevention (small DoS attacks, or request abuse)

  • Blacklisting of remote addresses

  • Whitelisting of remote addresses

  • Logging

Options:

:log                When false logging will be bypassed, otherwise pass an object responding to #puts
:log_format         Alter the logging format
:log_date_format    Alter the logging date format
:request_threshold  Number of requests allowed within the set :interval. Defaults to 100
:interval           Duration in seconds until the request counter is reset. Defaults to 5
:block_duration     Duration in seconds that a remote address will be blocked. Defaults to 900 (15 minutes)
:whitelist          Array of remote addresses which bypass Deflect. NOTE: this does not block others
:blacklist          Array of remote addresses immediately considered malicious

Examples:

use Rack::Deflect, :log => $stdout, :request_threshold => 20, :interval => 2, :block_duration => 60

CREDIT: TJ Holowaychuk <[email protected]>

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Deflect

Returns a new instance of Deflect.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/rack/contrib/deflect.rb', line 44

def initialize app, options = {}
  @mutex = Mutex.new
  @remote_addr_map = {}
  @app, @options = app, {
    :log => false,
    :log_format => 'deflect(%s): %s',
    :log_date_format => '%m/%d/%Y',
    :request_threshold => 100,
    :interval => 5,
    :block_duration => 900,
    :whitelist => [],
    :blacklist => []
  }.merge(options)
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



42
43
44
# File 'lib/rack/contrib/deflect.rb', line 42

def options
  @options
end

Instance Method Details

#block!Object



100
101
102
103
104
# File 'lib/rack/contrib/deflect.rb', line 100

def block!
  return if blocked?
  log "blocked #{@remote_addr}"
  map[:block_expires] = Time.now + options[:block_duration]
end

#block_expired?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/rack/contrib/deflect.rb', line 110

def block_expired?
  map[:block_expires] < Time.now rescue false
end

#blocked?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/rack/contrib/deflect.rb', line 106

def blocked?
  map.has_key? :block_expires
end

#call(env) ⇒ Object



59
60
61
62
63
# File 'lib/rack/contrib/deflect.rb', line 59

def call env
  return deflect! if deflect? env
  status, headers, body = @app.call env
  [status, headers, body]
end

#clear!Object



118
119
120
121
122
# File 'lib/rack/contrib/deflect.rb', line 118

def clear!
  return unless watching?
  log "released #{@remote_addr}" if blocked?
  @remote_addr_map.delete @remote_addr
end

#deflect!Object



65
66
67
# File 'lib/rack/contrib/deflect.rb', line 65

def deflect!
  [403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
end

#deflect?(env) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
# File 'lib/rack/contrib/deflect.rb', line 69

def deflect? env
  @remote_addr = env['REMOTE_ADDR']
  return false if options[:whitelist].include? @remote_addr
  return true  if options[:blacklist].include? @remote_addr
  sync { watch }
end

#exceeded_request_threshold?Boolean

Returns:

  • (Boolean)


128
129
130
# File 'lib/rack/contrib/deflect.rb', line 128

def exceeded_request_threshold?
  map[:requests] > options[:request_threshold]
end

#increment_requestsObject



124
125
126
# File 'lib/rack/contrib/deflect.rb', line 124

def increment_requests
  map[:requests] += 1
end

#log(message) ⇒ Object



76
77
78
79
# File 'lib/rack/contrib/deflect.rb', line 76

def log message
  return unless options[:log]
  options[:log].puts(options[:log_format] % [Time.now.strftime(options[:log_date_format]), message])
end

#mapObject



85
86
87
88
89
90
# File 'lib/rack/contrib/deflect.rb', line 85

def map
  @remote_addr_map[@remote_addr] ||= {
    :expires => Time.now + options[:interval],
    :requests => 0
  }
end

#sync(&block) ⇒ Object



81
82
83
# File 'lib/rack/contrib/deflect.rb', line 81

def sync &block
  @mutex.synchronize(&block)
end

#watchObject



92
93
94
95
96
97
98
# File 'lib/rack/contrib/deflect.rb', line 92

def watch
  increment_requests
  clear! if watch_expired? and not blocked?
  clear! if blocked? and block_expired?
  block! if watching? and exceeded_request_threshold?
  blocked?
end

#watch_expired?Boolean

Returns:

  • (Boolean)


132
133
134
# File 'lib/rack/contrib/deflect.rb', line 132

def watch_expired?
  map[:expires] <= Time.now
end

#watching?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/rack/contrib/deflect.rb', line 114

def watching?
  @remote_addr_map.has_key? @remote_addr
end