Rack应用简单的访问保护

September 15, 2015 23:01


简介

rack-attack 是一个基于 Rack 的中间件, 它可以按定义规则来禁止特定的访问,或是使用规则来限制访问频率

它对性能的影响极小,几乎可以忽略,每个请求在几毫秒内完成处理, 但是它会依赖你定义的规则的数量、规则匹配所使用的时间, 及与 throttle 使用的 cache 的访问速度

使用体会

在使用 rack-attack 后,暴露出来一些原本不知道的问题, 像客户端不在预期中的频繁请求,用户一些预料之外的操作造成的请求发送过多, 然后就是暴露出恶意的请求,ban掉一些请求从而为应用服务器减负

功能介绍

基本上翻译自 rack-attack 的 README

Whitelists

# 允许来些本机的请求
# 如果用户在whitelist中, blacklist 与 throttles 规则将被跳过
Rack::Attack.whitelist('allow from localhost') do |req|
  # 如果返回 true,表示请求是允许的
  '127.0.0.1' == req.ip
end

Blacklists

# 禁止来自IP 1.2.3.4的 请求
Rack::Attack.blacklist('block 1.2.3.4.') do |req|
  '1.2.3.4' == req.ip
end

Fail2Ban

Rack::Attack.blacklist('fail2ban') do |req|
  # 遇到匹配的请求,会直接 black (返回true)
  # 如果用户继续请求,在 findtime 时间内请求超过 maxretry 次时
  # 在 bantime 时间内将被禁止任何访问 (不再运行 block 中的判断,直接返回true)
  Rack::Attack::Fail2Ban.filter(req.ip, maxretry: 3, findtime: 10.minutes, bantime: 5.minutes) do
    req.params['query'] =~ /password|admin/
  end
end

Allow2Ban

Rack::Attack.blacklist('allow2ban login scrapers') do |req|
  # 遇到匹配的请求会返回 false
  # 如果用户在 findtime 时间内请求超过 maxretry 次时
  # 在 bantime 时间内将被禁止任何访问 (不再运行 block 中的判断,直接返回true)
  Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 10, findtime: 1.minute, bantime: 1.hour) do
    req.path == '/login'
  end
end

Throttles

# 限制每个 IP 每秒(period) 最多发送 3 个请求
Rack::Attack.throttle('req/ip', limit: 3, period: 1.second) do |req|
  req.ip
end

# 限制每个 email 60秒内,最多请求 `/login` 接口5次
Rack::Attack.throttle('login/email', limit: 5, period: 60) do |req|
  if req.path == '/login' && req.post?
    req.params['email']
  end
end

# 通过proc来动态调整 limit 和 period
limit_proc = proc {|req| req.path =~ /^\/api/ ? 60 : 10 }
period_proc = proc {|req| req.path =~ /^\/api/ ? 1.minute : 30.seconds }
Rack::Attack.throttle('req/ip', limit: limit_proc, period: period_proc) do |req|
  req.ip
end

Tracks

# 追踪特定的请求,每个匹配的请求都会触发 notification 
Rack::Attack.trace('special_agent') do |req|
  req.user_agent == 'SpecialAgent'
end

# 支持可选参数 limit 和 period, 只有达到 limit 的限制时,才会触发 notification
Rack::Attack.trace('special_agent', limit: 10, period: 1.minute) do |req|
  req.user_agent == 'SpecialAgent'
end

# 使用 ActiveSupport::Notification 来得到Trace匹配的请求
ActiveSupport::Notification.subscribe('rack.attack') do |name, start, finish, request_id, req|
  if req.env['rack.attack.matched'] == 'special_agent' && rack.env['rack.attack.match_type'] == 'trace'
    Rails.logger.info "special_agent: #{req.path}"
  end
end

应用

# 适当限制短时内的爆发请求
Rack::Attack.throttle('level 1', limit: 30, period: 20.seconds) do |req|
  req.ip
end

# 限制长期持续的或循环请求
Rack::Attack.throttle('level 2', limit: 60, period: 1.minute) do |req|
  req.ip
end
Rack::Attack.throttle('req path', limit: 10, period: 30.seconds) do |req|
  [req.ip, req.path].join('-')
end

特别的说明

__ Version: 4.3.0 __

References

Comments: