Commit c4b114cd authored by Michael Kozono's avatar Michael Kozono

Merge branch 'sh-fix-protected-paths' into 'master'

Only enable protected paths for POST requests

See merge request gitlab-org/gitlab!19184
parents 4d57d18d 254c1e24
---
title: Only enable protected paths for POST requests
merge_request: 19184
author:
type: fixed
...@@ -73,7 +73,8 @@ class Rack::Attack ...@@ -73,7 +73,8 @@ class Rack::Attack
end end
throttle('throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req| throttle('throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req|
if !req.should_be_skipped? && if req.post? &&
!req.should_be_skipped? &&
req.protected_path? && req.protected_path? &&
Gitlab::Throttle.protected_paths_enabled? && Gitlab::Throttle.protected_paths_enabled? &&
req.unauthenticated? req.unauthenticated?
...@@ -82,17 +83,19 @@ class Rack::Attack ...@@ -82,17 +83,19 @@ class Rack::Attack
end end
throttle('throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req| throttle('throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
if req.api_request? && if req.post? &&
Gitlab::Throttle.protected_paths_enabled? && req.api_request? &&
req.protected_path? req.protected_path? &&
Gitlab::Throttle.protected_paths_enabled?
req.authenticated_user_id([:api]) req.authenticated_user_id([:api])
end end
end end
throttle('throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req| throttle('throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
if req.web_request? && if req.post? &&
Gitlab::Throttle.protected_paths_enabled? && req.web_request? &&
req.protected_path? req.protected_path? &&
Gitlab::Throttle.protected_paths_enabled?
req.authenticated_user_id([:api, :rss, :ics]) req.authenticated_user_id([:api, :rss, :ics])
end end
end end
......
...@@ -22,6 +22,7 @@ describe 'Rack Attack global throttles' do ...@@ -22,6 +22,7 @@ describe 'Rack Attack global throttles' do
} }
end end
let(:request_method) { 'GET' }
let(:requests_per_period) { 1 } let(:requests_per_period) { 1 }
let(:period_in_seconds) { 10000 } let(:period_in_seconds) { 10000 }
let(:period) { period_in_seconds.seconds } let(:period) { period_in_seconds.seconds }
...@@ -143,15 +144,15 @@ describe 'Rack Attack global throttles' do ...@@ -143,15 +144,15 @@ describe 'Rack Attack global throttles' do
let(:api_partial_url) { '/todos' } let(:api_partial_url) { '/todos' }
context 'with the token in the query string' do context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] } let(:request_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] } let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
context 'with the token in the headers' do context 'with the token in the headers' do
let(:get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) } let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) } let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
...@@ -170,15 +171,15 @@ describe 'Rack Attack global throttles' do ...@@ -170,15 +171,15 @@ describe 'Rack Attack global throttles' do
let(:api_partial_url) { '/todos' } let(:api_partial_url) { '/todos' }
context 'with the token in the query string' do context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, oauth_access_token: token)] } let(:request_args) { [api(api_partial_url, oauth_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, oauth_access_token: other_user_token)] } let(:other_user_request_args) { [api(api_partial_url, oauth_access_token: other_user_token)] }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
context 'with the token in the headers' do context 'with the token in the headers' do
let(:get_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) } let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) } let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
...@@ -190,8 +191,8 @@ describe 'Rack Attack global throttles' do ...@@ -190,8 +191,8 @@ describe 'Rack Attack global throttles' do
let(:throttle_setting_prefix) { 'throttle_authenticated_web' } let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
context 'with the token in the query string' do context 'with the token in the query string' do
let(:get_args) { [rss_url(user), params: nil] } let(:request_args) { [rss_url(user), params: nil] }
let(:other_user_get_args) { [rss_url(other_user), params: nil] } let(:other_user_request_args) { [rss_url(other_user), params: nil] }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
...@@ -206,10 +207,13 @@ describe 'Rack Attack global throttles' do ...@@ -206,10 +207,13 @@ describe 'Rack Attack global throttles' do
end end
describe 'protected paths' do describe 'protected paths' do
let(:request_method) { 'POST' }
context 'unauthenticated requests' do context 'unauthenticated requests' do
let(:protected_path_that_does_not_require_authentication) do let(:protected_path_that_does_not_require_authentication) do
'/users/confirmation' '/users/sign_in'
end end
let(:post_params) { { user: { login: 'username', password: 'password' } } }
before do before do
settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1 settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1
...@@ -224,7 +228,7 @@ describe 'Rack Attack global throttles' do ...@@ -224,7 +228,7 @@ describe 'Rack Attack global throttles' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get protected_path_that_does_not_require_authentication post protected_path_that_does_not_require_authentication, params: post_params
expect(response).to have_http_status 200 expect(response).to have_http_status 200
end end
end end
...@@ -238,11 +242,11 @@ describe 'Rack Attack global throttles' do ...@@ -238,11 +242,11 @@ describe 'Rack Attack global throttles' do
it 'rejects requests over the rate limit' do it 'rejects requests over the rate limit' do
requests_per_period.times do requests_per_period.times do
get protected_path_that_does_not_require_authentication post protected_path_that_does_not_require_authentication, params: post_params
expect(response).to have_http_status 200 expect(response).to have_http_status 200
end end
expect_rejection { get protected_path_that_does_not_require_authentication } expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params }
end end
context 'when Omnibus throttle is present' do context 'when Omnibus throttle is present' do
...@@ -253,7 +257,7 @@ describe 'Rack Attack global throttles' do ...@@ -253,7 +257,7 @@ describe 'Rack Attack global throttles' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get protected_path_that_does_not_require_authentication post protected_path_that_does_not_require_authentication, params: post_params
expect(response).to have_http_status 200 expect(response).to have_http_status 200
end end
end end
...@@ -267,11 +271,11 @@ describe 'Rack Attack global throttles' do ...@@ -267,11 +271,11 @@ describe 'Rack Attack global throttles' do
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
let(:other_user_token) { create(:personal_access_token, user: other_user) } let(:other_user_token) { create(:personal_access_token, user: other_user) }
let(:throttle_setting_prefix) { 'throttle_protected_paths' } let(:throttle_setting_prefix) { 'throttle_protected_paths' }
let(:api_partial_url) { '/users' } let(:api_partial_url) { '/user/emails' }
let(:protected_paths) do let(:protected_paths) do
[ [
'/api/v4/users' '/api/v4/user/emails'
] ]
end end
...@@ -281,22 +285,22 @@ describe 'Rack Attack global throttles' do ...@@ -281,22 +285,22 @@ describe 'Rack Attack global throttles' do
end end
context 'with the token in the query string' do context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] } let(:request_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] } let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
context 'with the token in the headers' do context 'with the token in the headers' do
let(:get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) } let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) } let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
it_behaves_like 'rate-limited token-authenticated requests' it_behaves_like 'rate-limited token-authenticated requests'
end end
context 'when Omnibus throttle is present' do context 'when Omnibus throttle is present' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] } let(:request_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] } let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
before do before do
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
...@@ -310,8 +314,8 @@ describe 'Rack Attack global throttles' do ...@@ -310,8 +314,8 @@ describe 'Rack Attack global throttles' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get(*get_args) post(*request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
end end
end end
...@@ -320,7 +324,7 @@ describe 'Rack Attack global throttles' do ...@@ -320,7 +324,7 @@ describe 'Rack Attack global throttles' do
describe 'web requests authenticated with regular login' do describe 'web requests authenticated with regular login' do
let(:throttle_setting_prefix) { 'throttle_protected_paths' } let(:throttle_setting_prefix) { 'throttle_protected_paths' }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:url_that_requires_authentication) { '/dashboard/snippets' } let(:url_that_requires_authentication) { '/users/confirmation' }
let(:protected_paths) do let(:protected_paths) do
[ [
...@@ -350,8 +354,8 @@ describe 'Rack Attack global throttles' do ...@@ -350,8 +354,8 @@ describe 'Rack Attack global throttles' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get url_that_requires_authentication post url_that_requires_authentication
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
end end
end end
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
# #
# Requires let variables: # Requires let variables:
# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths" # * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
# * get_args # * request_method
# * other_user_get_args # * request_args
# * other_user_request_args
# * requests_per_period # * requests_per_period
# * period_in_seconds # * period_in_seconds
# * period # * period
...@@ -31,66 +32,66 @@ shared_examples_for 'rate-limited token-authenticated requests' do ...@@ -31,66 +32,66 @@ shared_examples_for 'rate-limited token-authenticated requests' do
it 'rejects requests over the rate limit' do it 'rejects requests over the rate limit' do
# At first, allow requests under the rate limit. # At first, allow requests under the rate limit.
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
# the last straw # the last straw
expect_rejection { get(*get_args) } expect_rejection { make_request(request_args) }
end end
it 'allows requests after throttling and then waiting for the next period' do it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_rejection { get(*get_args) } expect_rejection { make_request(request_args) }
Timecop.travel(period.from_now) do Timecop.travel(period.from_now) do
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_rejection { get(*get_args) } expect_rejection { make_request(request_args) }
end end
end end
it 'counts requests from different users separately, even from the same IP' do it 'counts requests from different users separately, even from the same IP' do
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
# would be over the limit if this wasn't a different user # would be over the limit if this wasn't a different user
get(*other_user_get_args) make_request(other_user_request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
it 'counts all requests from the same user, even via different IPs' do it 'counts all requests from the same user, even via different IPs' do
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4') expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
expect_rejection { get(*get_args) } expect_rejection { make_request(request_args) }
end end
it 'logs RackAttack info into structured logs' do it 'logs RackAttack info into structured logs' do
requests_per_period.times do requests_per_period.times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
arguments = { arguments = {
message: 'Rack_Attack', message: 'Rack_Attack',
env: :throttle, env: :throttle,
remote_ip: '127.0.0.1', remote_ip: '127.0.0.1',
request_method: 'GET', request_method: request_method,
path: get_args.first, path: request_args.first,
user_id: user.id, user_id: user.id,
username: user.username, username: user.username,
throttle_type: throttle_types[throttle_setting_prefix] throttle_type: throttle_types[throttle_setting_prefix]
...@@ -98,7 +99,7 @@ shared_examples_for 'rate-limited token-authenticated requests' do ...@@ -98,7 +99,7 @@ shared_examples_for 'rate-limited token-authenticated requests' do
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
expect_rejection { get(*get_args) } expect_rejection { make_request(request_args) }
end end
end end
...@@ -110,17 +111,26 @@ shared_examples_for 'rate-limited token-authenticated requests' do ...@@ -110,17 +111,26 @@ shared_examples_for 'rate-limited token-authenticated requests' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get(*get_args) make_request(request_args)
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
end end
end end
def make_request(args)
if request_method == 'POST'
post(*args)
else
get(*args)
end
end
end end
# Requires let variables: # Requires let variables:
# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths" # * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
# * user # * user
# * url_that_requires_authentication # * url_that_requires_authentication
# * request_method
# * requests_per_period # * requests_per_period
# * period_in_seconds # * period_in_seconds
# * period # * period
...@@ -149,68 +159,68 @@ shared_examples_for 'rate-limited web authenticated requests' do ...@@ -149,68 +159,68 @@ shared_examples_for 'rate-limited web authenticated requests' do
it 'rejects requests over the rate limit' do it 'rejects requests over the rate limit' do
# At first, allow requests under the rate limit. # At first, allow requests under the rate limit.
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
# the last straw # the last straw
expect_rejection { get url_that_requires_authentication } expect_rejection { request_authenticated_web_url }
end end
it 'allows requests after throttling and then waiting for the next period' do it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_rejection { get url_that_requires_authentication } expect_rejection { request_authenticated_web_url }
Timecop.travel(period.from_now) do Timecop.travel(period.from_now) do
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_rejection { get url_that_requires_authentication } expect_rejection { request_authenticated_web_url }
end end
end end
it 'counts requests from different users separately, even from the same IP' do it 'counts requests from different users separately, even from the same IP' do
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
# would be over the limit if this wasn't a different user # would be over the limit if this wasn't a different user
login_as(create(:user)) login_as(create(:user))
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
it 'counts all requests from the same user, even via different IPs' do it 'counts all requests from the same user, even via different IPs' do
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4') expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
expect_rejection { get url_that_requires_authentication } expect_rejection { request_authenticated_web_url }
end end
it 'logs RackAttack info into structured logs' do it 'logs RackAttack info into structured logs' do
requests_per_period.times do requests_per_period.times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
arguments = { arguments = {
message: 'Rack_Attack', message: 'Rack_Attack',
env: :throttle, env: :throttle,
remote_ip: '127.0.0.1', remote_ip: '127.0.0.1',
request_method: 'GET', request_method: request_method,
path: '/dashboard/snippets', path: url_that_requires_authentication,
user_id: user.id, user_id: user.id,
username: user.username, username: user.username,
throttle_type: throttle_types[throttle_setting_prefix] throttle_type: throttle_types[throttle_setting_prefix]
...@@ -218,7 +228,7 @@ shared_examples_for 'rate-limited web authenticated requests' do ...@@ -218,7 +228,7 @@ shared_examples_for 'rate-limited web authenticated requests' do
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
get url_that_requires_authentication request_authenticated_web_url
end end
end end
...@@ -230,9 +240,17 @@ shared_examples_for 'rate-limited web authenticated requests' do ...@@ -230,9 +240,17 @@ shared_examples_for 'rate-limited web authenticated requests' do
it 'allows requests over the rate limit' do it 'allows requests over the rate limit' do
(1 + requests_per_period).times do (1 + requests_per_period).times do
get url_that_requires_authentication request_authenticated_web_url
expect(response).to have_http_status 200 expect(response).not_to have_http_status 429
end end
end end
end end
def request_authenticated_web_url
if request_method == 'POST'
post url_that_requires_authentication
else
get url_that_requires_authentication
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