Commit 5bf33f1c authored by Mark Chao's avatar Mark Chao

Merge branch...

Merge branch 'philipcunningham-switch-over-to-build-associations-for-dast-on-demand-332942' into 'master'

Use Build relations for DAST on-demand variables

See merge request gitlab-org/gitlab!64882
parents 5251a6d7 1db05df7
# frozen_string_literal: true
class AssociateExistingDastBuildsWithVariables < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
class Profile < ApplicationRecord
self.table_name = 'dast_profiles'
self.inheritance_column = :_type_disabled
end
class ProfilesPipeline < ApplicationRecord
include EachBatch
self.table_name = 'dast_profiles_pipelines'
self.inheritance_column = :_type_disabled
belongs_to :profile, foreign_key: :dast_profile_id
end
class Build < ApplicationRecord
self.table_name = 'ci_builds'
self.inheritance_column = :_type_disabled
default_scope { where(name: :dast, stage: :dast) } # rubocop:disable Cop/DefaultScope
end
class SiteProfilesBuild < ApplicationRecord
self.table_name = 'dast_site_profiles_builds'
self.inheritance_column = :_type_disabled
end
BATCH_SIZE = 300
def up
process_batch do |batch|
bulk_inserts = []
grouped_builds = fetch_builds(batch).group_by(&:commit_id)
batch.includes(:profile).each do |profile_pipeline|
builds = grouped_builds[profile_pipeline.ci_pipeline_id]
next if builds.blank?
builds.each do |build|
bulk_inserts.push(dast_site_profile_id: profile_pipeline.profile.dast_site_profile_id, ci_build_id: build.id)
end
end
SiteProfilesBuild.insert_all(bulk_inserts, unique_by: :ci_build_id)
end
end
def down
process_batch do |batch|
builds = fetch_builds(batch)
SiteProfilesBuild
.where(ci_build_id: builds)
.delete_all
end
end
private
def fetch_builds(batch)
# pluck necessary to support ci table decomposition
# https://gitlab.com/groups/gitlab-org/-/epics/6289
Build.where(commit_id: batch.pluck(:ci_pipeline_id))
end
def process_batch
ProfilesPipeline.each_batch(of: BATCH_SIZE, column: :ci_pipeline_id) do |batch|
yield(batch)
end
end
end
4f20581b0d16157fbe984383417f0463d7e52252569480796aa3c73abf19c95f
\ No newline at end of file
# frozen_string_literal: true
module Dast
class SiteProfilesPipeline < ApplicationRecord
extend SuppressCompositePrimaryKeyWarning
self.table_name = 'dast_site_profiles_pipelines'
belongs_to :ci_pipeline, class_name: 'Ci::Pipeline', optional: false
belongs_to :dast_site_profile, class_name: 'DastSiteProfile', optional: false
validates :ci_pipeline_id, :dast_site_profile_id, presence: true
validate :project_ids_match
private
def project_ids_match
return if ci_pipeline.nil? || dast_site_profile.nil?
unless ci_pipeline.project_id == dast_site_profile.project_id
errors.add(:ci_pipeline_id, 'project_id must match dast_site_profile.project_id')
end
end
end
end
...@@ -62,7 +62,6 @@ module EE ...@@ -62,7 +62,6 @@ module EE
def variables def variables
strong_memoize(:variables) do strong_memoize(:variables) do
super.tap do |collection| super.tap do |collection|
collection.concat(dast_on_demand_variables)
collection.concat(dast_configuration_variables) collection.concat(dast_configuration_variables)
end end
end end
...@@ -195,19 +194,6 @@ module EE ...@@ -195,19 +194,6 @@ 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
break collection unless profile
collection.concat(profile.secret_ci_variables(pipeline.user))
end
end
def dast_configuration_variables def dast_configuration_variables
::Gitlab::Ci::Variables::Collection.new.tap do |collection| ::Gitlab::Ci::Variables::Collection.new.tap do |collection|
break collection unless (dast_configuration = options[:dast_configuration]) break collection unless (dast_configuration = options[:dast_configuration])
......
...@@ -23,10 +23,6 @@ module EE ...@@ -23,10 +23,6 @@ module EE
has_one :dast_profiles_pipeline, class_name: 'Dast::ProfilesPipeline', foreign_key: :ci_pipeline_id has_one :dast_profiles_pipeline, class_name: 'Dast::ProfilesPipeline', foreign_key: :ci_pipeline_id
has_one :dast_profile, class_name: 'Dast::Profile', through: :dast_profiles_pipeline has_one :dast_profile, class_name: 'Dast::Profile', through: :dast_profiles_pipeline
# Temporary location to be moved in the future. Please see gitlab-org/gitlab#330950 for more info.
has_one :dast_site_profiles_pipeline, class_name: 'Dast::SiteProfilesPipeline', foreign_key: :ci_pipeline_id
has_one :dast_site_profile, class_name: 'DastSiteProfile', through: :dast_site_profiles_pipeline
has_one :source_project, class_name: 'Ci::Sources::Project', foreign_key: :pipeline_id has_one :source_project, class_name: 'Ci::Sources::Project', foreign_key: :pipeline_id
# Legacy way to fetch security reports based on job name. This has been replaced by the reports feature. # Legacy way to fetch security reports based on job name. This has been replaced by the reports feature.
......
...@@ -13,8 +13,6 @@ module AppSec ...@@ -13,8 +13,6 @@ module AppSec
ServiceResponse.success( ServiceResponse.success(
payload: { payload: {
dast_profile: dast_profile, dast_profile: dast_profile,
dast_site_profile: dast_site_profile,
dast_scanner_profile: dast_scanner_profile,
branch: branch, branch: branch,
ci_configuration: ci_configuration ci_configuration: ci_configuration
} }
...@@ -45,16 +43,12 @@ module AppSec ...@@ -45,16 +43,12 @@ module AppSec
{ {
'stages' => ['dast'], 'stages' => ['dast'],
'include' => [{ 'template' => 'DAST-On-Demand-Scan.gitlab-ci.yml' }], 'include' => [{ 'template' => 'DAST-On-Demand-Scan.gitlab-ci.yml' }],
'variables' => ci_variables.to_hash.to_hash # Collection#to_hash returns HashWithIndifferentAccess which does not serialise correctly 'dast' => {
'dast_configuration' => { 'site_profile' => dast_site_profile.name, 'scanner_profile' => dast_scanner_profile&.name }.compact
}
}.to_yaml }.to_yaml
end end
def ci_variables
dast_site_profile.ci_variables.tap do |collection|
collection.concat(dast_scanner_profile.ci_variables) if dast_scanner_profile
end
end
def dast_profile def dast_profile
strong_memoize(:dast_profile) do strong_memoize(:dast_profile) do
params[:dast_profile] params[:dast_profile]
......
...@@ -8,12 +8,7 @@ module Ci ...@@ -8,12 +8,7 @@ module Ci
service = Ci::CreatePipelineService.new(project, current_user, ref: branch) service = Ci::CreatePipelineService.new(project, current_user, ref: branch)
response = service.execute(:ondemand_dast_scan, content: ci_configuration) do |pipeline| response = service.execute(:ondemand_dast_scan, content: ci_configuration) do |pipeline|
if dast_profile
pipeline.dast_profile = dast_profile pipeline.dast_profile = dast_profile
else
# Subject to change. Please see gitlab-org/gitlab#330950 for more info.
pipeline.dast_site_profile = dast_site_profile
end
end end
pipeline = response.payload pipeline = response.payload
......
...@@ -34,8 +34,9 @@ module Security ...@@ -34,8 +34,9 @@ module Security
ci_configuration = YAML.safe_load(result.payload[:ci_configuration]) ci_configuration = YAML.safe_load(result.payload[:ci_configuration])
dast_on_demand_template[:dast].deep_merge( dast_on_demand_template[:dast].deep_merge(
'variables' => dast_on_demand_template[:variables].deep_merge(ci_configuration['variables']), 'stage' => 'dast',
'stage' => 'test' 'variables' => dast_on_demand_template[:variables].merge(dast_on_demand_template[:dast][:variables]),
'dast_configuration' => ci_configuration['dast']['dast_configuration']
) )
end end
......
# frozen_string_literal: true
FactoryBot.define do
factory :dast_site_profiles_pipeline, class: 'Dast::SiteProfilesPipeline' do
dast_site_profile
ci_pipeline { association :ci_pipeline, project: dast_site_profile.project }
end
end
...@@ -78,10 +78,8 @@ RSpec.describe Mutations::DastOnDemandScans::Create do ...@@ -78,10 +78,8 @@ RSpec.describe Mutations::DastOnDemandScans::Create do
it 'passes additional arguments to the underlying service object' do it 'passes additional arguments to the underlying service object' do
args = hash_including( args = hash_including(
branch: project.default_branch,
dast_profile: nil, dast_profile: nil,
dast_site_profile: dast_site_profile, branch: project.default_branch,
dast_scanner_profile: dast_scanner_profile,
ci_configuration: kind_of(String) ci_configuration: kind_of(String)
) )
......
...@@ -126,24 +126,20 @@ RSpec.describe Gitlab::Ci::Config do ...@@ -126,24 +126,20 @@ RSpec.describe Gitlab::Ci::Config do
script: ["echo 'test'"] script: ["echo 'test'"]
}, },
'dast-on-demand-0': { 'dast-on-demand-0': {
stage: 'test', stage: 'dast',
image: { name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' }, image: { name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' },
variables: { variables: {
DAST_AUTH_URL: dast_site_profile.auth_url,
DAST_VERSION: 2, DAST_VERSION: 2,
SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix, SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix,
DAST_WEBSITE: dast_site_profile.dast_site.url, GIT_STRATEGY: 'none'
DAST_FULL_SCAN_ENABLED: 'false',
DAST_USE_AJAX_SPIDER: 'false',
DAST_DEBUG: 'false',
DAST_USERNAME: dast_site_profile.auth_username,
DAST_EXCLUDE_URLS: dast_site_profile.excluded_urls.join(','),
DAST_USERNAME_FIELD: 'session[username]',
DAST_PASSWORD_FIELD: 'session[password]'
}, },
allow_failure: true, allow_failure: true,
script: ['/analyze'], script: ['/analyze'],
artifacts: { reports: { dast: 'gl-dast-report.json' } } artifacts: { reports: { dast: 'gl-dast-report.json' } },
dast_configuration: {
site_profile: dast_site_profile.name,
scanner_profile: dast_scanner_profile.name
}
} }
} }
end end
......
...@@ -126,22 +126,14 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do ...@@ -126,22 +126,14 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do
{ {
image: 'ruby:3.0.1', image: 'ruby:3.0.1',
'dast-on-demand-0': { 'dast-on-demand-0': {
stage: 'test', stage: 'dast',
image: { image: {
name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION'
}, },
variables: { variables: {
DAST_AUTH_URL: dast_site_profile.auth_url,
DAST_VERSION: 2, DAST_VERSION: 2,
SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix, SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix,
DAST_WEBSITE: dast_site_profile.dast_site.url, GIT_STRATEGY: 'none'
DAST_FULL_SCAN_ENABLED: 'false',
DAST_USE_AJAX_SPIDER: 'false',
DAST_DEBUG: 'false',
DAST_USERNAME: dast_site_profile.auth_username,
DAST_EXCLUDE_URLS: dast_site_profile.excluded_urls.join(','),
DAST_USERNAME_FIELD: 'session[username]',
DAST_PASSWORD_FIELD: 'session[password]'
}, },
allow_failure: true, allow_failure: true,
script: ['/analyze'], script: ['/analyze'],
...@@ -149,6 +141,10 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do ...@@ -149,6 +141,10 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do
reports: { reports: {
dast: 'gl-dast-report.json' dast: 'gl-dast-report.json'
} }
},
dast_configuration: {
site_profile: dast_site_profile.name,
scanner_profile: dast_scanner_profile.name
} }
} }
} }
......
...@@ -138,7 +138,6 @@ RSpec.describe Ci::Build do ...@@ -138,7 +138,6 @@ RSpec.describe Ci::Build do
let_it_be(:user) { create(:user, developer_projects: [project]) } let_it_be(:user) { create(:user, developer_projects: [project]) }
let_it_be(:dast_site_profile) { create(:dast_site_profile, project: project) } let_it_be(:dast_site_profile) { create(:dast_site_profile, project: project) }
let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile, project: project) } 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_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(: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_it_be(:options) { { dast_configuration: { site_profile: dast_site_profile.name, scanner_profile: dast_scanner_profile.name } } }
...@@ -162,7 +161,6 @@ RSpec.describe Ci::Build do ...@@ -162,7 +161,6 @@ RSpec.describe Ci::Build do
let(:pipeline) { create(:ci_pipeline, project: project) } 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) } 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 it_behaves_like 'it includes variables' do
let(:expected_variables) { dast_site_profile.ci_variables } let(:expected_variables) { dast_site_profile.ci_variables }
end end
...@@ -172,31 +170,16 @@ RSpec.describe Ci::Build do ...@@ -172,31 +170,16 @@ RSpec.describe Ci::Build do
let(:expected_variables) { dast_site_profile.secret_ci_variables(user) } let(:expected_variables) { dast_site_profile.secret_ci_variables(user) }
end end
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
end end
context 'when there is a dast_scanner_profile associated with the job' do context 'when there is a dast_scanner_profile associated with the job' do
let(:pipeline) { create(:ci_pipeline, project: project, user: user) } 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) } let(:job) { create(:ci_build, :running, pipeline: pipeline, dast_scanner_profile: dast_scanner_profile, options: options) }
context 'when feature is enabled' do
it_behaves_like 'it includes variables' do it_behaves_like 'it includes variables' do
let(:expected_variables) { dast_scanner_profile.ci_variables } let(:expected_variables) { dast_scanner_profile.ci_variables }
end end
end end
end
context 'when there are profiles associated with the job' do context 'when there are profiles associated with the job' do
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
...@@ -242,56 +225,6 @@ RSpec.describe Ci::Build do ...@@ -242,56 +225,6 @@ RSpec.describe Ci::Build do
end end
end 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
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 end
......
...@@ -20,8 +20,6 @@ RSpec.describe Ci::Pipeline do ...@@ -20,8 +20,6 @@ RSpec.describe Ci::Pipeline do
it { is_expected.to have_many(:vulnerabilities_finding_pipelines).class_name('Vulnerabilities::FindingPipeline') } it { is_expected.to have_many(:vulnerabilities_finding_pipelines).class_name('Vulnerabilities::FindingPipeline') }
it { is_expected.to have_one(:dast_profiles_pipeline).class_name('Dast::ProfilesPipeline').with_foreign_key(:ci_pipeline_id) } it { is_expected.to have_one(:dast_profiles_pipeline).class_name('Dast::ProfilesPipeline').with_foreign_key(:ci_pipeline_id) }
it { is_expected.to have_one(:dast_profile).class_name('Dast::Profile').through(:dast_profiles_pipeline) } it { is_expected.to have_one(:dast_profile).class_name('Dast::Profile').through(:dast_profiles_pipeline) }
it { is_expected.to have_one(:dast_site_profiles_pipeline).class_name('Dast::SiteProfilesPipeline').with_foreign_key(:ci_pipeline_id) }
it { is_expected.to have_one(:dast_site_profile).class_name('DastSiteProfile').through(:dast_site_profiles_pipeline) }
end end
describe '.failure_reasons' do describe '.failure_reasons' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dast::SiteProfilesPipeline, type: :model do
subject { create(:dast_site_profiles_pipeline) }
describe 'associations' do
it { is_expected.to belong_to(:ci_pipeline).class_name('Ci::Pipeline').required }
it { is_expected.to belong_to(:dast_site_profile).class_name('DastSiteProfile').required }
end
describe 'validations' do
it { is_expected.to be_valid }
it { is_expected.to validate_presence_of(:ci_pipeline_id) }
it { is_expected.to validate_presence_of(:dast_site_profile_id) }
context 'when the ci_pipeline.project_id and dast_site_profile.project_id do not match' do
let(:pipeline) { build(:ci_pipeline, project_id: 1) }
let(:site_profile) { build(:dast_site_profile, project_id: 2) }
subject { build(:dast_site_profiles_pipeline, ci_pipeline: pipeline, 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 pipeline project_id must match dast_site_profile.project_id')
end
end
end
end
...@@ -29,18 +29,10 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do ...@@ -29,18 +29,10 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do
- dast - dast
include: include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml - template: DAST-On-Demand-Scan.gitlab-ci.yml
variables: dast:
DAST_WEBSITE: #{dast_website} dast_configuration:
DAST_EXCLUDE_URLS: #{dast_exclude_urls} site_profile: #{dast_site_profile.name}
DAST_AUTH_URL: #{dast_auth_url} scanner_profile: #{dast_scanner_profile.name}
DAST_USERNAME: #{dast_username}
DAST_USERNAME_FIELD: #{dast_username_field}
DAST_PASSWORD_FIELD: #{dast_password_field}
DAST_SPIDER_MINS: '#{dast_spider_mins}'
DAST_TARGET_AVAILABILITY_TIMEOUT: '#{dast_target_availability_timeout}'
DAST_FULL_SCAN_ENABLED: '#{dast_full_scan_enabled}'
DAST_USE_AJAX_SPIDER: '#{dast_use_ajax_spider}'
DAST_DEBUG: '#{dast_debug}'
YAML YAML
end end
...@@ -50,11 +42,9 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do ...@@ -50,11 +42,9 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do
context 'when a dast_profile is provided' do context 'when a dast_profile is provided' do
let(:params) { { dast_profile: dast_profile } } let(:params) { { dast_profile: dast_profile } }
it 'returns a dast_profile, dast_site_profile, dast_scanner_profile, branch and YAML configuration' do it 'returns a dast_profile, branch and YAML configuration' do
expected_payload = { expected_payload = {
dast_profile: dast_profile, dast_profile: dast_profile,
dast_site_profile: dast_site_profile,
dast_scanner_profile: dast_scanner_profile,
branch: dast_profile.branch_name, branch: dast_profile.branch_name,
ci_configuration: expected_yaml_configuration ci_configuration: expected_yaml_configuration
} }
...@@ -64,20 +54,22 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do ...@@ -64,20 +54,22 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do
end end
context 'when a dast_site_profile is provided' do context 'when a dast_site_profile is provided' do
context 'when a dast_scanner_profile is provided' do shared_examples 'a payload without a dast_profile' do
let(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile } } it 'returns a branch and YAML configuration' do
it 'returns a dast_site_profile, dast_scanner_profile, branch and YAML configuration' do
expected_payload = { expected_payload = {
dast_profile: nil, dast_profile: nil,
dast_site_profile: dast_site_profile, branch: dast_profile.branch_name,
dast_scanner_profile: dast_scanner_profile,
branch: project.default_branch,
ci_configuration: expected_yaml_configuration ci_configuration: expected_yaml_configuration
} }
expect(subject.payload).to eq(expected_payload) expect(subject.payload).to eq(expected_payload)
end end
end
context 'when a dast_scanner_profile is provided' do
let(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile } }
it_behaves_like 'a payload without a dast_profile'
context 'when the target is not validated and an active scan is requested' do context 'when the target is not validated and an active scan is requested' do
let_it_be(:active_dast_scanner_profile) { create(:dast_scanner_profile, project: project, scan_type: 'active') } let_it_be(:active_dast_scanner_profile) { create(:dast_scanner_profile, project: project, scan_type: 'active') }
...@@ -101,27 +93,13 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do ...@@ -101,27 +93,13 @@ RSpec.describe AppSec::Dast::ScanConfigs::BuildService do
- dast - dast
include: include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml - template: DAST-On-Demand-Scan.gitlab-ci.yml
variables: dast:
DAST_WEBSITE: #{dast_website} dast_configuration:
DAST_EXCLUDE_URLS: #{dast_exclude_urls} site_profile: #{dast_site_profile.name}
DAST_AUTH_URL: #{dast_auth_url}
DAST_USERNAME: #{dast_username}
DAST_USERNAME_FIELD: #{dast_username_field}
DAST_PASSWORD_FIELD: #{dast_password_field}
YAML YAML
end end
it 'returns a dast_site_profile, branch and YAML configuration' do it_behaves_like 'a payload without a dast_profile'
expected_payload = {
dast_profile: nil,
dast_site_profile: dast_site_profile,
dast_scanner_profile: nil,
branch: project.default_branch,
ci_configuration: expected_yaml_configuration
}
expect(subject.payload).to eq(expected_payload)
end
end end
end end
......
...@@ -20,11 +20,7 @@ RSpec.describe Ci::RunDastScanService do ...@@ -20,11 +20,7 @@ RSpec.describe Ci::RunDastScanService do
config_result = AppSec::Dast::ScanConfigs::BuildService.new( config_result = AppSec::Dast::ScanConfigs::BuildService.new(
container: project, container: project,
current_user: user, current_user: user,
params: { params: { branch: project.default_branch, dast_profile: dast_profile }
branch: project.default_branch,
dast_profile: dast_profile,
dast_site_profile: dast_site_profile
}
).execute ).execute
described_class.new(project, user).execute(**config_result.payload) described_class.new(project, user).execute(**config_result.payload)
...@@ -84,6 +80,7 @@ RSpec.describe Ci::RunDastScanService do ...@@ -84,6 +80,7 @@ RSpec.describe Ci::RunDastScanService do
it 'creates a build with appropriate options' do it 'creates a build with appropriate options' do
build = pipeline.builds.first build = pipeline.builds.first
expected_options = { expected_options = {
image: { image: {
name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION'
...@@ -95,8 +92,10 @@ RSpec.describe Ci::RunDastScanService do ...@@ -95,8 +92,10 @@ RSpec.describe Ci::RunDastScanService do
reports: { reports: {
dast: ['gl-dast-report.json'] dast: ['gl-dast-report.json']
} }
},
dast_configuration: { site_profile: dast_site_profile.name, scanner_profile: dast_scanner_profile.name }
} }
}
expect(build.options).to eq(expected_options) expect(build.options).to eq(expected_options)
end end
...@@ -107,81 +106,77 @@ RSpec.describe Ci::RunDastScanService do ...@@ -107,81 +106,77 @@ RSpec.describe Ci::RunDastScanService do
{ {
key: 'DAST_AUTH_URL', key: 'DAST_AUTH_URL',
value: dast_site_profile.auth_url, value: dast_site_profile.auth_url,
public: true public: true,
masked: false
}, { }, {
key: 'DAST_DEBUG', key: 'DAST_DEBUG',
value: String(dast_scanner_profile.show_debug_messages?), value: String(dast_scanner_profile.show_debug_messages?),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_EXCLUDE_URLS', key: 'DAST_EXCLUDE_URLS',
value: dast_site_profile.excluded_urls.join(','), value: dast_site_profile.excluded_urls.join(','),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_FULL_SCAN_ENABLED', key: 'DAST_FULL_SCAN_ENABLED',
value: String(dast_scanner_profile.full_scan_enabled?), value: String(dast_scanner_profile.full_scan_enabled?),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_PASSWORD_FIELD', key: 'DAST_PASSWORD_FIELD',
value: dast_site_profile.auth_password_field, value: dast_site_profile.auth_password_field,
public: true public: true,
masked: false
}, { }, {
key: 'DAST_SPIDER_MINS', key: 'DAST_SPIDER_MINS',
value: String(dast_scanner_profile.spider_timeout), value: String(dast_scanner_profile.spider_timeout),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_TARGET_AVAILABILITY_TIMEOUT', key: 'DAST_TARGET_AVAILABILITY_TIMEOUT',
value: String(dast_scanner_profile.target_timeout), value: String(dast_scanner_profile.target_timeout),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_USERNAME', key: 'DAST_USERNAME',
value: dast_site_profile.auth_username, value: dast_site_profile.auth_username,
public: true public: true,
masked: false
}, { }, {
key: 'DAST_USERNAME_FIELD', key: 'DAST_USERNAME_FIELD',
value: dast_site_profile.auth_username_field, value: dast_site_profile.auth_username_field,
public: true public: true,
masked: false
}, { }, {
key: 'DAST_USE_AJAX_SPIDER', key: 'DAST_USE_AJAX_SPIDER',
value: String(dast_scanner_profile.use_ajax_spider?), value: String(dast_scanner_profile.use_ajax_spider?),
public: true public: true,
masked: false
}, { }, {
key: 'DAST_VERSION', key: 'DAST_VERSION',
value: '2', value: '2',
public: true public: true,
masked: false
}, { }, {
key: 'DAST_WEBSITE', key: 'DAST_WEBSITE',
value: dast_site_profile.dast_site.url, value: dast_site_profile.dast_site.url,
public: true public: true,
masked: false
}, { }, {
key: 'GIT_STRATEGY', key: 'GIT_STRATEGY',
value: 'none', value: 'none',
public: true public: true,
masked: false
}, { }, {
key: 'SECURE_ANALYZERS_PREFIX', key: 'SECURE_ANALYZERS_PREFIX',
value: secure_analyzers_prefix, value: secure_analyzers_prefix,
public: true public: true,
masked: false
} }
] ]
expect(build.yaml_variables).to contain_exactly(*expected_variables) expect(build.variables.to_runner_variables).to include(*expected_variables)
end
context 'when the dast_profile and dast_site_profile are provided' do
it 'associates the dast_profile with the pipeline' do
expect(pipeline.dast_profile).to eq(dast_profile)
end
it 'does associate the dast_site_profile with the pipeline' do
expect(pipeline.dast_site_profile).to be_nil
end
end
context 'when the dast_site_profile is provided' do
let(:dast_profile) { nil }
it 'associates the dast_site_profile with the pipeline' do
expect(pipeline.dast_site_profile).to eq(dast_site_profile)
end
end end
context 'when the pipeline fails to save' do context 'when the pipeline fails to save' do
......
...@@ -64,9 +64,8 @@ RSpec.describe DastOnDemandScans::CreateService do ...@@ -64,9 +64,8 @@ RSpec.describe DastOnDemandScans::CreateService do
it_behaves_like 'a service that calls Ci::RunDastScanService' do it_behaves_like 'a service that calls Ci::RunDastScanService' do
let(:expected_params) do let(:expected_params) do
hash_including( hash_including(
dast_profile: nil,
branch: project.default_branch, branch: project.default_branch,
dast_site_profile: dast_site_profile,
dast_scanner_profile: dast_scanner_profile,
ci_configuration: kind_of(String) ci_configuration: kind_of(String)
) )
end end
......
...@@ -59,24 +59,17 @@ RSpec.describe Security::SecurityOrchestrationPolicies::OnDemandScanPipelineConf ...@@ -59,24 +59,17 @@ RSpec.describe Security::SecurityOrchestrationPolicies::OnDemandScanPipelineConf
it 'returns prepared CI configuration with DAST On-Demand scans defined' do it 'returns prepared CI configuration with DAST On-Demand scans defined' do
expected_configuration = { expected_configuration = {
'dast-on-demand-0': { 'dast-on-demand-0': {
stage: 'test', stage: 'dast',
image: { name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' }, image: { name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' },
variables: { variables: {
DAST_AUTH_URL: site_profile.auth_url,
DAST_DEBUG: 'false',
DAST_EXCLUDE_URLS: site_profile.excluded_urls.join(','),
DAST_FULL_SCAN_ENABLED: 'false',
DAST_PASSWORD_FIELD: site_profile.auth_password_field,
DAST_USERNAME: site_profile.auth_username,
DAST_USERNAME_FIELD: site_profile.auth_username_field,
DAST_USE_AJAX_SPIDER: 'false',
DAST_VERSION: 2, DAST_VERSION: 2,
DAST_WEBSITE: site_profile.dast_site.url, SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix,
SECURE_ANALYZERS_PREFIX: secure_analyzers_prefix GIT_STRATEGY: 'none'
}, },
allow_failure: true, allow_failure: true,
script: ['/analyze'], script: ['/analyze'],
artifacts: { reports: { dast: 'gl-dast-report.json' } } artifacts: { reports: { dast: 'gl-dast-report.json' } },
dast_configuration: { site_profile: site_profile.name, scanner_profile: scanner_profile.name }
}, },
'dast-on-demand-1': { 'dast-on-demand-1': {
script: 'echo "Error during On-Demand Scan execution: Dast site profile was not provided" && false', script: 'echo "Error during On-Demand Scan execution: Dast site profile was not provided" && false',
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20210629031900_associate_existing_dast_builds_with_variables.rb')
RSpec.describe AssociateExistingDastBuildsWithVariables do
subject(:migration) { described_class.new }
let_it_be(:namespaces_table) { table(:namespaces) }
let_it_be(:projects_table) { table(:projects) }
let_it_be(:ci_pipelines_table) { table(:ci_pipelines) }
let_it_be(:ci_builds_table) { table(:ci_builds) }
let_it_be(:dast_sites_table) { table(:dast_sites) }
let_it_be(:dast_site_profiles_table) { table(:dast_site_profiles) }
let_it_be(:dast_scanner_profiles_table) { table(:dast_scanner_profiles) }
let_it_be(:dast_site_profiles_builds_table) { table(:dast_site_profiles_builds) }
let_it_be(:dast_profiles_table) { table(:dast_profiles) }
let_it_be(:dast_profiles_pipelines_table) { table(:dast_profiles_pipelines) }
let!(:group) { namespaces_table.create!(type: 'Group', name: 'group', path: 'group') }
let!(:project) { projects_table.create!(name: 'project', path: 'project', namespace_id: group.id) }
let!(:pipeline_0) { ci_pipelines_table.create!(project_id: project.id, source: 13) }
let!(:pipeline_1) { ci_pipelines_table.create!(project_id: project.id, source: 13) }
let!(:build_0) { ci_builds_table.create!(project_id: project.id, commit_id: pipeline_0.id, name: :dast, stage: :dast) }
let!(:build_1) { ci_builds_table.create!(project_id: project.id, commit_id: pipeline_0.id, name: :dast, stage: :dast) }
let!(:build_2) { ci_builds_table.create!(project_id: project.id, commit_id: pipeline_1.id, name: :dast, stage: :dast) }
let!(:build_3) { ci_builds_table.create!(project_id: project.id, commit_id: pipeline_1.id, name: :dast) }
let!(:build_4) { ci_builds_table.create!(project_id: project.id, commit_id: pipeline_1.id, stage: :dast) }
let!(:dast_site) { dast_sites_table.create!(project_id: project.id, url: generate(:url)) }
let!(:dast_site_profile) { dast_site_profiles_table.create!(project_id: project.id, dast_site_id: dast_site.id, name: SecureRandom.hex) }
let!(:dast_scanner_profile) { dast_scanner_profiles_table.create!(project_id: project.id, name: SecureRandom.hex) }
let!(:dast_profile) do
dast_profiles_table.create!(
project_id: project.id,
dast_site_profile_id: dast_site_profile.id,
dast_scanner_profile_id: dast_scanner_profile.id,
name: SecureRandom.hex,
description: SecureRandom.hex
)
end
let!(:dast_profiles_pipeline_0) { dast_profiles_pipelines_table.create!(dast_profile_id: dast_profile.id, ci_pipeline_id: pipeline_0.id) }
let!(:dast_profiles_pipeline_1) { dast_profiles_pipelines_table.create!(dast_profile_id: dast_profile.id, ci_pipeline_id: pipeline_1.id) }
context 'when there are ci_pipelines with associated dast_profiles' do
describe 'migration up' do
it 'adds association of dast_site_profiles to ci_builds', :aggregate_failures do
expect(dast_site_profiles_builds_table.all).to be_empty
migration.up
expected_results = [
[dast_site_profile.id, build_0.id],
[dast_site_profile.id, build_1.id],
[dast_site_profile.id, build_2.id]
]
expect(dast_site_profiles_builds_table.all.map { |assoc| [assoc.dast_site_profile_id, assoc.ci_build_id] }).to contain_exactly(*expected_results)
end
end
end
describe 'migration down' do
it 'deletes all records in the dast_site_profiles_builds table', :aggregate_failures do
expect(dast_site_profiles_builds_table.all).to be_empty
migration.up
migration.down
expect(dast_site_profiles_builds_table.all).to be_empty
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