-
Notifications
You must be signed in to change notification settings - Fork 340
Description
Running Rack Attack in production systems sometimes requires debugging misbehaving rules. One of the common ways to find misbehaving application components in Ruby is by monkey-patching in tracing information (eg: https://github.com/DataDog/dd-trace-rb/blob/master/lib/datadog/tracing/contrib/). Tracing has the benefit of grouping calls to other components (eg: Redis, Valkey, PostgreSQL, MySQL, etc) in a causal way.
Refactoring Rack::Attack#call
to separate out the rule evaluation component would enable consumers that want to patch in tracing to do so. Depending on how folks conceive of the short-circuit code (and it belonging in the action
method) there could be a slight performance penalty if Rack Attack is not enabled for the class or has already been called (due to multiple invocations of path normalization, and allocation of Request objects).
I'm proposing (roughly):
module Rack
class Attack
def call(env)
# Could be moved into action, if small performance penalty is acceptable.
return @app.call(env) if !self.class.enabled || req["rack.attack.called"]
env["rack.attack.called"] = true
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
request = Rack::Attack::Request.new(env)
case action(request)
when Safe, Skip
@app.call(env)
when Blocked
if configuration.blocklisted_response
configuration.blocklisted_response.call(env)
else
configuration.blocklisted_responder.call(request)
end
when Throttle
if configuration.throttled_response
configuration.throttled_response.call(env)
else
configuration.throttled_responder.call(request)
end
else
configuration.tracked?(request)
@app.call(env)
end
end
# Determine which action to take for the given request.
def action(request)
return Skip if !self.class.enabled || req["rack.attack.called"]
return Safe if configuration.safelisted?(req)
return Blocked if configuration.blocklisted?(req)
return Throttle if configuration.throttled?(req)
nil
end
end
end
This allows an application to use prepends to patch the action method:
module RackAttackTracing
def action(req)
TracingLibrary.trace("rack.attack") do
super
end
end
end
::Rack::Attack.send(:prepend, RackAttackTracing)