diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index f38d27ef7a4091584754ab7207ca194c1d597048..0c174b69834817b6b4049834bc042f13b02520d5 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -443,6 +443,12 @@ Audit event types belong to the following product categories. |:------------|:------------|:------------------|:---------|:--------------|:--------------| | [`skip_secret_push_protection`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147855) | Triggered when secret push protection is skipped by the user | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/441185) | Project | +### Secrets management + +| Name | Description | Saved to database | Streamed | Introduced in | Scope | +|:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`user_authenticated_using_job_token`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164546) | Triggered when a user is authenticated using job token | **{dotted-circle}** No | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/481325) | Project | + ### Security policy management | Name | Description | Saved to database | Streamed | Introduced in | Scope | diff --git a/ee/config/audit_events/types/user_authenticated_using_job_token.yml b/ee/config/audit_events/types/user_authenticated_using_job_token.yml new file mode 100644 index 0000000000000000000000000000000000000000..351ce178924d30ce935f32db592e606068d958a8 --- /dev/null +++ b/ee/config/audit_events/types/user_authenticated_using_job_token.yml @@ -0,0 +1,10 @@ +--- +name: user_authenticated_using_job_token +description: Triggered when a user is authenticated using job token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/481325 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164546 +feature_category: secrets_management +milestone: '17.4' +saved_to_database: false +streamed: true +scope: [Project] diff --git a/ee/lib/ee/gitlab/auth/auth_finders.rb b/ee/lib/ee/gitlab/auth/auth_finders.rb index 17ddfad5d7cb5ce888c7d6923db9d55d32da87b4..1d8553ce290ea17d85c09f5b746d3c36d7163314 100644 --- a/ee/lib/ee/gitlab/auth/auth_finders.rb +++ b/ee/lib/ee/gitlab/auth/auth_finders.rb @@ -55,6 +55,33 @@ def scim_request? def geo_api_request? current_request.path.starts_with?("/api/#{::API::API.version}/geo/") end + + override :find_user_from_job_token + def find_user_from_job_token + user = super + return unless user + + audit_job_token_authentication(user) + + user + end + + private + + def audit_job_token_authentication(user) + # rubocop:disable Gitlab/ModuleWithInstanceVariables -- Already used in super + audit_context = { + name: "user_authenticated_using_job_token", + author: user, + scope: @current_authenticated_job.project, + target: @current_authenticated_job, + target_details: @current_authenticated_job.id.to_s, + message: "#{user.name} authenticated using job token of job id: #{@current_authenticated_job.id}" + } + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + ::Gitlab::Audit::Auditor.audit(audit_context) + end end end end diff --git a/ee/spec/lib/ee/gitlab/auth/auth_finders_spec.rb b/ee/spec/lib/ee/gitlab/auth/auth_finders_spec.rb index 9d5fb18ed315d14fcaac3e7e3a175c3cbe847bdc..20069d4806dbe45a78ba36e1362310af8d3fd888 100644 --- a/ee/spec/lib/ee/gitlab/auth/auth_finders_spec.rb +++ b/ee/spec/lib/ee/gitlab/auth/auth_finders_spec.rb @@ -7,13 +7,16 @@ include ::EE::GeoHelpers let(:request) { ActionDispatch::Request.new(env) } + let_it_be(:user) { create(:user) } let(:env) do { 'rack.input' => '' } end - let_it_be(:user) { create(:user) } + def set_header(key, value) + env[key] = value + end describe '#find_user_from_geo_token' do subject { find_user_from_geo_token } @@ -214,4 +217,49 @@ end end end + + describe '#find_user_from_job_token', :request_store do + let_it_be(:project) { create(:project, :private, developers: user) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:job) { create(:ci_build, :running, pipeline: pipeline, user: user) } + let_it_be(:route_authentication_setting) { { job_token_allowed: true } } + + subject { find_user_from_job_token } + + context 'when token is valid' do + let(:token) { job.token } + + before do + set_header(described_class::JOB_TOKEN_HEADER, token) + allow(::Gitlab::Audit::Auditor).to receive(:audit) + end + + it 'returns user and streams audit event' do + expect(subject).to eq(user) + + expect(::Gitlab::Audit::Auditor).to have_received(:audit).with( + name: "user_authenticated_using_job_token", + author: user, + scope: job.project, + target: job, + target_details: job.id.to_s, + message: "#{user.name} authenticated using job token of job id: #{job.id}" + ) + end + end + + context 'when token is invalid' do + let(:token) { "invalid token" } + + before do + set_header(described_class::JOB_TOKEN_HEADER, token) + allow(::Gitlab::Audit::Auditor).to receive(:audit) + end + + it 'returns user' do + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect(::Gitlab::Audit::Auditor).not_to have_received(:audit) + end + end + end end diff --git a/ee/spec/requests/api/ci/runner_spec.rb b/ee/spec/requests/api/ci/runner_spec.rb index 55ec693124009dd03935f1762d9adee163f30a69..28a724ef248bfa0894b7c219b2054e2698968a16 100644 --- a/ee/spec/requests/api/ci/runner_spec.rb +++ b/ee/spec/requests/api/ci/runner_spec.rb @@ -135,20 +135,21 @@ def request_job(token = runner.token, **params) before do project.group.root_ancestor.external_audit_event_destinations.create!(destination_url: 'http://example.com') stub_licensed_features(admin_audit_log: true, extended_audit_events: true, external_audit_events: true) + allow(::Gitlab::Audit::Auditor).to receive(:audit).and_call_original + allow(AuditEvents::AuditEventStreamingWorker).to receive(:perform_async).and_call_original end it 'downloads artifacts' do + download_artifact + expect(::Gitlab::Audit::Auditor).to( - receive(:audit).with(hash_including(name: 'job_artifact_downloaded')).and_call_original + have_received(:audit).with(hash_including(name: 'job_artifact_downloaded')) ) expect(AuditEvents::AuditEventStreamingWorker).to( - receive(:perform_async) + have_received(:perform_async) .with('job_artifact_downloaded', nil, a_string_including("Downloaded artifact")) - .and_call_original ) - download_artifact - expect(response).to have_gitlab_http_status(:ok) end end