Commit 9c8315e2 authored by Alan Paruszewski's avatar Alan Paruszewski

Add On Demand DAST Scan Pipeline Configuration Service

This change adds new service class that is responsible for preparing the
pipeline configuration with on demand DAST scan based on the policy
defined in Policy Project.
parent 9b4c4af9
# frozen_string_literal: true
module Security
module SecurityOrchestrationPolicies
class OnDemandScanPipelineConfigurationService
include Gitlab::Utils::StrongMemoize
def initialize(project)
@project = project
end
def execute(actions)
actions
.map.with_index { |action, index| prepare_policy_configuration(action, index) }
.reduce({}, :merge)
end
private
DAST_ON_DEMAND_TEMPLATE_NAME = 'DAST-On-Demand-Scan'
attr_reader :project
def prepare_policy_configuration(action, index)
{
"dast-on-demand-#{index}" => prepare_on_demand_scan_configuration(action)
}.deep_symbolize_keys
end
def prepare_on_demand_scan_configuration(action)
result = prepare_on_demand_scan_params(action[:site_profile], action[:scanner_profile])
return error_script(result.message) unless result.success?
ci_configuration = YAML.safe_load(::Ci::DastScanCiConfigurationService.execute(result.payload))
dast_on_demand_template[:dast].deep_merge(
'variables' => dast_on_demand_template[:variables].deep_merge(ci_configuration['variables']),
'stage' => 'test'
)
end
def prepare_on_demand_scan_params(site_profile_name, scanner_profile_name)
site_profile = DastSiteProfilesFinder.new(project_id: project.id, name: site_profile_name).execute.first
scanner_profile = DastScannerProfilesFinder.new(project_ids: [project.id], name: scanner_profile_name).execute.first if scanner_profile_name.present?
DastOnDemandScans::ParamsCreateService
.new(container: project, params: { dast_site_profile: site_profile, dast_scanner_profile: scanner_profile })
.execute
end
def dast_on_demand_template
strong_memoize(:dast_on_demand_template) do
template = ::TemplateFinder.build(:gitlab_ci_ymls, nil, name: DAST_ON_DEMAND_TEMPLATE_NAME).execute
Gitlab::Config::Loader::Yaml.new(template.content).load!
end
end
def error_script(error_message)
{
'script' => "echo \"Error during On-Demand Scan execution: #{error_message}\" && false",
'allow_failure' => true
}
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::SecurityOrchestrationPolicies::OnDemandScanPipelineConfigurationService do
describe '#execute' do
let_it_be_with_reload(:project) { create(:project, :repository) }
let_it_be(:site_profile) { create(:dast_site_profile, project: project) }
let_it_be(:scanner_profile) { create(:dast_scanner_profile, project: project) }
let(:service) { described_class.new(project) }
let(:actions) do
[
{
scan: 'dast',
site_profile: site_profile.name,
scanner_profile: scanner_profile.name
},
{
scan: 'dast',
site_profile: 'Site Profile B'
}
]
end
subject(:pipeline_configuration) { service.execute(actions) }
before do
allow(DastSiteProfilesFinder).to receive(:new).and_return(double(execute: []))
allow(DastSiteProfilesFinder).to receive(:new).with(project_id: project.id, name: site_profile.name).and_return(double(execute: [site_profile]))
allow(DastScannerProfilesFinder).to receive(:new).and_return(double(execute: []))
allow(DastScannerProfilesFinder).to receive(:new).with(project_ids: [project.id], name: scanner_profile.name).and_return(double(execute: [scanner_profile]))
end
it 'uses DastSiteProfilesFinder and DastScannerProfilesFinder to find DAST profiles within the project' do
expect(DastSiteProfilesFinder).to receive(:new).with(project_id: project.id, name: site_profile.name)
expect(DastSiteProfilesFinder).to receive(:new).with(project_id: project.id, name: 'Site Profile B')
expect(DastScannerProfilesFinder).to receive(:new).with(project_ids: [project.id], name: scanner_profile.name)
pipeline_configuration
end
it 'delegates params creation to DastOnDemandScans::ParamsCreateService' do
expect(DastOnDemandScans::ParamsCreateService).to receive(:new).with(container: project, params: { dast_site_profile: site_profile, dast_scanner_profile: scanner_profile }).and_call_original
expect(DastOnDemandScans::ParamsCreateService).to receive(:new).with(container: project, params: { dast_site_profile: nil, dast_scanner_profile: nil }).and_call_original
pipeline_configuration
end
it 'delegates variables preparation to ::Ci::DastScanCiConfigurationService' do
expected_params = {
branch: project.default_branch_or_master,
full_scan_enabled: false,
show_debug_messages: false,
spider_timeout: nil,
target_timeout: nil,
target_url: site_profile.dast_site.url,
use_ajax_spider: false
}
expect(::Ci::DastScanCiConfigurationService).to receive(:execute).with(expected_params).and_call_original
pipeline_configuration
end
it 'fetches template content using ::TemplateFinder' do
expect(::TemplateFinder).to receive(:build).with(:gitlab_ci_ymls, nil, name: 'DAST-On-Demand-Scan').and_call_original
pipeline_configuration
end
it 'returns prepared CI configuration with DAST On-Demand scans defined' do
expected_configuration = {
'dast-on-demand-0': {
stage: 'test',
image: { name: '$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION' },
variables: {
DAST_VERSION: 1,
SECURE_ANALYZERS_PREFIX: 'registry.gitlab.com/gitlab-org/security-products/analyzers',
DAST_WEBSITE: site_profile.dast_site.url,
DAST_FULL_SCAN_ENABLED: 'false',
DAST_USE_AJAX_SPIDER: 'false',
DAST_DEBUG: 'false'
},
allow_failure: true,
script: ['/analyze'],
artifacts: { reports: { dast: 'gl-dast-report.json' } }
},
'dast-on-demand-1': {
script: 'echo "Error during On-Demand Scan execution: Site Profile was not provided" && false',
allow_failure: true
}
}
expect(pipeline_configuration).to eq(expected_configuration)
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