Commit 0c875762 authored by Philip Cunningham's avatar Philip Cunningham

Associate Build with DAST Site and Scanner profile

- Add new table dast_scanner_profiles_builds
- Add new table dast_site_profiles_builds
- Add new models
- Add new specs

Changelog: added
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63362
parent c044eeae
...@@ -10,6 +10,10 @@ module Ci ...@@ -10,6 +10,10 @@ module Ci
resource_group scheduling_type].freeze resource_group scheduling_type].freeze
end end
def self.extra_accessors
[]
end
def execute(build) def execute(build)
build.ensure_scheduling_type! build.ensure_scheduling_type!
......
# frozen_string_literal: true
class CreateDastSiteProfilesBuilds < ActiveRecord::Migration[6.1]
def up
table_comment = { owner: 'group::dynamic analysis', description: 'Join table between DAST Site Profiles and CI Builds' }
create_table :dast_site_profiles_builds, primary_key: [:dast_site_profile_id, :ci_build_id], comment: table_comment.to_json do |t|
t.bigint :dast_site_profile_id, null: false
t.bigint :ci_build_id, null: false
t.index :ci_build_id, unique: true, name: :dast_site_profiles_builds_on_ci_build_id
end
end
def down
drop_table :dast_site_profiles_builds
end
end
# frozen_string_literal: true
class AddCiBuildIdFkToDastSiteProfilesBuilds < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_site_profiles_builds, :ci_builds, column: :ci_build_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_site_profiles_builds, column: :ci_build_id
end
end
end
# frozen_string_literal: true
class AddDastSiteProfileIdFkToDastSiteProfilesBuilds < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_site_profiles_builds, :dast_site_profiles, column: :dast_site_profile_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_site_profiles_builds, column: :dast_site_profile_id
end
end
end
# frozen_string_literal: true
class CreateDastScannerProfilesBuilds < ActiveRecord::Migration[6.1]
def up
table_comment = { owner: 'group::dynamic analysis', description: 'Join table between DAST Scanner Profiles and CI Builds' }
create_table :dast_scanner_profiles_builds, primary_key: [:dast_scanner_profile_id, :ci_build_id], comment: table_comment.to_json do |t|
t.bigint :dast_scanner_profile_id, null: false
t.bigint :ci_build_id, null: false
t.index :ci_build_id, unique: true, name: :dast_scanner_profiles_builds_on_ci_build_id
end
end
def down
drop_table :dast_scanner_profiles_builds
end
end
# frozen_string_literal: true
class AddCiBuildIdFkToDastScannerProfilesBuilds < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_scanner_profiles_builds, :ci_builds, column: :ci_build_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_scanner_profiles_builds, column: :ci_build_id
end
end
end
# frozen_string_literal: true
class AddDastScannerProfileIdFkToDastScannerProfilesBuilds < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_scanner_profiles_builds, :dast_scanner_profiles, column: :dast_scanner_profile_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_scanner_profiles_builds, column: :dast_scanner_profile_id
end
end
end
fa373e98739d57d829273cfa9246137e2c151be67e97183c1dcdb288150aaeb5
\ No newline at end of file
c7cf4aad7637d793d1ace8fee02111bc9b0d2eea09efadb0fd616bc5c5e5550c
\ No newline at end of file
da868be7c8edefc462110b5b36415870cc0c7c59dba1e3d514348011a9e70642
\ No newline at end of file
2d025932dca7a407968e14872ce053461e69550098ca089d4e6ece323d240927
\ No newline at end of file
7529373266b6c9b179367d5fa8775f5e2ad600008957b3a821d689aec70c7407
\ No newline at end of file
3818094a4470ff7d0c105c000655dac4205e8265f78df638df0e2ef3dc6deaf3
\ No newline at end of file
...@@ -12047,6 +12047,13 @@ CREATE TABLE dast_scanner_profiles ( ...@@ -12047,6 +12047,13 @@ CREATE TABLE dast_scanner_profiles (
CONSTRAINT check_568568fabf CHECK ((char_length(name) <= 255)) CONSTRAINT check_568568fabf CHECK ((char_length(name) <= 255))
); );
CREATE TABLE dast_scanner_profiles_builds (
dast_scanner_profile_id bigint NOT NULL,
ci_build_id bigint NOT NULL
);
COMMENT ON TABLE dast_scanner_profiles_builds IS '{"owner":"group::dynamic analysis","description":"Join table between DAST Scanner Profiles and CI Builds"}';
CREATE SEQUENCE dast_scanner_profiles_id_seq CREATE SEQUENCE dast_scanner_profiles_id_seq
START WITH 1 START WITH 1
INCREMENT BY 1 INCREMENT BY 1
...@@ -12102,6 +12109,13 @@ CREATE TABLE dast_site_profiles ( ...@@ -12102,6 +12109,13 @@ CREATE TABLE dast_site_profiles (
CONSTRAINT check_f22f18002a CHECK ((char_length(auth_username) <= 255)) CONSTRAINT check_f22f18002a CHECK ((char_length(auth_username) <= 255))
); );
CREATE TABLE dast_site_profiles_builds (
dast_site_profile_id bigint NOT NULL,
ci_build_id bigint NOT NULL
);
COMMENT ON TABLE dast_site_profiles_builds IS '{"owner":"group::dynamic analysis","description":"Join table between DAST Site Profiles and CI Builds"}';
CREATE SEQUENCE dast_site_profiles_id_seq CREATE SEQUENCE dast_site_profiles_id_seq
START WITH 1 START WITH 1
INCREMENT BY 1 INCREMENT BY 1
...@@ -21092,12 +21106,18 @@ ALTER TABLE ONLY dast_profiles_pipelines ...@@ -21092,12 +21106,18 @@ ALTER TABLE ONLY dast_profiles_pipelines
ALTER TABLE ONLY dast_profiles ALTER TABLE ONLY dast_profiles
ADD CONSTRAINT dast_profiles_pkey PRIMARY KEY (id); ADD CONSTRAINT dast_profiles_pkey PRIMARY KEY (id);
ALTER TABLE ONLY dast_scanner_profiles_builds
ADD CONSTRAINT dast_scanner_profiles_builds_pkey PRIMARY KEY (dast_scanner_profile_id, ci_build_id);
ALTER TABLE ONLY dast_scanner_profiles ALTER TABLE ONLY dast_scanner_profiles
ADD CONSTRAINT dast_scanner_profiles_pkey PRIMARY KEY (id); ADD CONSTRAINT dast_scanner_profiles_pkey PRIMARY KEY (id);
ALTER TABLE ONLY dast_site_profile_secret_variables ALTER TABLE ONLY dast_site_profile_secret_variables
ADD CONSTRAINT dast_site_profile_secret_variables_pkey PRIMARY KEY (id); ADD CONSTRAINT dast_site_profile_secret_variables_pkey PRIMARY KEY (id);
ALTER TABLE ONLY dast_site_profiles_builds
ADD CONSTRAINT dast_site_profiles_builds_pkey PRIMARY KEY (dast_site_profile_id, ci_build_id);
ALTER TABLE ONLY dast_site_profiles_pipelines ALTER TABLE ONLY dast_site_profiles_pipelines
ADD CONSTRAINT dast_site_profiles_pipelines_pkey PRIMARY KEY (dast_site_profile_id, ci_pipeline_id); ADD CONSTRAINT dast_site_profiles_pipelines_pkey PRIMARY KEY (dast_site_profile_id, ci_pipeline_id);
...@@ -22343,6 +22363,10 @@ CREATE INDEX commit_id_and_note_id_index ON commit_user_mentions USING btree (co ...@@ -22343,6 +22363,10 @@ CREATE INDEX commit_id_and_note_id_index ON commit_user_mentions USING btree (co
CREATE INDEX composer_cache_files_index_on_deleted_at ON packages_composer_cache_files USING btree (delete_at, id); CREATE INDEX composer_cache_files_index_on_deleted_at ON packages_composer_cache_files USING btree (delete_at, id);
CREATE UNIQUE INDEX dast_scanner_profiles_builds_on_ci_build_id ON dast_scanner_profiles_builds USING btree (ci_build_id);
CREATE UNIQUE INDEX dast_site_profiles_builds_on_ci_build_id ON dast_site_profiles_builds USING btree (ci_build_id);
CREATE UNIQUE INDEX design_management_designs_versions_uniqueness ON design_management_designs_versions USING btree (design_id, version_id); CREATE UNIQUE INDEX design_management_designs_versions_uniqueness ON design_management_designs_versions USING btree (design_id, version_id);
CREATE INDEX design_user_mentions_on_design_id_and_note_id_index ON design_user_mentions USING btree (design_id, note_id); CREATE INDEX design_user_mentions_on_design_id_and_note_id_index ON design_user_mentions USING btree (design_id, note_id);
...@@ -25705,6 +25729,9 @@ ALTER TABLE ONLY vulnerability_feedback ...@@ -25705,6 +25729,9 @@ ALTER TABLE ONLY vulnerability_feedback
ALTER TABLE ONLY deploy_keys_projects ALTER TABLE ONLY deploy_keys_projects
ADD CONSTRAINT fk_58a901ca7e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_58a901ca7e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_scanner_profiles_builds
ADD CONSTRAINT fk_5d46286ad3 FOREIGN KEY (dast_scanner_profile_id) REFERENCES dast_scanner_profiles(id) ON DELETE CASCADE;
ALTER TABLE ONLY issue_assignees ALTER TABLE ONLY issue_assignees
ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
...@@ -25864,6 +25891,9 @@ ALTER TABLE ONLY ci_pipeline_schedules ...@@ -25864,6 +25891,9 @@ ALTER TABLE ONLY ci_pipeline_schedules
ALTER TABLE ONLY todos ALTER TABLE ONLY todos
ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE; ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_site_profiles_builds
ADD CONSTRAINT fk_94e80df60e FOREIGN KEY (dast_site_profile_id) REFERENCES dast_site_profiles(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_feedback ALTER TABLE ONLY vulnerability_feedback
ADD CONSTRAINT fk_94f7c8a81e FOREIGN KEY (comment_author_id) REFERENCES users(id) ON DELETE SET NULL; ADD CONSTRAINT fk_94f7c8a81e FOREIGN KEY (comment_author_id) REFERENCES users(id) ON DELETE SET NULL;
...@@ -25927,6 +25957,9 @@ ALTER TABLE ONLY ci_builds ...@@ -25927,6 +25957,9 @@ ALTER TABLE ONLY ci_builds
ALTER TABLE ONLY ci_pipelines ALTER TABLE ONLY ci_pipelines
ADD CONSTRAINT fk_a23be95014 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; ADD CONSTRAINT fk_a23be95014 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_site_profiles_builds
ADD CONSTRAINT fk_a325505e99 FOREIGN KEY (ci_build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY bulk_import_entities ALTER TABLE ONLY bulk_import_entities
ADD CONSTRAINT fk_a44ff95be5 FOREIGN KEY (parent_id) REFERENCES bulk_import_entities(id) ON DELETE CASCADE; ADD CONSTRAINT fk_a44ff95be5 FOREIGN KEY (parent_id) REFERENCES bulk_import_entities(id) ON DELETE CASCADE;
...@@ -26137,6 +26170,9 @@ ALTER TABLE ONLY gitlab_subscriptions ...@@ -26137,6 +26170,9 @@ ALTER TABLE ONLY gitlab_subscriptions
ALTER TABLE ONLY ci_triggers ALTER TABLE ONLY ci_triggers
ADD CONSTRAINT fk_e3e63f966e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_e3e63f966e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_scanner_profiles_builds
ADD CONSTRAINT fk_e4c49200f8 FOREIGN KEY (ci_build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_requests ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_e719a85f8a FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL; ADD CONSTRAINT fk_e719a85f8a FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
# frozen_string_literal: true
module AppSec
module Dast
module Buildable
extend ::ActiveSupport::Concern
included do
extend SuppressCompositePrimaryKeyWarning
validate :project_ids_match
end
def project_ids_match
return if ci_build.nil? || profile.nil?
unless ci_build.project_id == profile.project_id
errors.add(:ci_build_id, "project_id must match #{profile.class.underscore}.project_id")
end
end
end
end
end
# frozen_string_literal: true
module Dast
class ScannerProfilesBuild < ApplicationRecord
include AppSec::Dast::Buildable
self.table_name = 'dast_scanner_profiles_builds'
belongs_to :ci_build, class_name: 'Ci::Build', optional: false, inverse_of: :dast_scanner_profiles_build
belongs_to :dast_scanner_profile, class_name: 'DastScannerProfile', optional: false, inverse_of: :dast_scanner_profiles_builds
validates :ci_build_id, :dast_scanner_profile_id, presence: true
alias_attribute :profile, :dast_scanner_profile
end
end
# frozen_string_literal: true
module Dast
class SiteProfilesBuild < ApplicationRecord
include AppSec::Dast::Buildable
self.table_name = 'dast_site_profiles_builds'
belongs_to :ci_build, class_name: 'Ci::Build', optional: false, inverse_of: :dast_site_profiles_build
belongs_to :dast_site_profile, class_name: 'DastSiteProfile', optional: false, inverse_of: :dast_site_profiles_builds
validates :ci_build_id, :dast_site_profile_id, presence: true
alias_attribute :profile, :dast_site_profile
end
end
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
class DastScannerProfile < ApplicationRecord class DastScannerProfile < ApplicationRecord
belongs_to :project belongs_to :project
has_many :dast_scanner_profiles_builds, class_name: 'Dast::ScannerProfilesBuild', foreign_key: :dast_scanner_profile_id, inverse_of: :dast_scanner_profile
has_many :ci_builds, class_name: 'Ci::Build', through: :dast_scanner_profiles_builds
validates :project_id, presence: true validates :project_id, presence: true
validates :name, length: { maximum: 255 }, uniqueness: { scope: :project_id }, presence: true validates :name, length: { maximum: 255 }, uniqueness: { scope: :project_id }, presence: true
......
...@@ -9,6 +9,9 @@ class DastSiteProfile < ApplicationRecord ...@@ -9,6 +9,9 @@ class DastSiteProfile < ApplicationRecord
has_many :dast_site_profiles_pipelines, class_name: 'Dast::SiteProfilesPipeline', foreign_key: :dast_site_profile_id, inverse_of: :dast_site_profile has_many :dast_site_profiles_pipelines, class_name: 'Dast::SiteProfilesPipeline', foreign_key: :dast_site_profile_id, inverse_of: :dast_site_profile
has_many :ci_pipelines, class_name: 'Ci::Pipeline', through: :dast_site_profiles_pipelines has_many :ci_pipelines, class_name: 'Ci::Pipeline', through: :dast_site_profiles_pipelines
has_many :dast_site_profiles_builds, class_name: 'Dast::SiteProfilesBuild', foreign_key: :dast_site_profile_id, inverse_of: :dast_site_profile
has_many :ci_builds, class_name: 'Ci::Build', through: :dast_site_profiles_builds
validates :excluded_urls, length: { maximum: 25 } validates :excluded_urls, length: { maximum: 25 }
validates :auth_url, addressable_url: true, length: { maximum: 1024 }, allow_nil: true validates :auth_url, addressable_url: true, length: { maximum: 1024 }, allow_nil: true
validates :auth_username_field, :auth_password_field, :auth_username, length: { maximum: 255 } validates :auth_username_field, :auth_password_field, :auth_username, length: { maximum: 255 }
......
...@@ -31,6 +31,12 @@ module EE ...@@ -31,6 +31,12 @@ module EE
has_many :security_scans, class_name: 'Security::Scan' has_many :security_scans, class_name: 'Security::Scan'
has_one :dast_site_profiles_build, class_name: 'Dast::SiteProfilesBuild', foreign_key: :ci_build_id, inverse_of: :ci_build
has_one :dast_site_profile, class_name: 'DastSiteProfile', through: :dast_site_profiles_build
has_one :dast_scanner_profiles_build, class_name: 'Dast::ScannerProfilesBuild', foreign_key: :ci_build_id, inverse_of: :ci_build
has_one :dast_scanner_profile, class_name: 'DastScannerProfile', through: :dast_scanner_profiles_build
after_commit :track_ci_secrets_management_usage, on: :create after_commit :track_ci_secrets_management_usage, on: :create
delegate :service_specification, to: :runner_session, allow_nil: true delegate :service_specification, to: :runner_session, allow_nil: true
...@@ -47,12 +53,8 @@ module EE ...@@ -47,12 +53,8 @@ module EE
def variables def variables
strong_memoize(:variables) do strong_memoize(:variables) do
super.tap do |collection| super.tap do |collection|
if pipeline.triggered_for_ondemand_dast_scan? collection.concat(dast_on_demand_variables)
# Subject to change. Please see gitlab-org/gitlab#330950 for more info. collection.concat(dast_configuration_variables)
profile = pipeline.dast_profile || pipeline.dast_site_profile
collection.concat(profile.secret_ci_variables(pipeline.user))
end
end end
end end
end end
...@@ -169,6 +171,33 @@ module EE ...@@ -169,6 +171,33 @@ module EE
end end
end end
def dast_on_demand_variables
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
break collection unless pipeline.triggered_for_ondemand_dast_scan?
# Subject to change. Please see gitlab-org/gitlab#330950 for more info.
profile = pipeline.dast_profile || pipeline.dast_site_profile
collection.concat(profile.secret_ci_variables(pipeline.user))
end
end
def dast_configuration_variables
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
break collection unless ::Feature.enabled?(:dast_configuration_ui, project)
break collection unless (dast_configuration = options[:dast_configuration])
if dast_configuration[:site_profile] && dast_site_profile
collection.concat(dast_site_profile.ci_variables)
collection.concat(dast_site_profile.secret_ci_variables(user))
end
if dast_configuration[:scanner_profile] && dast_scanner_profile
collection.concat(dast_scanner_profile.ci_variables)
end
end
end
def parse_security_artifact_blob(security_report, blob) def parse_security_artifact_blob(security_report, blob)
report_clone = security_report.clone_as_blank report_clone = security_report.clone_as_blank
signatures_enabled = ::Feature.enabled?(:vulnerability_finding_tracking_signatures, project) && project.licensed_feature_available?(:vulnerability_finding_signatures) signatures_enabled = ::Feature.enabled?(:vulnerability_finding_tracking_signatures, project) && project.licensed_feature_available?(:vulnerability_finding_signatures)
......
...@@ -11,7 +11,12 @@ module EE ...@@ -11,7 +11,12 @@ module EE
override :clone_accessors override :clone_accessors
def clone_accessors def clone_accessors
(super + %i[secrets]).freeze (super + extra_accessors).freeze
end
override :extra_accessors
def extra_accessors
%i[dast_site_profile dast_scanner_profile secrets].freeze
end end
end end
......
# frozen_string_literal: true
FactoryBot.define do
factory :dast_scanner_profiles_build, class: 'Dast::ScannerProfilesBuild' do
dast_scanner_profile
ci_build { association :ci_build, project: dast_scanner_profile.project }
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :dast_site_profiles_build, class: 'Dast::SiteProfilesBuild' do
dast_site_profile
ci_build { association :ci_build, project: dast_site_profile.project }
end
end
...@@ -129,59 +129,182 @@ RSpec.describe Ci::Build do ...@@ -129,59 +129,182 @@ RSpec.describe Ci::Build do
end end
end end
context 'when there is a dast_profile associated with the pipeline' do context 'dast' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) } let_it_be(:user) { create(:user, developer_projects: [project]) }
let_it_be(:dast_profile) { create(:dast_profile, project: project) } let_it_be(:dast_site_profile) { create(:dast_site_profile, project: project) }
let_it_be(:dast_site_profile_secret_variable) { create(:dast_site_profile_secret_variable, key: 'DAST_PASSWORD_BASE64', dast_site_profile: dast_profile.dast_site_profile) } let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile, project: project) }
let_it_be(:dast_profile) { create(:dast_profile, project: project, dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile) }
let(:pipeline) { create(:ci_pipeline, pipeline_params.merge!(project: project, dast_profile: dast_profile, user: user) ) } let_it_be(:dast_site_profile_secret_variable) { create(:dast_site_profile_secret_variable, key: 'DAST_PASSWORD_BASE64', dast_site_profile: dast_site_profile) }
let_it_be(:options) { { dast_configuration: { site_profile: dast_site_profile.name, scanner_profile: dast_scanner_profile.name } } }
let(:key) { dast_site_profile_secret_variable.key }
let(:value) { dast_site_profile_secret_variable.value }
before do before do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
shared_examples 'a pipeline with no dast on-demand variables' do shared_examples 'it includes variables' do
it 'does not include variables associated with the profile' do it 'includes variables from the profile' do
keys = subject.to_runner_variables.map { |var| var[:key] } expect(subject.to_runner_variables).to include(*expected_variables.to_runner_variables)
expect(keys).not_to include(key)
end end
end end
it_behaves_like 'a pipeline with no dast on-demand variables' do shared_examples 'it excludes variables' do
let(:pipeline_params) { { config_source: :parameter_source } } it 'excludes variables from the profile' do
expect(subject.to_runner_variables).not_to include(*expected_variables.to_runner_variables)
end
end end
it_behaves_like 'a pipeline with no dast on-demand variables' do context 'when there is a dast_site_profile associated with the job' do
let(:pipeline_params) { { source: :ondemand_dast_scan } } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:job) { create(:ci_build, :running, pipeline: pipeline, dast_site_profile: dast_site_profile, user: user, options: options) }
context 'when feature is enabled' do
it_behaves_like 'it includes variables' do
let(:expected_variables) { dast_site_profile.ci_variables }
end
context 'when user has permission' do
it_behaves_like 'it includes variables' do
let(:expected_variables) { dast_site_profile.secret_ci_variables(user) }
end
end
context 'when user does not have permission' do
let_it_be(:user) { create(:user) }
before do
project.add_guest(user)
end
it_behaves_like 'it excludes variables' do
let(:expected_variables) { dast_site_profile.secret_ci_variables(user) }
end
end
end
context 'when feature is disabled' do
before do
stub_feature_flags(dast_configuration_ui: false)
end
it_behaves_like 'it excludes variables' do
let(:expected_variables) { dast_site_profile.ci_variables.concat(dast_site_profile.secret_ci_variables(user)) }
end
end
end end
context 'when the dast on-demand pipeline is correctly configured' do context 'when there is a dast_scanner_profile associated with the job' do
let(:pipeline_params) { { source: :ondemand_dast_scan, config_source: :parameter_source } } let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:job) { create(:ci_build, :running, pipeline: pipeline, dast_scanner_profile: dast_scanner_profile, options: options) }
it 'includes variables associated with the profile' do context 'when feature is enabled' do
expect(subject.to_runner_variables).to include(key: key, value: value, public: false, masked: true) it_behaves_like 'it includes variables' do
let(:expected_variables) { dast_scanner_profile.ci_variables }
end
end end
context 'when user cannot read secrets' do context 'when feature is disabled' do
before do before do
stub_licensed_features(security_on_demand_scans: false) stub_feature_flags(dast_configuration_ui: false)
end end
it 'does not include variables associated with the profile' do it_behaves_like 'it excludes variables' do
expect(subject.to_runner_variables).not_to include(key: key, value: value, public: false, masked: true) let(:expected_variables) { dast_scanner_profile.ci_variables }
end end
end end
end
context 'when there are profiles associated with the job' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:job) { create(:ci_build, :running, pipeline: pipeline, dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile, user: user, options: options) }
context 'when there is no user associated with the pipeline' do context 'when dast_configuration is absent from the options' do
let_it_be(:user) { nil } let(:options) { {} }
it 'does not attempt look up any dast profiles', :aggregate_failures do
expect(job).not_to receive(:dast_site_profile)
expect(job).not_to receive(:dast_scanner_profile)
subject
end
end
context 'when site_profile is absent from the dast_configuration' do
let(:options) { { dast_configuration: { scanner_profile: dast_scanner_profile.name } } }
it 'does not attempt look up the site profile' do
expect(job).not_to receive(:dast_site_profile)
subject
end
end
context 'when scanner_profile is absent from the dast_configuration' do
let(:options) { { dast_configuration: { site_profile: dast_site_profile.name } } }
it 'does not attempt look up the scanner profile' do
expect(job).not_to receive(:dast_scanner_profile)
subject
end
end
context 'when both profiles are present in the dast_configuration' do
it 'attempts look up dast profiles', :aggregate_failures do
expect(job).to receive(:dast_site_profile).and_call_original.at_least(:once)
expect(job).to receive(:dast_scanner_profile).and_call_original.at_least(:once)
subject
end
end
end
context 'when there is a dast_profile associated with the pipeline' do
let(:pipeline) { create(:ci_pipeline, pipeline_params.merge!(project: project, dast_profile: dast_profile, user: user) ) }
let(:key) { dast_site_profile_secret_variable.key }
let(:value) { dast_site_profile_secret_variable.value }
shared_examples 'a record with no associated dast variables' do
it 'does not include variables associated with the profile' do it 'does not include variables associated with the profile' do
expect(subject.to_runner_variables).not_to include(key: key, value: value, public: false, masked: true) keys = subject.to_runner_variables.map { |var| var[:key] }
expect(keys).not_to include(key)
end
end
context 'when the on-demand pipeline is incorrectly configured' do
it_behaves_like 'a record with no associated dast variables' do
let(:pipeline_params) { { config_source: :parameter_source } }
end
it_behaves_like 'a record with no associated dast variables' do
let(:pipeline_params) { { source: :ondemand_dast_scan } }
end
end
context 'when the dast on-demand pipeline is correctly configured' do
let(:pipeline_params) { { source: :ondemand_dast_scan, config_source: :parameter_source } }
it 'includes variables associated with the profile' do
expect(subject.to_runner_variables).to include(key: key, value: value, public: false, masked: true)
end
context 'when user cannot read secrets' do
before do
stub_licensed_features(security_on_demand_scans: false)
end
it 'does not include variables associated with the profile' do
expect(subject.to_runner_variables).not_to include(key: key, value: value, public: false, masked: true)
end
end
context 'when there is no user associated with the pipeline' do
let_it_be(:user) { nil }
it 'does not include variables associated with the profile' do
expect(subject.to_runner_variables).not_to include(key: key, value: value, public: false, masked: true)
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dast::ScannerProfilesBuild, type: :model do
subject { create(:dast_scanner_profiles_build) }
describe 'associations' do
it { is_expected.to belong_to(:ci_build).class_name('Ci::Build').inverse_of(:dast_scanner_profiles_build).required }
it { is_expected.to belong_to(:dast_scanner_profile).class_name('DastScannerProfile').inverse_of(:dast_scanner_profiles_builds).required }
end
describe 'validations' do
it { is_expected.to be_valid }
it { is_expected.to validate_presence_of(:ci_build_id) }
it { is_expected.to validate_presence_of(:dast_scanner_profile_id) }
context 'when the ci_build.project_id and dast_scanner_profile.project_id do not match' do
let(:ci_build) { build(:ci_build, project_id: 1) }
let(:scanner_profile) { build(:dast_scanner_profile, project_id: 2) }
subject { build(:dast_scanner_profiles_build, ci_build: ci_build, dast_scanner_profile: scanner_profile) }
it 'is not valid', :aggregate_failures do
expect(subject).not_to be_valid
expect(subject.errors.full_messages).to include('Ci build project_id must match dast_scanner_profile.project_id')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dast::SiteProfilesBuild, type: :model do
subject { create(:dast_site_profiles_build) }
describe 'associations' do
it { is_expected.to belong_to(:ci_build).class_name('Ci::Build').inverse_of(:dast_site_profiles_build).required }
it { is_expected.to belong_to(:dast_site_profile).class_name('DastSiteProfile').inverse_of(:dast_site_profiles_builds).required }
end
describe 'validations' do
it { is_expected.to be_valid }
it { is_expected.to validate_presence_of(:ci_build_id) }
it { is_expected.to validate_presence_of(:dast_site_profile_id) }
context 'when the ci_build.project_id and dast_site_profile.project_id do not match' do
let(:ci_build) { build(:ci_build, project_id: 1) }
let(:site_profile) { build(:dast_site_profile, project_id: 2) }
subject { build(:dast_site_profiles_build, ci_build: ci_build, dast_site_profile: site_profile) }
it 'is not valid', :aggregate_failures do
expect(subject).not_to be_valid
expect(subject.errors.full_messages).to include('Ci build project_id must match dast_site_profile.project_id')
end
end
end
end
...@@ -7,6 +7,8 @@ RSpec.describe DastScannerProfile, type: :model do ...@@ -7,6 +7,8 @@ RSpec.describe DastScannerProfile, type: :model do
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:dast_scanner_profiles_builds).class_name('Dast::ScannerProfilesBuild').with_foreign_key(:dast_scanner_profile_id).inverse_of(:dast_scanner_profile) }
it { is_expected.to have_many(:ci_builds).class_name('Ci::Build').through(:dast_scanner_profiles_builds) }
end end
describe 'validations' do describe 'validations' do
......
...@@ -11,6 +11,8 @@ RSpec.describe DastSiteProfile, type: :model do ...@@ -11,6 +11,8 @@ RSpec.describe DastSiteProfile, type: :model do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:dast_site) } it { is_expected.to belong_to(:dast_site) }
it { is_expected.to have_many(:secret_variables).class_name('Dast::SiteProfileSecretVariable') } it { is_expected.to have_many(:secret_variables).class_name('Dast::SiteProfileSecretVariable') }
it { is_expected.to have_many(:dast_site_profiles_builds).class_name('Dast::SiteProfilesBuild').with_foreign_key(:dast_site_profile_id).inverse_of(:dast_site_profile) }
it { is_expected.to have_many(:ci_builds).class_name('Ci::Build').through(:dast_site_profiles_builds) }
end end
describe 'validations' do describe 'validations' do
......
...@@ -28,6 +28,20 @@ RSpec.describe Ci::RetryBuildService do ...@@ -28,6 +28,20 @@ RSpec.describe Ci::RetryBuildService do
project.add_developer(user) project.add_developer(user)
end end
context 'dast' do
let(:dast_site_profile) { create(:dast_site_profile, project: project) }
let(:dast_scanner_profile) { create(:dast_scanner_profile, project: project) }
before do
build.update!(dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile)
end
it 'clones the profile associations', :aggregate_failures do
expect(new_build.dast_site_profile).to eq(dast_site_profile)
expect(new_build.dast_scanner_profile).to eq(dast_scanner_profile)
end
end
context 'when build has secrets' do context 'when build has secrets' do
let(:secrets) do let(:secrets) do
{ {
......
...@@ -30,7 +30,7 @@ RSpec.describe Ci::RetryBuildService do ...@@ -30,7 +30,7 @@ RSpec.describe Ci::RetryBuildService do
project.add_reporter(reporter) project.add_reporter(reporter)
end end
clone_accessors = described_class.clone_accessors clone_accessors = described_class.clone_accessors.without(described_class.extra_accessors)
reject_accessors = reject_accessors =
%i[id status user token token_encrypted coverage trace runner %i[id status user token token_encrypted coverage trace runner
...@@ -98,7 +98,7 @@ RSpec.describe Ci::RetryBuildService do ...@@ -98,7 +98,7 @@ RSpec.describe Ci::RetryBuildService do
end end
clone_accessors.each do |attribute| clone_accessors.each do |attribute|
it "clones #{attribute} build attribute" do it "clones #{attribute} build attribute", :aggregate_failures do
expect(attribute).not_to be_in(forbidden_associations), "association #{attribute} must be `belongs_to`" expect(attribute).not_to be_in(forbidden_associations), "association #{attribute} must be `belongs_to`"
expect(build.send(attribute)).not_to be_nil expect(build.send(attribute)).not_to be_nil
expect(new_build.send(attribute)).not_to be_nil expect(new_build.send(attribute)).not_to be_nil
...@@ -134,7 +134,7 @@ RSpec.describe Ci::RetryBuildService do ...@@ -134,7 +134,7 @@ RSpec.describe Ci::RetryBuildService do
end end
end end
it 'has correct number of known attributes' do it 'has correct number of known attributes', :aggregate_failures do
processed_accessors = clone_accessors + reject_accessors processed_accessors = clone_accessors + reject_accessors
known_accessors = processed_accessors + ignore_accessors known_accessors = processed_accessors + ignore_accessors
...@@ -146,9 +146,10 @@ RSpec.describe Ci::RetryBuildService do ...@@ -146,9 +146,10 @@ RSpec.describe Ci::RetryBuildService do
Ci::Build.attribute_names.map(&:to_sym) + Ci::Build.attribute_names.map(&:to_sym) +
Ci::Build.attribute_aliases.keys.map(&:to_sym) + Ci::Build.attribute_aliases.keys.map(&:to_sym) +
Ci::Build.reflect_on_all_associations.map(&:name) + Ci::Build.reflect_on_all_associations.map(&:name) +
[:tag_list, :needs_attributes] [:tag_list, :needs_attributes] -
# ee-specific accessors should be tested in ee/spec/services/ci/retry_build_service_spec.rb instead
current_accessors << :secrets if Gitlab.ee? described_class.extra_accessors -
[:dast_site_profiles_build, :dast_scanner_profiles_build] # join tables
current_accessors.uniq! current_accessors.uniq!
......
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