summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Kozono <mkozono@gmail.com>2017-09-15 10:31:32 -0700
committerFrancisco Lopez <fjlopez@gitlab.com>2017-11-17 09:58:18 +0100
commitdc9266fbeacd24446b52e4dad328c8286be40b31 (patch)
treef882ee2eac622aa6daafa71d80663d6a5014d3b9
parent732b122644bf56729996b3cc239453f537a798f4 (diff)
downloadgitlab-ce-dc9266fbeacd24446b52e4dad328c8286be40b31.tar.gz
Add request throttles
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/helpers/application_settings_helper.rb9
-rw-r--r--app/views/admin/application_settings/_form.html.haml51
-rw-r--r--changelogs/unreleased/mk-add-user-rate-limits.yml6
-rw-r--r--config/application.rb2
-rw-r--r--config/initializers/rack_attack_global.rb73
-rw-r--r--lib/gitlab/auth.rb30
7 files changed, 180 insertions, 3 deletions
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 3be7aee69bc..42eae408fdc 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,8 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- before_action :authenticate_user_from_personal_access_token!
- before_action :authenticate_user_from_rss_token!
+ before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :check_password_expiration
@@ -100,6 +99,7 @@ class ApplicationController < ActionController::Base
return try(:authenticated_user)
end
+<<<<<<< HEAD
def authenticate_user_from_personal_access_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
@@ -121,6 +121,14 @@ class ApplicationController < ActionController::Base
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
+=======
+ # This filter handles private tokens, personal access tokens, and atom
+ # requests with rss tokens
+ def authenticate_sessionless_user!
+ user = Gitlab::Auth.find_sessionless_user(request)
+
+ sessionless_sign_in(user) if user
+>>>>>>> Add request throttles
end
def log_exception(exception)
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index cd1ecaadb85..e5d2693b01e 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -231,6 +231,15 @@ module ApplicationSettingsHelper
:sign_in_text,
:signup_enabled,
:terminal_max_session_time,
+ :throttle_unauthenticated_enabled,
+ :throttle_unauthenticated_requests_per_period,
+ :throttle_unauthenticated_period_in_seconds,
+ :throttle_authenticated_web_enabled,
+ :throttle_authenticated_web_requests_per_period,
+ :throttle_authenticated_web_period_in_seconds,
+ :throttle_authenticated_api_enabled,
+ :throttle_authenticated_api_requests_per_period,
+ :throttle_authenticated_api_period_in_seconds,
:two_factor_grace_period,
:unique_ips_limit_enabled,
:unique_ips_limit_per_user,
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 3a4d5ce0b5c..12658dddc06 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -743,5 +743,56 @@
installations. Set to 0 to completely disable polling.
= link_to icon('question-circle'), help_page_path('administration/polling')
+ %fieldset
+ %legend User and IP Rate Limits
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :throttle_unauthenticated_enabled do
+ = f.check_box :throttle_unauthenticated_enabled
+ Enable unauthenticated request rate limit
+ %span.help-block
+ Helps reduce request volume (e.g. from crawlers or abusive bots)
+ .form-group
+ = f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control'
+ .form-group
+ = f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :throttle_authenticated_api_enabled do
+ = f.check_box :throttle_authenticated_api_enabled
+ Enable authenticated API request rate limit
+ %span.help-block
+ Helps reduce request volume (e.g. from crawlers or abusive bots)
+ .form-group
+ = f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control'
+ .form-group
+ = f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :throttle_authenticated_web_enabled do
+ = f.check_box :throttle_authenticated_web_enabled
+ Enable authenticated web request rate limit
+ %span.help-block
+ Helps reduce request volume (e.g. from crawlers or abusive bots)
+ .form-group
+ = f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control'
+ .form-group
+ = f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
+
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/changelogs/unreleased/mk-add-user-rate-limits.yml b/changelogs/unreleased/mk-add-user-rate-limits.yml
new file mode 100644
index 00000000000..512757da5fc
--- /dev/null
+++ b/changelogs/unreleased/mk-add-user-rate-limits.yml
@@ -0,0 +1,6 @@
+---
+title: Add anonymous rate limit per IP, and authenticated (web or API) rate limits
+ per user
+merge_request: 14708
+author:
+type: added
diff --git a/config/application.rb b/config/application.rb
index 5100ec5d2b7..6436f887d14 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -113,7 +113,7 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
- config.middleware.insert_before Warden::Manager, Rack::Attack
+ config.middleware.insert_after Warden::Manager, Rack::Attack
# Allow access to GitLab API from other domains
config.middleware.insert_before Warden::Manager, Rack::Cors do
diff --git a/config/initializers/rack_attack_global.rb b/config/initializers/rack_attack_global.rb
new file mode 100644
index 00000000000..0b51fadbd02
--- /dev/null
+++ b/config/initializers/rack_attack_global.rb
@@ -0,0 +1,73 @@
+class Rack::Attack
+ def self.settings
+ Gitlab::CurrentSettings.current_application_settings
+ end
+
+ def self.throttle_unauthenticated_options
+ limit_proc = proc { |req| settings.throttle_unauthenticated_requests_per_period }
+ period_proc = proc { |req| settings.throttle_unauthenticated_period_in_seconds.seconds }
+ { limit: limit_proc, period: period_proc }
+ end
+
+ def self.throttle_authenticated_api_options
+ limit_proc = proc { |req| settings.throttle_authenticated_api_requests_per_period }
+ period_proc = proc { |req| settings.throttle_authenticated_api_period_in_seconds.seconds }
+ { limit: limit_proc, period: period_proc }
+ end
+
+ def self.throttle_authenticated_web_options
+ limit_proc = proc { |req| settings.throttle_authenticated_web_requests_per_period }
+ period_proc = proc { |req| settings.throttle_authenticated_web_period_in_seconds.seconds }
+ { limit: limit_proc, period: period_proc }
+ end
+
+ def self.define_throttles
+ throttle('throttle_unauthenticated', throttle_unauthenticated_options) do |req|
+ settings.throttle_unauthenticated_enabled &&
+ req.unauthenticated? &&
+ req.ip
+ end
+
+ throttle('throttle_authenticated_api', throttle_authenticated_api_options) do |req|
+ settings.throttle_authenticated_api_enabled &&
+ req.api_request? &&
+ req.authenticated_user_id
+ end
+
+ throttle('throttle_authenticated_web', throttle_authenticated_web_options) do |req|
+ settings.throttle_authenticated_web_enabled &&
+ req.web_request? &&
+ req.authenticated_user_id
+ end
+ end
+
+ define_throttles unless Rails.env.test?
+
+ class Request
+ def unauthenticated?
+ !authenticated_user_id
+ end
+
+ def authenticated_user_id
+ session_user_id || sessionless_user_id
+ end
+
+ def api_request?
+ path.start_with?('/api')
+ end
+
+ def web_request?
+ !api_request?
+ end
+
+ private
+
+ def session_user_id
+ Gitlab::Auth.find_session_user(self)&.id
+ end
+
+ def sessionless_user_id
+ Gitlab::Auth.find_sessionless_user(self)&.id
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index cbbc51db99e..8f39885c608 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -82,6 +82,36 @@ module Gitlab
end
end
+ # request may be Rack::Attack::Request which is just a Rack::Request, so
+ # we cannot use ActionDispatch::Request methods.
+ def find_user_by_private_token(request)
+ token = request.params['private_token'].presence || request.env['HTTP_PRIVATE_TOKEN'].presence
+
+ return unless token.present?
+
+ User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
+ end
+
+ # request may be Rack::Attack::Request which is just a Rack::Request, so
+ # we cannot use ActionDispatch::Request methods.
+ def find_user_by_rss_token(request)
+ return unless request.params['format'] == 'atom'
+
+ token = request.params['rss_token'].presence
+
+ return unless token.present?
+
+ User.find_by_rss_token(token)
+ end
+
+ def find_session_user(request)
+ request.env['warden']&.authenticate
+ end
+
+ def find_sessionless_user(request)
+ find_user_by_private_token(request) || find_user_by_rss_token(request)
+ end
+
private
def service_request_check(login, password, project)