Commit d74f1d9b authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Alex Kalderimis

Extract logic to prepare DAST job variables into separate service

This change extracts logic responsible for preparing service params and
job variables into separate services so they can be used in different
context.
parent 50e06870
# frozen_string_literal: true
module Ci
module DastScanCiConfigurationService
ENV_MAPPING = {
spider_timeout: 'DAST_SPIDER_MINS',
target_timeout: 'DAST_TARGET_AVAILABILITY_TIMEOUT',
target_url: 'DAST_WEBSITE',
use_ajax_spider: 'DAST_USE_AJAX_SPIDER',
show_debug_messages: 'DAST_DEBUG',
full_scan_enabled: 'DAST_FULL_SCAN_ENABLED'
}.freeze
def self.execute(args)
variables = args.slice(*ENV_MAPPING.keys).compact.to_h do |key, val|
[ENV_MAPPING[key], to_env_value(val)]
end
{
'stages' => ['dast'],
'include' => [{ 'template' => 'DAST-On-Demand-Scan.gitlab-ci.yml' }],
'variables' => variables
}.to_yaml
end
def self.bool?(value)
!!value == value
end
private_class_method :bool?
def self.to_env_value(value)
bool?(value) ? value.to_s : value
end
private_class_method :to_env_value
end
end
...@@ -2,28 +2,6 @@ ...@@ -2,28 +2,6 @@
module Ci module Ci
class RunDastScanService < BaseService class RunDastScanService < BaseService
ENV_MAPPING = {
spider_timeout: 'DAST_SPIDER_MINS',
target_timeout: 'DAST_TARGET_AVAILABILITY_TIMEOUT',
target_url: 'DAST_WEBSITE',
use_ajax_spider: 'DAST_USE_AJAX_SPIDER',
show_debug_messages: 'DAST_DEBUG',
full_scan_enabled: 'DAST_FULL_SCAN_ENABLED'
}.freeze
def self.ci_template
@ci_template ||= YAML.safe_load(ci_template_raw)
end
def self.ci_template_raw
<<~YAML
stages:
- dast
include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml
YAML
end
def execute(branch:, **args) def execute(branch:, **args)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed? return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
...@@ -44,16 +22,7 @@ module Ci ...@@ -44,16 +22,7 @@ module Ci
end end
def ci_yaml(args) def ci_yaml(args)
variables = args.each_with_object({}) do |(key, val), hash| Ci::DastScanCiConfigurationService.execute(args)
next if val.nil? || !ENV_MAPPING[key]
hash[ENV_MAPPING[key]] = !!val == val ? val.to_s : val
hash
end
self.class.ci_template.deep_merge(
'variables' => variables
).to_yaml
end end
end end
end end
...@@ -4,7 +4,6 @@ module DastOnDemandScans ...@@ -4,7 +4,6 @@ module DastOnDemandScans
class CreateService < BaseContainerService class CreateService < BaseContainerService
def execute def execute
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed? return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
return ServiceResponse.error(message: 'Cannot run active scan against unvalidated target') unless active_scan_allowed?
create_pipeline create_pipeline
rescue KeyError => err rescue KeyError => err
...@@ -17,49 +16,6 @@ module DastOnDemandScans ...@@ -17,49 +16,6 @@ module DastOnDemandScans
container.feature_available?(:security_on_demand_scans) container.feature_available?(:security_on_demand_scans)
end end
def active_scan_allowed?
return true unless dast_scanner_profile&.full_scan_enabled?
dast_site_validation = DastSiteValidationsFinder.new(
project_id: container.id,
state: :passed,
url_base: url_base
).execute.first
dast_site_validation.present?
end
def dast_site
@dast_site ||= params.fetch(:dast_site_profile).dast_site
end
def dast_scanner_profile
@dast_scanner_profile ||= params[:dast_scanner_profile]
end
def url_base
@url_base ||= DastSiteValidation.get_normalized_url_base(dast_site.url)
end
def default_config
{
branch: container.default_branch,
target_url: dast_site.url
}
end
def scanner_profile_config
return {} unless dast_scanner_profile
{
spider_timeout: dast_scanner_profile.spider_timeout,
target_timeout: dast_scanner_profile.target_timeout,
full_scan_enabled: dast_scanner_profile.full_scan_enabled?,
use_ajax_spider: dast_scanner_profile.use_ajax_spider,
show_debug_messages: dast_scanner_profile.show_debug_messages
}
end
def success_response(pipeline) def success_response(pipeline)
pipeline_url = Rails.application.routes.url_helpers.project_pipeline_url( pipeline_url = Rails.application.routes.url_helpers.project_pipeline_url(
container, container,
...@@ -75,8 +31,11 @@ module DastOnDemandScans ...@@ -75,8 +31,11 @@ module DastOnDemandScans
end end
def create_pipeline def create_pipeline
params = default_config.merge(scanner_profile_config) params_result = DastOnDemandScans::ParamsCreateService.new(container: container, current_user: current_user, params: params).execute
result = ::Ci::RunDastScanService.new(container, current_user).execute(**params)
return params_result unless params_result.success?
result = ::Ci::RunDastScanService.new(container, current_user).execute(**params_result.payload)
return success_response(result.payload) if result.success? return success_response(result.payload) if result.success?
......
# frozen_string_literal: true
module DastOnDemandScans
class ParamsCreateService < BaseContainerService
def execute
return ServiceResponse.error(message: 'Site Profile was not provided') unless dast_site.present?
return ServiceResponse.error(message: 'Cannot run active scan against unvalidated target') unless active_scan_allowed?
ServiceResponse.success(
payload: default_config.merge(scanner_profile_config)
)
end
private
def active_scan_allowed?
return true unless dast_scanner_profile&.full_scan_enabled?
DastSiteValidationsFinder.new(
project_id: container.id,
state: :passed,
url_base: url_base
).execute.present?
end
def dast_site
@dast_site ||= params[:dast_site_profile]&.dast_site
end
def dast_scanner_profile
@dast_scanner_profile ||= params[:dast_scanner_profile]
end
def url_base
@url_base ||= DastSiteValidation.get_normalized_url_base(dast_site&.url)
end
def default_config
{
branch: container.default_branch,
target_url: dast_site&.url
}
end
def scanner_profile_config
return {} unless dast_scanner_profile
{
spider_timeout: dast_scanner_profile.spider_timeout,
target_timeout: dast_scanner_profile.target_timeout,
full_scan_enabled: dast_scanner_profile.full_scan_enabled?,
use_ajax_spider: dast_scanner_profile.use_ajax_spider,
show_debug_messages: dast_scanner_profile.show_debug_messages
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DastScanCiConfigurationService do
describe '.execute' do
subject(:yaml_configuration) { described_class.execute(params) }
context 'when all variables are provided' do
let(:params) do
{
spider_timeout: 1000,
target_timeout: 100,
target_url: 'https://gitlab.local',
use_ajax_spider: true,
show_debug_messages: true,
full_scan_enabled: true
}
end
let(:expected_yaml_configuration) do
<<~YAML
---
stages:
- dast
include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml
variables:
DAST_SPIDER_MINS: 1000
DAST_TARGET_AVAILABILITY_TIMEOUT: 100
DAST_WEBSITE: https://gitlab.local
DAST_USE_AJAX_SPIDER: 'true'
DAST_DEBUG: 'true'
DAST_FULL_SCAN_ENABLED: 'true'
YAML
end
it 'return YAML configuration of the On-Demand DAST scan' do
expect(yaml_configuration).to eq(expected_yaml_configuration)
end
end
context 'when additional variables are provided' do
let(:params) do
{
target_url: 'https://gitlab.local',
use_ajax_spider: false,
show_debug_messages: nil,
full_scan_enabled: nil,
additional_argument: true,
additional_list: ['item a']
}
end
let(:expected_yaml_configuration) do
<<~YAML
---
stages:
- dast
include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml
variables:
DAST_WEBSITE: https://gitlab.local
DAST_USE_AJAX_SPIDER: 'false'
YAML
end
it 'return YAML configuration of the On-Demand DAST scan' do
expect(yaml_configuration).to eq(expected_yaml_configuration)
end
end
context 'when no variable is provided' do
let(:params) { {} }
let(:expected_yaml_configuration) do
<<~YAML
---
stages:
- dast
include:
- template: DAST-On-Demand-Scan.gitlab-ci.yml
variables: {}
YAML
end
it 'return YAML configuration of the On-Demand DAST scan' do
expect(yaml_configuration).to eq(expected_yaml_configuration)
end
end
end
end
...@@ -15,16 +15,6 @@ RSpec.describe Ci::RunDastScanService do ...@@ -15,16 +15,6 @@ RSpec.describe Ci::RunDastScanService do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
describe '.ci_template' do
it 'builds a hash' do
expect(described_class.ci_template).to be_a(Hash)
end
it 'has only one stage' do
expect(described_class.ci_template['stages']).to eq(['dast'])
end
end
describe '#execute' do describe '#execute' do
subject { described_class.new(project, user).execute(branch: branch, target_url: target_url, spider_timeout: 42, target_timeout: 21, use_ajax_spider: use_ajax_spider, show_debug_messages: show_debug_messages, full_scan_enabled: full_scan_enabled) } subject { described_class.new(project, user).execute(branch: branch, target_url: target_url, spider_timeout: 42, target_timeout: 21, use_ajax_spider: use_ajax_spider, show_debug_messages: show_debug_messages, full_scan_enabled: full_scan_enabled) }
...@@ -140,7 +130,7 @@ RSpec.describe Ci::RunDastScanService do ...@@ -140,7 +130,7 @@ RSpec.describe Ci::RunDastScanService do
'public' => true 'public' => true
} }
] ]
expect(build.yaml_variables).to eq(expected_variables) expect(build.yaml_variables).to contain_exactly(*expected_variables)
end end
it 'enqueues a build' do it 'enqueues a build' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DastOnDemandScans::ParamsCreateService do
let_it_be(:project) { create(:project, :repository) }
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(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile } }
subject { described_class.new(container: project, params: params).execute }
describe 'execute' do
context 'when dast_site_profile is not provided' do
let(:params) { { dast_site_profile: nil, dast_scanner_profile: dast_scanner_profile } }
it 'responds with error message', :aggregate_failures do
expect(subject).not_to be_success
expect(subject.message).to eq('Site Profile was not provided')
end
end
context 'when dast_site_profile is provided' do
context 'and when dast_scanner_profile is not provided' do
let(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: nil } }
it 'returns prepared scanner params in the payload' do
expect(subject.payload).to eq(
branch: 'master',
target_url: dast_site_profile.dast_site.url
)
end
end
context 'and when dast_scanner_profile is provided' do
let(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile } }
it 'returns prepared scanner params in the payload' do
expect(subject.payload).to eq(
branch: 'master',
full_scan_enabled: false,
show_debug_messages: false,
spider_timeout: nil,
target_timeout: nil,
target_url: dast_site_profile.dast_site.url,
use_ajax_spider: false
)
end
context 'but 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(:params) { { dast_site_profile: dast_site_profile, dast_scanner_profile: active_dast_scanner_profile } }
it 'responds with error message', :aggregate_failures do
expect(subject).not_to be_success
expect(subject.message).to eq('Cannot run active scan against unvalidated target')
end
end
end
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