diff options
Diffstat (limited to 'lib/gitlab/metrics/subscribers/rack_attack.rb')
-rw-r--r-- | lib/gitlab/metrics/subscribers/rack_attack.rb | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/lib/gitlab/metrics/subscribers/rack_attack.rb b/lib/gitlab/metrics/subscribers/rack_attack.rb new file mode 100644 index 00000000000..2791a39fb16 --- /dev/null +++ b/lib/gitlab/metrics/subscribers/rack_attack.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module Gitlab + module Metrics + module Subscribers + # - Adds logging for all Rack Attack blocks and throttling events. + # - Instrument the cache operations of RackAttack to use in structured + # logs. Two fields are exposed: + # + rack_attack_redis_count: the number of redis calls triggered by + # RackAttack in a request. + # + rack_attack_redis_duration_s: the total duration of all redis calls + # triggered by RackAttack in a request. + class RackAttack < ActiveSupport::Subscriber + attach_to 'rack_attack' + + INSTRUMENTATION_STORE_KEY = :rack_attack_instrumentation + + THROTTLES_WITH_USER_INFORMATION = [ + :throttle_authenticated_api, + :throttle_authenticated_web, + :throttle_authenticated_protected_paths_api, + :throttle_authenticated_protected_paths_web + ].freeze + + PAYLOAD_KEYS = [ + :rack_attack_redis_count, + :rack_attack_redis_duration_s + ].freeze + + def self.payload + Gitlab::SafeRequestStore[INSTRUMENTATION_STORE_KEY] ||= { + rack_attack_redis_count: 0, + rack_attack_redis_duration_s: 0.0 + } + end + + def redis(event) + self.class.payload[:rack_attack_redis_count] += 1 + self.class.payload[:rack_attack_redis_duration_s] += event.duration.to_f / 1000 + end + + def safelist(event) + req = event.payload[:request] + Gitlab::Instrumentation::Throttle.safelist = req.env['rack.attack.matched'] + end + + def throttle(event) + log_into_auth_logger(event) + end + + def blocklist(event) + log_into_auth_logger(event) + end + + def track(event) + log_into_auth_logger(event) + end + + private + + def log_into_auth_logger(event) + req = event.payload[:request] + rack_attack_info = { + message: 'Rack_Attack', + env: req.env['rack.attack.match_type'], + remote_ip: req.ip, + request_method: req.request_method, + path: req.fullpath, + matched: req.env['rack.attack.matched'] + } + + if THROTTLES_WITH_USER_INFORMATION.include? req.env['rack.attack.matched'].to_sym + user_id = req.env['rack.attack.match_discriminator'] + user = User.find_by(id: user_id) # rubocop:disable CodeReuse/ActiveRecord + + rack_attack_info[:user_id] = user_id + rack_attack_info['meta.user'] = user.username unless user.nil? + end + + Gitlab::InstrumentationHelper.add_instrumentation_data(rack_attack_info) + + logger.error(rack_attack_info) + end + + def logger + Gitlab::AuthLogger + end + end + end + end +end |