From eba49037ecb1f6beef60422b80475901fd9bfbea Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 25 May 2021 15:30:21 +0200 Subject: [PATCH 01/14] Audit DAST profile actions This commit actually only adds auditing for DAST profile creation, but the whole MR will add it for deletion and updates as well Changelog: added EE: true --- .../app_sec/dast/profiles/create_service.rb | 12 ++++++++++ .../dast/profiles/create_service_spec.rb | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ee/app/services/app_sec/dast/profiles/create_service.rb b/ee/app/services/app_sec/dast/profiles/create_service.rb index ef9ea4d245844e..93660242590f8b 100644 --- a/ee/app/services/app_sec/dast/profiles/create_service.rb +++ b/ee/app/services/app_sec/dast/profiles/create_service.rb @@ -16,6 +16,8 @@ def execute dast_scanner_profile: dast_scanner_profile ) + create_audit_event(dast_profile) + return ServiceResponse.success(payload: { dast_profile: dast_profile, pipeline_url: nil }) unless params.fetch(:run_after_create) response = ::DastOnDemandScans::CreateService.new( @@ -46,6 +48,16 @@ def dast_site_profile def dast_scanner_profile @dast_scanner_profile ||= params.fetch(:dast_scanner_profile) end + + def create_audit_event(profile) + ::Gitlab::Audit::Auditor.audit( + name: 'dast_profile_create', + author: current_user, + scope: container, + target: profile, + message: "Added DAST profile" + ) + end end end end diff --git a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb index 614ce26621be94..a41ebd1a00157a 100644 --- a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb @@ -12,7 +12,7 @@ { name: SecureRandom.hex, description: :description, - branch_name: 'orphaned-branch', + branch_name: project.default_branch, dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile, run_after_create: false @@ -48,6 +48,27 @@ expect { subject }.to change { Dast::Profile.count }.by(1) end + it 'audits the creation' do + profile = subject.payload[:dast_profile] + + audit_event = AuditEvent.last + + aggregate_failures do + expect(audit_event.author).to eq(developer) + expect(audit_event.entity).to eq(project) + expect(audit_event.target_id).to eq(profile.id) + expect(audit_event.target_type).to eq('Dast::Profile') + expect(audit_event.target_details).to eq(profile.name) + expect(audit_event.details).to eq({ + author_name: developer.name, + custom_message: 'Added DAST profile', + target_id: profile.id, + target_type: 'Dast::Profile', + target_details: profile.name + }) + end + end + context 'when param run_after_create: true' do let(:params) { default_params.merge(run_after_create: true) } -- GitLab From 1390146ce66df76a188110340a60bbe68b04436a Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 25 May 2021 15:55:25 +0200 Subject: [PATCH 02/14] Audit DAST profile deletion --- .../app_sec/dast/profiles/destroy_service.rb | 12 +++++++++++ .../dast/profiles/destroy_service_spec.rb | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ee/app/services/app_sec/dast/profiles/destroy_service.rb b/ee/app/services/app_sec/dast/profiles/destroy_service.rb index 7e4b0d50dccb51..2a99a9271ff0b9 100644 --- a/ee/app/services/app_sec/dast/profiles/destroy_service.rb +++ b/ee/app/services/app_sec/dast/profiles/destroy_service.rb @@ -9,6 +9,8 @@ def execute return ServiceResponse.error(message: 'Profile parameter missing') unless dast_profile return ServiceResponse.error(message: 'Profile failed to delete') unless dast_profile.destroy + create_audit_event + ServiceResponse.success(payload: dast_profile) end @@ -28,6 +30,16 @@ def unauthorized def dast_profile params[:dast_profile] end + + def create_audit_event + ::Gitlab::Audit::Auditor.audit( + name: 'dast_profile_destroy', + author: current_user, + scope: container, + target: dast_profile, + message: "Removed DAST profile" + ) + end end end end diff --git a/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb index a25b36c19597a6..a7fa1351aa3c0f 100644 --- a/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb @@ -60,6 +60,27 @@ expect(subject.payload).to be_a(Dast::Profile) end + it 'audits the deletion' do + profile = subject.payload + + audit_event = AuditEvent.last + + aggregate_failures do + expect(audit_event.author).to eq(user) + expect(audit_event.entity).to eq(project) + expect(audit_event.target_id).to eq(profile.id) + expect(audit_event.target_type).to eq('Dast::Profile') + expect(audit_event.target_details).to eq(profile.name) + expect(audit_event.details).to eq({ + author_name: user.name, + custom_message: 'Removed DAST profile', + target_id: profile.id, + target_type: 'Dast::Profile', + target_details: profile.name + }) + end + end + context 'when the dast_profile fails to destroy' do it 'communicates failure' do allow(dast_profile).to receive(:destroy).and_return(false) -- GitLab From 64d667e873b3dfc900fd692e649be29b9a6a6015 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Wed, 26 May 2021 19:42:18 +0200 Subject: [PATCH 03/14] Audit DAST profile updates --- .../app_sec/dast/profiles/update_service.rb | 36 +++++++++++ .../dast/profiles/update_service_spec.rb | 60 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/ee/app/services/app_sec/dast/profiles/update_service.rb b/ee/app/services/app_sec/dast/profiles/update_service.rb index cb876219dc5f64..579046d2aec74e 100644 --- a/ee/app/services/app_sec/dast/profiles/update_service.rb +++ b/ee/app/services/app_sec/dast/profiles/update_service.rb @@ -9,8 +9,13 @@ class UpdateService < BaseContainerService def execute return unauthorized unless allowed? return error('Profile parameter missing') unless dast_profile + + old_params = dast_profile.attributes.symbolize_keys + return error(dast_profile.errors.full_messages) unless dast_profile.update(dast_profile_params) + create_audit_events(old_params) + return success(dast_profile: dast_profile, pipeline_url: nil) unless params[:run_after_update] response = create_scan(dast_profile) @@ -54,6 +59,37 @@ def create_scan(dast_profile) params: { dast_profile: dast_profile } ).execute end + + def create_audit_events(old_params) + dast_profile_params.each do |property, new_value| + old_value = old_params[property] + + next if old_value == new_value + + ::Gitlab::Audit::Auditor.audit( + name: 'dast_profile_update', + author: current_user, + scope: container, + target: dast_profile, + message: audit_message(property, new_value, old_value) + ) + end + end + + def audit_message(property, new_value, old_value) + case property + when :dast_scanner_profile_id + new_value = DastScannerProfile.find(new_value).name + old_value = DastScannerProfile.find(old_value).name + property = :dast_scanner_profile + when :dast_site_profile_id + new_value = DastSiteProfile.find(new_value).name + old_value = DastSiteProfile.find(old_value).name + property = :dast_site_profile + end + + "Changed DAST profile #{property} from #{old_value} to #{new_value}" + end end end end diff --git a/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb index 3b1341f0691a66..c60b66eaca7db4 100644 --- a/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb @@ -76,6 +76,66 @@ end end + it 'audits the update', :aggregate_failures do + old_profile_attrs = { + description: dast_profile.description, + name: dast_profile.name, + scanner_profile_name: dast_profile.dast_scanner_profile.name, + site_profile_name: dast_profile.dast_site_profile.name + } + + subject + + new_profile = dast_profile.reload + audit_events = AuditEvent.all + audit_events_details = audit_events.map(&:details) + + audit_events.each do |event| + expect(event.author).to eq(user) + expect(event.entity).to eq(project) + expect(event.target_id).to eq(new_profile.id) + expect(event.target_type).to eq('Dast::Profile') + expect(event.target_details).to eq(new_profile.name) + end + + expect(audit_events_details).to include( + a_hash_including( + author_name: user.name, + custom_message: "Changed DAST profile dast_scanner_profile from #{old_profile_attrs[:scanner_profile_name]} to #{dast_scanner_profile.name}", + target_id: new_profile.id, + target_type: 'Dast::Profile', + target_details: new_profile.name + ) + ) + expect(audit_events_details).to include( + a_hash_including( + author_name: user.name, + custom_message: "Changed DAST profile dast_site_profile from #{old_profile_attrs[:site_profile_name]} to #{dast_site_profile.name}", + target_id: new_profile.id, + target_type: 'Dast::Profile', + target_details: new_profile.name + ) + ) + expect(audit_events_details).to include( + a_hash_including( + author_name: user.name, + custom_message: "Changed DAST profile name from #{old_profile_attrs[:name]} to #{new_profile.name}", + target_id: new_profile.id, + target_type: 'Dast::Profile', + target_details: new_profile.name + ) + ) + expect(audit_events_details).to include( + a_hash_including( + author_name: user.name, + custom_message: "Changed DAST profile description from #{old_profile_attrs[:description]} to #{new_profile.description}", + target_id: new_profile.id, + target_type: 'Dast::Profile', + target_details: new_profile.name + ) + ) + end + context 'when param run_after_update: true' do let(:params) { default_params.merge(run_after_update: true) } -- GitLab From cff30c8ae8fe2c954cfacdd423863c2c07f8327c Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Wed, 26 May 2021 20:21:41 +0200 Subject: [PATCH 04/14] Extract update audit logic into another service It's complex enough that it deserves it's own place (just like the site profile update audit) --- .../dast/profiles/audit/update_service.rb | 44 ++++++++++++++ .../app_sec/dast/profiles/update_service.rb | 38 ++----------- .../profiles/audit/update_service_spec.rb | 57 +++++++++++++++++++ 3 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 ee/app/services/app_sec/dast/profiles/audit/update_service.rb create mode 100644 ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb diff --git a/ee/app/services/app_sec/dast/profiles/audit/update_service.rb b/ee/app/services/app_sec/dast/profiles/audit/update_service.rb new file mode 100644 index 00000000000000..58756047707fca --- /dev/null +++ b/ee/app/services/app_sec/dast/profiles/audit/update_service.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module AppSec + module Dast + module Profiles + module Audit + class UpdateService < BaseContainerService + def execute + params[:new_params].each do |property, new_value| + old_value = params[:old_params][property] + + next if old_value == new_value + + ::Gitlab::Audit::Auditor.audit( + name: 'dast_profile_update', + author: current_user, + scope: container, + target: params[:dast_profile], + message: audit_message(property, new_value, old_value) + ) + end + end + + private + + def audit_message(property, new_value, old_value) + case property + when :dast_scanner_profile_id + new_value = DastScannerProfile.find(new_value).name + old_value = DastScannerProfile.find(old_value).name + property = :dast_scanner_profile + when :dast_site_profile_id + new_value = DastSiteProfile.find(new_value).name + old_value = DastSiteProfile.find(old_value).name + property = :dast_site_profile + end + + "Changed DAST profile #{property} from #{old_value} to #{new_value}" + end + end + end + end + end +end diff --git a/ee/app/services/app_sec/dast/profiles/update_service.rb b/ee/app/services/app_sec/dast/profiles/update_service.rb index 579046d2aec74e..eec319a343aa0c 100644 --- a/ee/app/services/app_sec/dast/profiles/update_service.rb +++ b/ee/app/services/app_sec/dast/profiles/update_service.rb @@ -11,10 +11,15 @@ def execute return error('Profile parameter missing') unless dast_profile old_params = dast_profile.attributes.symbolize_keys + auditor = Audit::UpdateService.new(container: container, current_user: current_user, params: { + dast_profile: dast_profile, + new_params: dast_profile_params, + old_params: old_params + }) return error(dast_profile.errors.full_messages) unless dast_profile.update(dast_profile_params) - create_audit_events(old_params) + auditor.execute return success(dast_profile: dast_profile, pipeline_url: nil) unless params[:run_after_update] @@ -59,37 +64,6 @@ def create_scan(dast_profile) params: { dast_profile: dast_profile } ).execute end - - def create_audit_events(old_params) - dast_profile_params.each do |property, new_value| - old_value = old_params[property] - - next if old_value == new_value - - ::Gitlab::Audit::Auditor.audit( - name: 'dast_profile_update', - author: current_user, - scope: container, - target: dast_profile, - message: audit_message(property, new_value, old_value) - ) - end - end - - def audit_message(property, new_value, old_value) - case property - when :dast_scanner_profile_id - new_value = DastScannerProfile.find(new_value).name - old_value = DastScannerProfile.find(old_value).name - property = :dast_scanner_profile - when :dast_site_profile_id - new_value = DastSiteProfile.find(new_value).name - old_value = DastSiteProfile.find(old_value).name - property = :dast_site_profile - end - - "Changed DAST profile #{property} from #{old_value} to #{new_value}" - end end end end diff --git a/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb new file mode 100644 index 00000000000000..7c9d924189d3a1 --- /dev/null +++ b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe AppSec::Dast::Profiles::Audit::UpdateService do + let_it_be(:dast_profile) { create(:dast_profile) } + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + describe '#execute' do + it 'creates audit events for the changed properties', :aggregate_failures do + auditor = described_class.new(container: project, current_user: user, params: { + dast_profile: dast_profile, + new_params: { name: 'New name' }, + old_params: { name: 'Old name' } + }) + + auditor.execute + + audit_event = AuditEvent.last + expect(audit_event.author).to eq(user) + expect(audit_event.entity).to eq(project) + expect(audit_event.target_id).to eq(dast_profile.id) + expect(audit_event.target_type).to eq('Dast::Profile') + expect(audit_event.target_details).to eq(dast_profile.name) + expect(audit_event.details).to eq({ + author_name: user.name, + custom_message: 'Changed DAST profile name from Old name to New name', + target_id: dast_profile.id, + target_type: 'Dast::Profile', + target_details: dast_profile.name + }) + end + + it 'uses names instead of IDs for the changed scanner and site profile messages' do + new_scanner_profile = create(:dast_scanner_profile) + old_scanner_profile = create(:dast_scanner_profile) + new_site_profile = create(:dast_site_profile) + old_site_profile = create(:dast_site_profile) + + auditor = described_class.new(container: project, current_user: user, params: { + dast_profile: dast_profile, + new_params: { dast_scanner_profile_id: new_scanner_profile.id, dast_site_profile_id: new_site_profile.id }, + old_params: { dast_scanner_profile_id: old_scanner_profile.id, dast_site_profile_id: old_site_profile.id } + }) + + auditor.execute + + audit_events = AuditEvent.all + messages = audit_events.map(&:details).pluck(:custom_message) + expect(messages).to contain_exactly( + "Changed DAST profile dast_scanner_profile from #{old_scanner_profile.name} to #{new_scanner_profile.name}", + "Changed DAST profile dast_site_profile from #{old_site_profile.name} to #{new_site_profile.name}" + ) + end + end +end -- GitLab From 5a4bf2807c2dc9b3973fe8e643fb326246868e7d Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Wed, 26 May 2021 20:58:56 +0200 Subject: [PATCH 05/14] Fix audit service namespace The specs were passing, but my local development environment errored because it couldn't locate the class --- ee/app/services/app_sec/dast/profiles/update_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/services/app_sec/dast/profiles/update_service.rb b/ee/app/services/app_sec/dast/profiles/update_service.rb index eec319a343aa0c..e8fba3f294634f 100644 --- a/ee/app/services/app_sec/dast/profiles/update_service.rb +++ b/ee/app/services/app_sec/dast/profiles/update_service.rb @@ -11,7 +11,7 @@ def execute return error('Profile parameter missing') unless dast_profile old_params = dast_profile.attributes.symbolize_keys - auditor = Audit::UpdateService.new(container: container, current_user: current_user, params: { + auditor = AppSec::Dast::Profiles::Audit::UpdateService.new(container: container, current_user: current_user, params: { dast_profile: dast_profile, new_params: dast_profile_params, old_params: old_params -- GitLab From 0d2838bd3ae97f00fbe02f8bdc3bf4be84ce26b8 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Wed, 26 May 2021 21:04:34 +0200 Subject: [PATCH 06/14] Add docs for DAST profile management auditing --- doc/administration/audit_events.md | 1 + doc/user/application_security/dast/index.md | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index f0c4d94766849a..7fab424ac936c1 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -120,6 +120,7 @@ From there, you can see the following actions: - Project access token was successfully created or revoked ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9) - Failed attempt to create or revoke a project access token ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9) - When default branch changes for a project ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/52339) in GitLab 13.9) +- Created, updated, or deleted DAST profiles, DAST scanner profiles, and DAST site profiles ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872)) Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events). diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 4a96efc3d72a30..01aecccbedb036 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -957,6 +957,7 @@ Alternatively, you can use the CI/CD variable `SECURE_ANALYZERS_PREFIX` to overr > - The saved scans feature was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5100) in GitLab 13.9. > - The option to select a branch was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/4847) in GitLab 13.10. > - DAST branch selection [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/322672) in GitLab 13.11. +> - Auditing for DAST profile management was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.0. An on-demand DAST scan runs outside the DevOps life cycle. Changes in your repository don't trigger the scan. You must start it manually. @@ -1280,6 +1281,13 @@ If a scanner profile is linked to a security policy, a user cannot delete the pr page. See [Scan Policies](../policies/index.md) for more information. +### Auditing + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.0. + +The creation, updating, and deletion of DAST profiles, DAST scanner profiles, +and DAST site profiles are included in the [audit log](../../../administration/audit_events.md). + ## Reports The DAST tool outputs a report file in JSON format by default. However, this tool can also generate reports in -- GitLab From e80dadf3761b75b1e4d53bbd37e65f13aafd6f06 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 17 Jun 2021 17:53:16 +0200 Subject: [PATCH 07/14] Prevent spec flakiness --- .../profiles/audit/update_service_spec.rb | 4 +- .../dast/profiles/create_service_spec.rb | 2 +- .../dast/profiles/destroy_service_spec.rb | 2 +- .../dast/profiles/update_service_spec.rb | 47 ++++--------------- 4 files changed, 13 insertions(+), 42 deletions(-) diff --git a/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb index 7c9d924189d3a1..1090bfbdab48a7 100644 --- a/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb @@ -17,7 +17,7 @@ auditor.execute - audit_event = AuditEvent.last + audit_event = AuditEvent.find_by(author_id: user.id) expect(audit_event.author).to eq(user) expect(audit_event.entity).to eq(project) expect(audit_event.target_id).to eq(dast_profile.id) @@ -46,7 +46,7 @@ auditor.execute - audit_events = AuditEvent.all + audit_events = AuditEvent.where(author_id: user.id) messages = audit_events.map(&:details).pluck(:custom_message) expect(messages).to contain_exactly( "Changed DAST profile dast_scanner_profile from #{old_scanner_profile.name} to #{new_scanner_profile.name}", diff --git a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb index a41ebd1a00157a..e056f9f7efa0a0 100644 --- a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb @@ -51,7 +51,7 @@ it 'audits the creation' do profile = subject.payload[:dast_profile] - audit_event = AuditEvent.last + audit_event = AuditEvent.find_by(author_id: developer.id) aggregate_failures do expect(audit_event.author).to eq(developer) diff --git a/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb index a7fa1351aa3c0f..809438ed36c72b 100644 --- a/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/destroy_service_spec.rb @@ -63,7 +63,7 @@ it 'audits the deletion' do profile = subject.payload - audit_event = AuditEvent.last + audit_event = AuditEvent.find_by(author_id: user.id) aggregate_failures do expect(audit_event.author).to eq(user) diff --git a/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb index c60b66eaca7db4..0c25dc06c44fb8 100644 --- a/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/update_service_spec.rb @@ -87,8 +87,7 @@ subject new_profile = dast_profile.reload - audit_events = AuditEvent.all - audit_events_details = audit_events.map(&:details) + audit_events = AuditEvent.where(author_id: user.id) audit_events.each do |event| expect(event.author).to eq(user) @@ -98,42 +97,14 @@ expect(event.target_details).to eq(new_profile.name) end - expect(audit_events_details).to include( - a_hash_including( - author_name: user.name, - custom_message: "Changed DAST profile dast_scanner_profile from #{old_profile_attrs[:scanner_profile_name]} to #{dast_scanner_profile.name}", - target_id: new_profile.id, - target_type: 'Dast::Profile', - target_details: new_profile.name - ) - ) - expect(audit_events_details).to include( - a_hash_including( - author_name: user.name, - custom_message: "Changed DAST profile dast_site_profile from #{old_profile_attrs[:site_profile_name]} to #{dast_site_profile.name}", - target_id: new_profile.id, - target_type: 'Dast::Profile', - target_details: new_profile.name - ) - ) - expect(audit_events_details).to include( - a_hash_including( - author_name: user.name, - custom_message: "Changed DAST profile name from #{old_profile_attrs[:name]} to #{new_profile.name}", - target_id: new_profile.id, - target_type: 'Dast::Profile', - target_details: new_profile.name - ) - ) - expect(audit_events_details).to include( - a_hash_including( - author_name: user.name, - custom_message: "Changed DAST profile description from #{old_profile_attrs[:description]} to #{new_profile.description}", - target_id: new_profile.id, - target_type: 'Dast::Profile', - target_details: new_profile.name - ) - ) + messages = audit_events.map(&:details).pluck(:custom_message) + expected_messages = [ + "Changed DAST profile dast_scanner_profile from #{old_profile_attrs[:scanner_profile_name]} to #{dast_scanner_profile.name}", + "Changed DAST profile dast_site_profile from #{old_profile_attrs[:site_profile_name]} to #{dast_site_profile.name}", + "Changed DAST profile name from #{old_profile_attrs[:name]} to #{new_profile.name}", + "Changed DAST profile description from #{old_profile_attrs[:description]} to #{new_profile.description}" + ] + expect(messages).to match_array(expected_messages) end context 'when param run_after_update: true' do -- GitLab From 5da934c50ef953b9fc12723ae543097bd3198ab5 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 21 Jun 2021 18:28:50 +0200 Subject: [PATCH 08/14] Revert branch name change in spec The issue with it is that my Gitaly cache needed to be cleared :) --- ee/spec/services/app_sec/dast/profiles/create_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb index e056f9f7efa0a0..6e6c18ff9e1964 100644 --- a/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/create_service_spec.rb @@ -12,7 +12,7 @@ { name: SecureRandom.hex, description: :description, - branch_name: project.default_branch, + branch_name: 'orphaned-branch', dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile, run_after_create: false -- GitLab From de5a67396c3666ec399e344aa801d5d02cc1ebe8 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 21 Jun 2021 18:30:50 +0200 Subject: [PATCH 09/14] Update milestone in docs --- doc/user/application_security/dast/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 01aecccbedb036..b14d06d16ac7cb 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -1283,7 +1283,7 @@ for more information. ### Auditing -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.0. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.1. The creation, updating, and deletion of DAST profiles, DAST scanner profiles, and DAST site profiles are included in the [audit log](../../../administration/audit_events.md). -- GitLab From 4e28aa7e58d83c3f478e5401e6ee1e585c118577 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 21 Jun 2021 18:50:04 +0200 Subject: [PATCH 10/14] Fetch site and scanner profile names in one query This commit introduces DastScannerProfile.names and DastSiteProfile.names to support the querying --- ee/app/models/dast_scanner_profile.rb | 4 ++++ ee/app/models/dast_site_profile.rb | 4 ++++ .../app_sec/dast/profiles/audit/update_service.rb | 6 ++---- ee/spec/models/dast_scanner_profile_spec.rb | 11 +++++++++++ ee/spec/models/dast_site_profile_spec.rb | 11 +++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/ee/app/models/dast_scanner_profile.rb b/ee/app/models/dast_scanner_profile.rb index 3d599afbbe079b..22e30d24252268 100644 --- a/ee/app/models/dast_scanner_profile.rb +++ b/ee/app/models/dast_scanner_profile.rb @@ -17,6 +17,10 @@ class DastScannerProfile < ApplicationRecord active: 2 } + def self.names(scanner_profile_ids) + where(id: scanner_profile_ids).pluck(:name) + end + def ci_variables ::Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'DAST_SPIDER_MINS', value: String(spider_timeout)) if spider_timeout diff --git a/ee/app/models/dast_site_profile.rb b/ee/app/models/dast_site_profile.rb index 1b061d690ddfc0..d615946679fe98 100644 --- a/ee/app/models/dast_site_profile.rb +++ b/ee/app/models/dast_site_profile.rb @@ -31,6 +31,10 @@ class DastSiteProfile < ApplicationRecord delegate :dast_site_validation, to: :dast_site, allow_nil: true + def self.names(site_profile_ids) + where(id: site_profile_ids).pluck(:name) + end + def ci_variables url = dast_site.url diff --git a/ee/app/services/app_sec/dast/profiles/audit/update_service.rb b/ee/app/services/app_sec/dast/profiles/audit/update_service.rb index 58756047707fca..8260346640d4e3 100644 --- a/ee/app/services/app_sec/dast/profiles/audit/update_service.rb +++ b/ee/app/services/app_sec/dast/profiles/audit/update_service.rb @@ -26,12 +26,10 @@ def execute def audit_message(property, new_value, old_value) case property when :dast_scanner_profile_id - new_value = DastScannerProfile.find(new_value).name - old_value = DastScannerProfile.find(old_value).name + old_value, new_value = DastScannerProfile.names([old_value, new_value]) property = :dast_scanner_profile when :dast_site_profile_id - new_value = DastSiteProfile.find(new_value).name - old_value = DastSiteProfile.find(old_value).name + old_value, new_value = DastSiteProfile.names([old_value, new_value]) property = :dast_site_profile end diff --git a/ee/spec/models/dast_scanner_profile_spec.rb b/ee/spec/models/dast_scanner_profile_spec.rb index 5563859c3ecc9a..29e9c963018ef8 100644 --- a/ee/spec/models/dast_scanner_profile_spec.rb +++ b/ee/spec/models/dast_scanner_profile_spec.rb @@ -35,6 +35,17 @@ end end + describe '.names' do + it 'returns the names for the DAST scanner profiles with the given IDs' do + first_profile = create(:dast_scanner_profile, name: 'First profile') + second_profile = create(:dast_scanner_profile, name: 'Second profile') + + names = described_class.names([first_profile.id, second_profile.id]) + + expect(names).to contain_exactly('First profile', 'Second profile') + end + end + describe '#ci_variables' do let(:collection) { subject.ci_variables } diff --git a/ee/spec/models/dast_site_profile_spec.rb b/ee/spec/models/dast_site_profile_spec.rb index da4b98485dd786..52455ce3e4d2ee 100644 --- a/ee/spec/models/dast_site_profile_spec.rb +++ b/ee/spec/models/dast_site_profile_spec.rb @@ -132,6 +132,17 @@ it { is_expected.to define_enum_for(:target_type).with_values(**target_types) } end + describe '.names' do + it 'returns the names for the DAST site profiles with the given IDs' do + first_profile = create(:dast_site_profile, name: 'First profile') + second_profile = create(:dast_site_profile, name: 'Second profile') + + names = described_class.names([first_profile.id, second_profile.id]) + + expect(names).to contain_exactly('First profile', 'Second profile') + end + end + describe 'instance methods' do describe '#destroy!' do context 'when the associated dast_site has no dast_site_profiles' do -- GitLab From a47012bbc992c0f4325a707bd0201c2c28f43e95 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 22 Jun 2021 12:39:18 +0200 Subject: [PATCH 11/14] Remove unnecessary variable I accidentally overrode the previous commit that did this with an unfortunate force push. MEH --- ee/app/services/app_sec/dast/profiles/update_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ee/app/services/app_sec/dast/profiles/update_service.rb b/ee/app/services/app_sec/dast/profiles/update_service.rb index e8fba3f294634f..d44f366f39c83c 100644 --- a/ee/app/services/app_sec/dast/profiles/update_service.rb +++ b/ee/app/services/app_sec/dast/profiles/update_service.rb @@ -10,11 +10,10 @@ def execute return unauthorized unless allowed? return error('Profile parameter missing') unless dast_profile - old_params = dast_profile.attributes.symbolize_keys auditor = AppSec::Dast::Profiles::Audit::UpdateService.new(container: container, current_user: current_user, params: { dast_profile: dast_profile, new_params: dast_profile_params, - old_params: old_params + old_params: dast_profile.attributes.symbolize_keys }) return error(dast_profile.errors.full_messages) unless dast_profile.update(dast_profile_params) -- GitLab From 58e7d5a9596b2ba24e0c33c22fa8aaf9432d0d04 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 22 Jun 2021 12:42:15 +0200 Subject: [PATCH 12/14] Update the milestone Missed one :) --- doc/user/application_security/dast/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index b14d06d16ac7cb..eb4faca63f96a3 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -957,7 +957,7 @@ Alternatively, you can use the CI/CD variable `SECURE_ANALYZERS_PREFIX` to overr > - The saved scans feature was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5100) in GitLab 13.9. > - The option to select a branch was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/4847) in GitLab 13.10. > - DAST branch selection [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/322672) in GitLab 13.11. -> - Auditing for DAST profile management was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.0. +> - Auditing for DAST profile management was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.1. An on-demand DAST scan runs outside the DevOps life cycle. Changes in your repository don't trigger the scan. You must start it manually. -- GitLab From 55cc01194b41c1a17fd3f7bb1c56b94754b9a43a Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 22 Jun 2021 16:39:03 +0200 Subject: [PATCH 13/14] Add spec to check query number This should help to prevent future changes from making this feature unperformant --- .../profiles/audit/update_service_spec.rb | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb index 1090bfbdab48a7..f33ccdf8d7c242 100644 --- a/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb +++ b/ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb @@ -53,5 +53,37 @@ "Changed DAST profile dast_site_profile from #{old_site_profile.name} to #{new_site_profile.name}" ) end + + it 'does not exceed the maximum permitted number of queries' do + new_scanner_profile = create(:dast_scanner_profile) + old_scanner_profile = create(:dast_scanner_profile) + new_site_profile = create(:dast_site_profile) + old_site_profile = create(:dast_site_profile) + + new_params = { + branch_name: 'new-branch', + dast_scanner_profile_id: new_scanner_profile.id, + dast_site_profile_id: new_site_profile.id, + description: 'New description', + name: 'New name' + } + old_params = { + branch_name: 'old-branch', + dast_scanner_profile_id: old_scanner_profile.id, + dast_site_profile_id: old_site_profile.id, + description: 'Old description', + name: 'Old name' + } + + auditor = described_class.new(container: project, current_user: user, params: { + dast_profile: dast_profile, new_params: new_params, old_params: old_params + }) + + recorder = ActiveRecord::QueryRecorder.new do + auditor.execute + end + + expect(recorder.count).to be <= 18 + end end end -- GitLab From f28741b7ad46a2e5e01d0b54a8a169f2adae4791 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 22 Jun 2021 18:15:33 +0200 Subject: [PATCH 14/14] Ensure names returned in order of given IDs --- ee/app/models/dast_scanner_profile.rb | 4 +++- ee/app/models/dast_site_profile.rb | 4 +++- ee/spec/models/dast_scanner_profile_spec.rb | 8 ++++++++ ee/spec/models/dast_site_profile_spec.rb | 8 ++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ee/app/models/dast_scanner_profile.rb b/ee/app/models/dast_scanner_profile.rb index 22e30d24252268..93cb684546ae77 100644 --- a/ee/app/models/dast_scanner_profile.rb +++ b/ee/app/models/dast_scanner_profile.rb @@ -18,7 +18,9 @@ class DastScannerProfile < ApplicationRecord } def self.names(scanner_profile_ids) - where(id: scanner_profile_ids).pluck(:name) + find(*scanner_profile_ids).pluck(:name) + rescue ActiveRecord::RecordNotFound + [] end def ci_variables diff --git a/ee/app/models/dast_site_profile.rb b/ee/app/models/dast_site_profile.rb index d615946679fe98..2b19997cc70b1e 100644 --- a/ee/app/models/dast_site_profile.rb +++ b/ee/app/models/dast_site_profile.rb @@ -32,7 +32,9 @@ class DastSiteProfile < ApplicationRecord delegate :dast_site_validation, to: :dast_site, allow_nil: true def self.names(site_profile_ids) - where(id: site_profile_ids).pluck(:name) + find(*site_profile_ids).pluck(:name) + rescue ActiveRecord::RecordNotFound + [] end def ci_variables diff --git a/ee/spec/models/dast_scanner_profile_spec.rb b/ee/spec/models/dast_scanner_profile_spec.rb index 29e9c963018ef8..c090cce866d8f7 100644 --- a/ee/spec/models/dast_scanner_profile_spec.rb +++ b/ee/spec/models/dast_scanner_profile_spec.rb @@ -44,6 +44,14 @@ expect(names).to contain_exactly('First profile', 'Second profile') end + + context 'when a profile is not found' do + it 'rescues the error and returns an empty array' do + names = described_class.names([0]) + + expect(names).to be_empty + end + end end describe '#ci_variables' do diff --git a/ee/spec/models/dast_site_profile_spec.rb b/ee/spec/models/dast_site_profile_spec.rb index 52455ce3e4d2ee..f57516827987af 100644 --- a/ee/spec/models/dast_site_profile_spec.rb +++ b/ee/spec/models/dast_site_profile_spec.rb @@ -141,6 +141,14 @@ expect(names).to contain_exactly('First profile', 'Second profile') end + + context 'when a profile is not found' do + it 'rescues the error and returns an empty array' do + names = described_class.names([0]) + + expect(names).to be_empty + end + end end describe 'instance methods' do -- GitLab