From 9c15124fdb59b50fd210837d803dba3ed1723284 Mon Sep 17 00:00:00 2001
From: Magdalena Frankiewicz <mfrankiewicz@gitlab.com>
Date: Sun, 9 Jan 2022 23:09:51 +0100
Subject: [PATCH] Rate limit /users/sign_up

This is to mitigate abuse, such us attempts to discover usernames
or emails

Changelog: security
---
 app/controllers/registrations_controller.rb      |  3 +++
 .../rate_limit_user_sign_up_endpoint.yml         |  8 ++++++++
 lib/gitlab/application_rate_limiter.rb           |  1 +
 .../controllers/registrations_controller_spec.rb | 16 ++++++++++++++++
 4 files changed, 28 insertions(+)
 create mode 100644 config/feature_flags/development/rate_limit_user_sign_up_endpoint.yml

diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index bd7631c7e78d..79c4f9a02601 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -13,6 +13,9 @@ class RegistrationsController < Devise::RegistrationsController
   before_action :ensure_destroy_prerequisites_met, only: [:destroy]
   before_action :load_recaptcha, only: :new
   before_action :set_invite_params, only: :new
+  before_action only: [:create] do
+    check_rate_limit!(:user_sign_up, scope: request.ip) if Feature.enabled?(:rate_limit_user_sign_up_endpoint, default_enabled: :yaml)
+  end
 
   feature_category :authentication_and_authorization
 
diff --git a/config/feature_flags/development/rate_limit_user_sign_up_endpoint.yml b/config/feature_flags/development/rate_limit_user_sign_up_endpoint.yml
new file mode 100644
index 000000000000..af1957e54c87
--- /dev/null
+++ b/config/feature_flags/development/rate_limit_user_sign_up_endpoint.yml
@@ -0,0 +1,8 @@
+---
+name: rate_limit_user_sign_up_endpoint
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77835
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349843
+milestone: '14.7'
+type: development
+group: group::optimize
+default_enabled: false
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index dac1f686da70..b90f1f4da0dd 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -51,6 +51,7 @@ def rate_limits
           web_hook_calls:               { interval: 1.minute },
           users_get_by_id:              { threshold: 10, interval: 1.minute },
           username_exists:              { threshold: 20, interval: 1.minute },
+          user_sign_up:                 { threshold: 20, interval: 1.minute },
           profile_resend_email_confirmation:  { threshold: 5, interval: 1.minute },
           profile_update_username:            { threshold: 10, interval: 1.minute },
           update_environment_canary_ingress:  { threshold: 1, interval: 1.minute },
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 889401e78f87..d5fe32ac0943 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -20,6 +20,10 @@
   end
 
   describe '#create' do
+    before do
+      allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
+    end
+
     let_it_be(:base_user_params) do
       { first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: 'Any_password' }
     end
@@ -410,6 +414,18 @@
       end
     end
 
+    context 'when the rate limit has been reached' do
+      it 'returns status 429 Too Many Requests', :aggregate_failures do
+        ip = '1.2.3.4'
+        expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:user_sign_up, scope: ip).and_return(true)
+
+        controller.request.env['REMOTE_ADDR'] = ip
+        post(:create, params: user_params, session: session_params)
+
+        expect(response).to have_gitlab_http_status(:too_many_requests)
+      end
+    end
+
     it "logs a 'User Created' message" do
       expect(Gitlab::AppLogger).to receive(:info).with(/\AUser Created: username=new_username email=new@user.com.+\z/).and_call_original
 
-- 
GitLab