diff --git a/changelogs/unreleased/22465-rack-attack-authenticate-runner-requests-with-job-token-basic-auth.yml b/changelogs/unreleased/22465-rack-attack-authenticate-runner-requests-with-job-token-basic-auth.yml new file mode 100644 index 0000000000000000000000000000000000000000..06f618bd29be0400eccf79270c1d7a6070c791e3 --- /dev/null +++ b/changelogs/unreleased/22465-rack-attack-authenticate-runner-requests-with-job-token-basic-auth.yml @@ -0,0 +1,5 @@ +--- +title: Authenticate requests with job token as basic auth header for request limiting +merge_request: 21562 +author: +type: fixed diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index 6210aca739a66753dfc31af43c341163b6e81722..33cbb070c2f28370e144c030d9dda116db1b0547 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -21,6 +21,7 @@ module AuthFinders prepend_if_ee('::EE::Gitlab::Auth::AuthFinders') # rubocop: disable Cop/InjectEnterpriseEditionModule include Gitlab::Utils::StrongMemoize + include ActionController::HttpAuthentication::Basic PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN' PRIVATE_TOKEN_PARAM = :private_token @@ -67,6 +68,19 @@ def find_user_from_job_token job.user end + def find_user_from_basic_auth_job + return unless has_basic_credentials?(current_request) + + login, password = user_name_and_password(current_request) + return unless login.present? && password.present? + return unless ::Ci::Build::CI_REGISTRY_USER == login + + job = ::Ci::Build.find_by_token(password) + raise UnauthorizedError unless job + + job.user + end + # We only allow Private Access Tokens with `api` scope to be used by web # requests on RSS feeds or ICS files for backwards compatibility. # It is also used by GraphQL/API requests. diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb index 9b1b7b8e879f9be82be110ada5f96f7c102df12e..34ccff588f402f0037ddac023e9adf9c8bc37f67 100644 --- a/lib/gitlab/auth/request_authenticator.rb +++ b/lib/gitlab/auth/request_authenticator.rb @@ -32,7 +32,8 @@ def runner def find_sessionless_user(request_format) find_user_from_web_access_token(request_format) || find_user_from_feed_token(request_format) || - find_user_from_static_object_token(request_format) + find_user_from_static_object_token(request_format) || + find_user_from_basic_auth_job rescue Gitlab::Auth::AuthenticationError nil end diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 3d10f4113102b54e459f03298b242d0df5b23a74..82ff8e7f76cd4b96a576c1877983aae932020c28 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -335,6 +335,72 @@ def set_param(key, value) end end + describe '#find_user_from_basic_auth_job' do + def basic_http_auth(username, password) + ActionController::HttpAuthentication::Basic.encode_credentials(username, password) + end + + def set_auth(username, password) + env['HTTP_AUTHORIZATION'] = basic_http_auth(username, password) + end + + subject { find_user_from_basic_auth_job } + + context 'when the request does not have AUTHORIZATION header' do + it { is_expected.to be_nil } + end + + context 'with wrong credentials' do + it 'returns nil without user and password' do + set_auth(nil, nil) + + is_expected.to be_nil + end + + it 'returns nil without password' do + set_auth('some-user', nil) + + is_expected.to be_nil + end + + it 'returns nil without user' do + set_auth(nil, 'password') + + is_expected.to be_nil + end + + it 'returns nil without CI username' do + set_auth('user', 'password') + + is_expected.to be_nil + end + end + + context 'with CI username' do + let(:username) { ::Ci::Build::CI_REGISTRY_USER } + let(:user) { create(:user) } + let(:build) { create(:ci_build, user: user) } + + it 'returns nil without password' do + set_auth(username, nil) + + is_expected.to be_nil + end + + it 'returns user with valid token' do + set_auth(username, build.token) + + is_expected.to eq user + end + + it 'raises error with invalid token' do + set_auth(username, 'token') + + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + end + end + describe '#validate_access_token!' do let(:personal_access_token) { create(:personal_access_token, user: user) }