travis-api/lib/travis/api/attack.rb
Igor Wiedler 0156671fc8 safelist build status image requests coming from github
Currently almost all calls against API are being rate limited, including
build status images. This leads to common requesters such as GitHub's
camo proxy to get rate limited and receive a 429 response code.

This patch attempts to allow those requests.
2016-06-29 10:53:27 +02:00

108 lines
3.0 KiB
Ruby

require 'rack/attack'
class Rack::Attack
class Request
TOKEN = 'travis.access_token'.freeze
def travis_token
env.fetch(TOKEN)
end
def authenticated?
env.include? TOKEN
end
def identifier
authenticated? ? travis_token.to_s : ip
end
end
def self.bantime(value)
case Travis.env
when "production" then value
when "staging" then 10 # ban for 10 seconds on staging
else 1
end
end
POST_SAFELIST = [
"/auth/handshake",
"/auth/post_message",
"/auth/post_message/iframe"
]
IMAGE_PATTERN = /^\/([a-z0-9_-]+)\/([a-z0-9_-]+)\.(png|svg)$/
####
# Whitelisted IP addresses
whitelist('whitelist client requesting from redis') do |request|
Travis.redis.sismember(:api_whitelisted_ips, request.ip)
end
whitelist('safelist build status images when requested by github') do |request|
request.user_agent and request.user_agent.start_with?('github-camo') and IMAGE_PATTERN.match(request.path)
end
####
# Ban based on: IP address
# Ban time: indefinite
# Ban after: manually banned
blacklist('block client requesting from redis') do |request|
Travis.redis.sismember(:api_blacklisted_ips, request.ip)
end
####
# Ban based on: IP address or access token
# Ban time: 5 hours
# Ban after: 10 POST requests within five minutes to /auth/github
blacklist('hammering /auth/github') do |request|
Rack::Attack::Allow2Ban.filter(request.identifier, maxretry: 2, findtime: 5.minutes, bantime: bantime(5.hours)) do
request.post? and request.path == '/auth/github'
end
end
####
# Ban based on: IP address or access token
# Ban time: 1 hour
# Ban after: 10 POST requests within 30 seconds
blacklist('spamming with POST requests') do |request|
Rack::Attack::Allow2Ban.filter(request.identifier, maxretry: 10, findtime: 30.seconds, bantime: bantime(1.hour)) do
request.post? and not POST_SAFELIST.include? request.path
end
end
###
# Throttle: unauthenticated requests to /auth/github - 1 per minute
# Scoped by: IP address
throttle('req/ip/1min', limit: 1, period: 1.minute) do |request|
request.ip unless request.authenticated? and request.path == '/auth/github'
end
###
# Throttle: unauthenticated requests - 500 per minute
# Scoped by: IP address
throttle('req/ip/1min', limit: 500, period: 1.minute) do |request|
request.ip unless request.authenticated?
end
###
# Throttle: authenticated requests - 2000 per minute
# Scoped by: access token
throttle('req/token/1min', limit: 2000, period: 1.minute) do |request|
request.identifier
end
if ENV["MEMCACHIER_SERVERS"]
cache.store = Dalli::Client.new(
ENV["MEMCACHIER_SERVERS"].split(","),
username: ENV["MEMCACHIER_USERNAME"],
password: ENV["MEMCACHIER_PASSWORD"],
failover: true,
socket_timeout: 1.5,
socket_failure_delay: 0.2)
else
cache.store = ActiveSupport::Cache::MemoryStore.new
end
end