summaryrefslogtreecommitdiff
path: root/lib/gitlab/rack_attack.rb
blob: 7c336153e3286e3868a23354f461828ea92f0052 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# frozen_string_literal: true

# When adding new user-configurable throttles, remember to update the documentation
# in doc/user/admin_area/settings/user_and_ip_rate_limits.md
#
# Integration specs for throttling can be found in:
# spec/requests/rack_attack_global_spec.rb
module Gitlab
  module RackAttack
    def self.configure(rack_attack)
      # This adds some methods used by our throttles to the `Rack::Request`
      rack_attack::Request.include(Gitlab::RackAttack::Request)
      # Send the Retry-After header so clients (e.g. python-gitlab) can make good choices about delays
      Rack::Attack.throttled_response_retry_after_header = true
      # Configure the throttles
      configure_throttles(rack_attack)

      configure_user_allowlist
    end

    def self.configure_user_allowlist
      @user_allowlist = nil
      user_allowlist
    end

    def self.configure_throttles(rack_attack)
      throttle_or_track(rack_attack, 'throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req|
        if !req.should_be_skipped? &&
           Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
           req.unauthenticated?
          req.ip
        end
      end

      throttle_or_track(rack_attack, 'throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req|
        if req.api_request? &&
           Gitlab::Throttle.settings.throttle_authenticated_api_enabled
          req.throttled_user_id([:api])
        end
      end

      # Product analytics feature is in experimental stage.
      # At this point we want to limit amount of events registered
      # per application (aid stands for application id).
      throttle_or_track(rack_attack, 'throttle_product_analytics_collector', limit: 100, period: 60) do |req|
        if req.product_analytics_collector_request?
          req.params['aid']
        end
      end

      throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
        if req.web_request? &&
           Gitlab::Throttle.settings.throttle_authenticated_web_enabled
          req.throttled_user_id([:api, :rss, :ics])
        end
      end

      throttle_or_track(rack_attack, 'throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req|
        if req.post? &&
           !req.should_be_skipped? &&
           req.protected_path? &&
           Gitlab::Throttle.protected_paths_enabled? &&
           req.unauthenticated?
          req.ip
        end
      end

      throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
        if req.post? &&
           req.api_request? &&
           req.protected_path? &&
           Gitlab::Throttle.protected_paths_enabled?
          req.throttled_user_id([:api])
        end
      end

      throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
        if req.post? &&
           req.web_request? &&
           req.protected_path? &&
           Gitlab::Throttle.protected_paths_enabled?
          req.throttled_user_id([:api, :rss, :ics])
        end
      end

      rack_attack.safelist('throttle_bypass_header') do |req|
        Gitlab::Throttle.bypass_header.present? &&
          req.get_header(Gitlab::Throttle.bypass_header) == '1'
      end
    end

    def self.throttle_or_track(rack_attack, throttle_name, *args, &block)
      if track?(throttle_name)
        rack_attack.track(throttle_name, *args, &block)
      else
        rack_attack.throttle(throttle_name, *args, &block)
      end
    end

    def self.track?(name)
      dry_run_config = ENV['GITLAB_THROTTLE_DRY_RUN'].to_s.strip

      return false if dry_run_config.empty?
      return true if dry_run_config == '*'

      dry_run_config.split(',').map(&:strip).include?(name)
    end

    def self.user_allowlist
      @user_allowlist ||= begin
        list = UserAllowlist.new(ENV['GITLAB_THROTTLE_USER_ALLOWLIST'])
        Gitlab::AuthLogger.info(gitlab_throttle_user_allowlist: list.to_a)
        list
      end
    end
  end
end
::Gitlab::RackAttack.prepend_if_ee('::EE::Gitlab::RackAttack')