Commit 5bc8987a authored by Sean McGivern's avatar Sean McGivern

Always use rate limiting Redis

We have deployed this change to GitLab.com and so we no longer need to
switch based on a feature flag or environment variable.
Gitlab::Redis::RateLimiting falls back to Gitlab::Redis::Cache when not
configured, which means that existing self-managed instances will
continue to work correctly.

Changelog: other
parent 21133fee
---
name: use_rate_limiting_store_for_application_rate_limiter
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71196
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
milestone: '14.4'
type: development
group: group::scalability
default_enabled: false
...@@ -73,7 +73,7 @@ module Gitlab ...@@ -73,7 +73,7 @@ module Gitlab
value = 0 value = 0
interval_value = interval || interval(key) interval_value = interval || interval(key)
cache_store.with do |redis| ::Gitlab::Redis::RateLimiting.with do |redis|
cache_key = action_key(key, scope) cache_key = action_key(key, scope)
value = redis.incr(cache_key) value = redis.incr(cache_key)
redis.expire(cache_key, interval_value) if value == 1 redis.expire(cache_key, interval_value) if value == 1
...@@ -109,14 +109,6 @@ module Gitlab ...@@ -109,14 +109,6 @@ module Gitlab
private private
def cache_store
if ::Feature.enabled?(:use_rate_limiting_store_for_application_rate_limiter, default_enabled: :yaml)
::Gitlab::Redis::RateLimiting
else
::Gitlab::Redis::Cache
end
end
def threshold(key) def threshold(key)
value = rate_limit_value_by_key(key, :threshold) value = rate_limit_value_by_key(key, :threshold)
......
...@@ -15,18 +15,7 @@ module Gitlab ...@@ -15,18 +15,7 @@ module Gitlab
delegate :silence!, :mute, to: :@upstream_store delegate :silence!, :mute, to: :@upstream_store
# Clean up in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249 def initialize(upstream_store: ::Gitlab::Redis::RateLimiting.cache_store, notifier: ActiveSupport::Notifications)
def self.store
if ENV['USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK'] == '1'
Gitlab::AuthLogger.info(message: 'Rack::Attack using rate limiting store')
::Gitlab::Redis::RateLimiting.cache_store
else
Gitlab::AuthLogger.info(message: 'Rack::Attack using cache store')
::Rails.cache
end
end
def initialize(upstream_store: self.class.store, notifier: ActiveSupport::Notifications)
@upstream_store = upstream_store @upstream_store = upstream_store
@notifier = notifier @notifier = notifier
end end
......
...@@ -20,6 +20,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do ...@@ -20,6 +20,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
subject { described_class } subject { described_class }
before do before do
allow(Gitlab::Redis::RateLimiting).to receive(:with).and_yield(redis)
allow(described_class).to receive(:rate_limits).and_return(rate_limits) allow(described_class).to receive(:rate_limits).and_return(rate_limits)
end end
...@@ -48,94 +49,73 @@ RSpec.describe Gitlab::ApplicationRateLimiter do ...@@ -48,94 +49,73 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
end end
end end
# Clean up in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249 context 'when the key is an array of only ActiveRecord models' do
shared_examples 'rate limiting' do let(:scope) { [user, project] }
context 'when the key is an array of only ActiveRecord models' do
let(:scope) { [user, project] }
let(:cache_key) do let(:cache_key) do
"application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" "application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
end
it_behaves_like 'action rate limiter'
end end
context 'when they key a combination of ActiveRecord models and strings' do it_behaves_like 'action rate limiter'
let(:project) { create(:project, :public, :repository) } end
let(:commit) { project.repository.commit }
let(:path) { 'app/controllers/groups_controller.rb' }
let(:scope) { [project, commit, path] }
let(:cache_key) do context 'when they key a combination of ActiveRecord models and strings' do
"application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}" let(:project) { create(:project, :public, :repository) }
end let(:commit) { project.repository.commit }
let(:path) { 'app/controllers/groups_controller.rb' }
let(:scope) { [project, commit, path] }
it_behaves_like 'action rate limiter' let(:cache_key) do
"application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
end end
describe '#log_request' do it_behaves_like 'action rate limiter'
let(:file_path) { 'master/README.md' } end
let(:type) { :raw_blob_request_limit }
let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
let(:request) do
double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
end
let(:base_attributes) do
{
message: 'Application_Rate_Limiter_Request',
env: type,
remote_ip: '127.0.0.1',
request_method: 'GET',
path: fullpath
}
end
context 'without a current user' do
let(:current_user) { nil }
it 'logs information to auth.log' do describe '#log_request' do
expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once let(:file_path) { 'master/README.md' }
let(:type) { :raw_blob_request_limit }
let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
subject.log_request(request, type, current_user) let(:request) do
end double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
end end
context 'with a current_user' do let(:base_attributes) do
let(:current_user) { create(:user) } {
message: 'Application_Rate_Limiter_Request',
env: type,
remote_ip: '127.0.0.1',
request_method: 'GET',
path: fullpath
}
end
let(:attributes) do context 'without a current user' do
base_attributes.merge({ let(:current_user) { nil }
user_id: current_user.id,
username: current_user.username
})
end
it 'logs information to auth.log' do it 'logs information to auth.log' do
expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once
subject.log_request(request, type, current_user) subject.log_request(request, type, current_user)
end
end end
end end
end
context 'when use_rate_limiting_store_for_application_rate_limiter is enabled' do context 'with a current_user' do
before do let(:current_user) { create(:user) }
stub_feature_flags(use_rate_limiting_store_for_application_rate_limiter: true)
allow(Gitlab::Redis::RateLimiting).to receive(:with).and_yield(redis)
end
it_behaves_like 'rate limiting' let(:attributes) do
end base_attributes.merge({
user_id: current_user.id,
username: current_user.username
})
end
context 'when use_rate_limiting_store_for_application_rate_limiter is disabled' do it 'logs information to auth.log' do
before do expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
stub_feature_flags(use_rate_limiting_store_for_application_rate_limiter: false)
allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
end
it_behaves_like 'rate limiting' subject.log_request(request, type, current_user)
end
end
end end
end end
...@@ -86,17 +86,4 @@ RSpec.describe Gitlab::RackAttack::InstrumentedCacheStore do ...@@ -86,17 +86,4 @@ RSpec.describe Gitlab::RackAttack::InstrumentedCacheStore do
test_proc.call(subject) test_proc.call(subject)
end end
end end
# Remove in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
describe '.store' do
it 'uses the rate limiting store when USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK is set' do
stub_env('USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK', '1')
expect(described_class.store).to eq(Gitlab::Redis::RateLimiting.cache_store)
end
it 'uses the cache store' do
expect(described_class.store).to eq(Rails.cache)
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment