Commit ea385057 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'use-existing-scanner-profile-for-scans-238448' into 'master'

Use dast_scanner_profiles in DAST on-demand scans

See merge request gitlab-org/gitlab!41060
parents bf0daa4c 168b0e8a
...@@ -2913,6 +2913,11 @@ input DastOnDemandScanCreateInput { ...@@ -2913,6 +2913,11 @@ input DastOnDemandScanCreateInput {
""" """
clientMutationId: String clientMutationId: String
"""
ID of the scanner profile to be used for the scan.
"""
dastScannerProfileId: DastScannerProfileID
""" """
ID of the site profile to be used for the scan. ID of the site profile to be used for the scan.
""" """
......
...@@ -7889,6 +7889,16 @@ ...@@ -7889,6 +7889,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "dastScannerProfileId",
"description": "ID of the scanner profile to be used for the scan.",
"type": {
"kind": "SCALAR",
"name": "DastScannerProfileID",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
...@@ -21,16 +21,27 @@ module Mutations ...@@ -21,16 +21,27 @@ module Mutations
required: true, required: true,
description: 'ID of the site profile to be used for the scan.' description: 'ID of the site profile to be used for the scan.'
argument :dast_scanner_profile_id, ::Types::GlobalIDType[::DastScannerProfile],
required: false,
description: 'ID of the scanner profile to be used for the scan.'
authorize :create_on_demand_dast_scan authorize :create_on_demand_dast_scan
def resolve(full_path:, dast_site_profile_id:) def resolve(full_path:, dast_site_profile_id:, **args)
project = authorized_find_project!(full_path: full_path) project = authorized_find_project!(full_path: full_path)
dast_site_profile = find_dast_site_profile(project: project, dast_site_profile_id: dast_site_profile_id) dast_site_profile = find_dast_site_profile(project: project, dast_site_profile_id: dast_site_profile_id)
dast_site = dast_site_profile.dast_site dast_site = dast_site_profile.dast_site
dast_scanner_profile = find_dast_scanner_profile(project: project, dast_scanner_profile_id: args[:dast_scanner_profile_id])
service = ::Ci::RunDastScanService.new(project, current_user) result = ::Ci::RunDastScanService.new(
result = service.execute(branch: project.default_branch, target_url: dast_site.url) project, current_user
).execute(
branch: project.default_branch,
target_url: dast_site.url,
spider_timeout: dast_scanner_profile&.spider_timeout,
target_timeout: dast_scanner_profile&.target_timeout
)
if result.success? if result.success?
success_response(project: project, pipeline: result.payload) success_response(project: project, pipeline: result.payload)
...@@ -41,11 +52,20 @@ module Mutations ...@@ -41,11 +52,20 @@ module Mutations
private private
# rubocop: disable CodeReuse/ActiveRecord
def find_dast_site_profile(project:, dast_site_profile_id:) def find_dast_site_profile(project:, dast_site_profile_id:)
DastSiteProfilesFinder.new(project_id: project.id, id: dast_site_profile_id.model_id)
.execute
.first!
end
# rubocop: enable CodeReuse/ActiveRecord
def find_dast_scanner_profile(project:, dast_scanner_profile_id:)
return unless dast_scanner_profile_id
project project
.dast_site_profiles .dast_scanner_profiles
.with_dast_site .find(dast_scanner_profile_id.model_id)
.find(dast_site_profile_id.model_id)
end end
def success_response(project:, pipeline:) def success_response(project:, pipeline:)
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
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'
}.freeze
def self.ci_template_raw def self.ci_template_raw
@ci_template_raw ||= Gitlab::Template::GitlabCiYmlTemplate.find('DAST').content @ci_template_raw ||= Gitlab::Template::GitlabCiYmlTemplate.find('DAST').content
end end
...@@ -13,11 +19,11 @@ module Ci ...@@ -13,11 +19,11 @@ module Ci
end end
end end
def execute(branch:, target_url:) def execute(branch:, **args)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed? return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
service = Ci::CreatePipelineService.new(project, current_user, ref: branch) service = Ci::CreatePipelineService.new(project, current_user, ref: branch)
pipeline = service.execute(:ondemand_dast_scan, content: ci_yaml(target_url)) pipeline = service.execute(:ondemand_dast_scan, content: ci_yaml(args))
if pipeline.created_successfully? if pipeline.created_successfully?
ServiceResponse.success(payload: pipeline) ServiceResponse.success(payload: pipeline)
...@@ -32,9 +38,16 @@ module Ci ...@@ -32,9 +38,16 @@ module Ci
Ability.allowed?(current_user, :create_on_demand_dast_scan, project) Ability.allowed?(current_user, :create_on_demand_dast_scan, project)
end end
def ci_yaml(target_url) def ci_yaml(args)
variables = args.each_with_object({}) do |(key, val), hash|
next unless val && ENV_MAPPING[key]
hash[ENV_MAPPING[key]] = val
hash
end
self.class.ci_template.deep_merge( self.class.ci_template.deep_merge(
'variables' => { 'DAST_WEBSITE' => target_url } 'variables' => variables
).to_yaml ).to_yaml
end end
end end
......
---
title: Use dast_scanner_profiles in DAST on-demand scans
merge_request: 41060
author:
type: added
...@@ -102,6 +102,36 @@ RSpec.describe Mutations::DastOnDemandScans::Create do ...@@ -102,6 +102,36 @@ RSpec.describe Mutations::DastOnDemandScans::Create do
end end
end end
context 'when dast_scanner_profile_id is provided' do
let(:dast_scanner_profile) { create(:dast_scanner_profile, project: project, target_timeout: 200, spider_timeout: 5000) }
let(:dast_scanner_profile_id) { dast_scanner_profile.to_global_id }
subject do
mutation.resolve(
full_path: full_path,
dast_site_profile_id: dast_site_profile_id,
dast_scanner_profile_id: dast_scanner_profile_id
)
end
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
it 'passes additional arguments to the underlying service object' do
args = hash_including(
spider_timeout: dast_scanner_profile.spider_timeout,
target_timeout: dast_scanner_profile.target_timeout
)
expect_any_instance_of(::Ci::RunDastScanService).to receive(:execute).with(args).and_call_original
subject
end
end
context 'when on demand scan feature is not enabled' do context 'when on demand scan feature is not enabled' do
it 'raises an exception' do it 'raises an exception' do
stub_feature_flags(security_on_demand_scans_feature_flag: false) stub_feature_flags(security_on_demand_scans_feature_flag: false)
......
...@@ -6,13 +6,16 @@ RSpec.describe 'Running a DAST Scan' do ...@@ -6,13 +6,16 @@ RSpec.describe 'Running a DAST Scan' do
include GraphqlHelpers include GraphqlHelpers
let(:dast_site_profile) { create(:dast_site_profile, project: project) } let(:dast_site_profile) { create(:dast_site_profile, project: project) }
let(:dast_site_profile_id) { dast_site_profile.to_global_id.to_s }
let(:dast_scanner_profile_id) { nil }
let(:mutation_name) { :dast_on_demand_scan_create } let(:mutation_name) { :dast_on_demand_scan_create }
let(:mutation) do let(:mutation) do
graphql_mutation( graphql_mutation(
mutation_name, mutation_name,
full_path: full_path, full_path: full_path,
dast_site_profile_id: dast_site_profile.to_global_id.to_s dast_site_profile_id: dast_site_profile_id,
dast_scanner_profile_id: dast_scanner_profile_id
) )
end end
...@@ -28,6 +31,17 @@ RSpec.describe 'Running a DAST Scan' do ...@@ -28,6 +31,17 @@ RSpec.describe 'Running a DAST Scan' do
expect(mutation_response['pipelineUrl']).to eq(expected_url) expect(mutation_response['pipelineUrl']).to eq(expected_url)
end end
context 'when dast_scanner_profile_id is provided' do
let(:dast_scanner_profile) { create(:dast_scanner_profile, project: project, target_timeout: 200, spider_timeout: 5000) }
let(:dast_scanner_profile_id) { dast_scanner_profile.to_global_id.to_s }
it 'returns an empty errors array' do
subject
expect(mutation_response["errors"]).to be_empty
end
end
context 'when wrong type of global id is passed' do context 'when wrong type of global id is passed' do
let(:mutation) do let(:mutation) do
graphql_mutation( graphql_mutation(
......
...@@ -27,7 +27,7 @@ RSpec.describe Ci::RunDastScanService do ...@@ -27,7 +27,7 @@ RSpec.describe Ci::RunDastScanService do
end end
describe '#execute' do describe '#execute' do
subject { described_class.new(project, user).execute(branch: branch, target_url: target_url) } subject { described_class.new(project, user).execute(branch: branch, target_url: target_url, spider_timeout: 42, target_timeout: 21) }
let(:status) { subject.status } let(:status) { subject.status }
let(:pipeline) { subject.payload } let(:pipeline) { subject.payload }
...@@ -116,6 +116,15 @@ RSpec.describe Ci::RunDastScanService do ...@@ -116,6 +116,15 @@ RSpec.describe Ci::RunDastScanService do
'key' => 'DAST_WEBSITE', 'key' => 'DAST_WEBSITE',
'value' => target_url, 'value' => target_url,
'public' => true 'public' => true
},
{
'key' => 'DAST_SPIDER_MINS',
'value' => '42',
'public' => true
}, {
'key' => 'DAST_TARGET_AVAILABILITY_TIMEOUT',
'value' => '21',
'public' => true
}, { }, {
'key' => 'GIT_STRATEGY', 'key' => 'GIT_STRATEGY',
'value' => 'none', 'value' => 'none',
......
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