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.



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

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.



44
45
46
# File 'lib/rack/contrib/deflect.rb', line 44

def options
  @options
end

Instance Method Details

#block!(remote_addr) ⇒ Object



102
103
104
105
106
# File 'lib/rack/contrib/deflect.rb', line 102

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

#block_expired?(remote_addr) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/rack/contrib/deflect.rb', line 112

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

#blocked?(remote_addr) ⇒ Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/rack/contrib/deflect.rb', line 108

def blocked?(remote_addr)
  map(remote_addr).has_key? :block_expires
end

#call(env) ⇒ Object



61
62
63
64
65
# File 'lib/rack/contrib/deflect.rb', line 61

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

#clear!(remote_addr) ⇒ Object



120
121
122
123
124
# File 'lib/rack/contrib/deflect.rb', line 120

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

#deflect!Object



67
68
69
# File 'lib/rack/contrib/deflect.rb', line 67

def deflect!
  [403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
end

#deflect?(env) ⇒ Boolean

Returns:

  • (Boolean)


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

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(remote_addr) }
end

#exceeded_request_threshold?(remote_addr) ⇒ Boolean

Returns:

  • (Boolean)


130
131
132
# File 'lib/rack/contrib/deflect.rb', line 130

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

#increment_requests(remote_addr) ⇒ Object



126
127
128
# File 'lib/rack/contrib/deflect.rb', line 126

def increment_requests(remote_addr)
  map(remote_addr)[:requests] += 1
end

#log(message) ⇒ Object



78
79
80
81
# File 'lib/rack/contrib/deflect.rb', line 78

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

#map(remote_addr) ⇒ Object



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

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

#sync(&block) ⇒ Object



83
84
85
# File 'lib/rack/contrib/deflect.rb', line 83

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

#watch(remote_addr) ⇒ Object



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

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

#watch_expired?(remote_addr) ⇒ Boolean

Returns:

  • (Boolean)


134
135
136
# File 'lib/rack/contrib/deflect.rb', line 134

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

#watching?(remote_addr) ⇒ Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/rack/contrib/deflect.rb', line 116

def watching?(remote_addr)
  @remote_addr_map.has_key? remote_addr
end