Commit 6791a6bf authored by James Fargher's avatar James Fargher

Merge branch 'sk/33415-scheduled-secret-detection' into 'master'

Scheduled secret detection scan for security policy

See merge request gitlab-org/gitlab!67752
parents f09b50ba 394a3e49
......@@ -38,7 +38,8 @@ module Enums
external_pull_request_event: 11,
parent_pipeline: 12,
ondemand_dast_scan: 13,
ondemand_dast_validation: 14
ondemand_dast_validation: 14,
security_orchestration_policy: 15
}
end
......@@ -52,7 +53,7 @@ module Enums
# - when an ondemand_dast_validation pipeline runs it is for validating a DAST site
# profile and should not affect the ref CI status.
def self.dangling_sources
sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan, :ondemand_dast_validation)
sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan, :ondemand_dast_validation, :security_orchestration_policy)
end
# CI sources are those pipeline events that affect the CI status of the ref
......
......@@ -129,9 +129,9 @@ rule in the defined policy are met.
| Field | Type | Possible values | Description |
|-------|------|-----------------|-------------|
| `scan` | `string` | `dast` | The action's type. |
| `site_profile` | `string` | Name of the selected [DAST site profile](../dast/index.md#site-profile). | The DAST site profile to execute the DAST scan. |
| `scanner_profile` | `string` or `null` | Name of the selected [DAST scanner profile](../dast/index.md#scanner-profile). | The DAST scanner profile to execute the DAST scan. |
| `scan` | `string` | `dast`, `secret_detection` | The action's type. |
| `site_profile` | `string` | Name of the selected [DAST site profile](../dast/index.md#site-profile). | The DAST site profile to execute the DAST scan. This field should only be set if `scan` type is `dast`. |
| `scanner_profile` | `string` or `null` | Name of the selected [DAST scanner profile](../dast/index.md#scanner-profile). | The DAST scanner profile to execute the DAST scan. This field should only be set if `scan` type is `dast`.|
Note the following:
......@@ -144,6 +144,11 @@ Note the following:
- When configuring policies with a scheduled DAST scan, the author of the commit in the security
policy project's repository must have access to the scanner and site profiles. Otherwise, the scan
is not scheduled successfully.
- For a secret detection scan, only rules with the default ruleset are supported. [Custom rulesets](../secret_detection/index.md#custom-rulesets)
are not supported.
- A secret detection scan runs in `normal` mode when executed as part of a pipeline, and in
[`historic`](../secret_detection/index.md#full-history-secret-scan)
mode when executed as part of a scheduled scan.
Here's an example:
......@@ -161,8 +166,8 @@ scan_execution_policy:
- scan: dast
scanner_profile: Scanner Profile A
site_profile: Site Profile B
- name: Enforce DAST scan every 10 minutes
description: This policy enforces a DAST scan to run every 10 minutes
- name: Enforce DAST and secret detection scans every 10 minutes
description: This policy enforces DAST and secret detection scans to run every 10 minutes
enabled: true
rules:
- type: schedule
......@@ -173,12 +178,25 @@ scan_execution_policy:
- scan: dast
scanner_profile: Scanner Profile C
site_profile: Site Profile D
- scan: secret_detection
- name: Enforce Secret Detection in every default branch pipeline
description: This policy enforces pipeline configuration to have a job with Secret Detection scan for the default branch
enabled: true
rules:
- type: pipeline
branches:
- main
actions:
- scan: secret_detection
```
In this example, the DAST scan runs with the scanner profile `Scanner Profile A` and the site
profile `Site Profile B` for every pipeline executed on branches that match the
`release/*` wildcard (for example, branch name `release/v1.2.1`); and the DAST scan runs with
the scanner profile `Scanner Profile C` and the site profile `Site Profile D` every 10 minutes.
In this example:
- For every pipeline executed on branches that match the `release/*` wildcard (for example, branch
`release/v1.2.1`), DAST scans run with `Scanner Profile A` and `Site Profile B`.
- DAST and secret detection scans run every 10 minutes. The DAST scan runs with `Scanner Profile C`
and `Site Profile D`.
- Secret detection scans run for every pipeline executed on the `main` branch.
## Security Policy project selection
......
# frozen_string_literal: true
module Security
module SecurityOrchestrationPolicies
class CreatePipelineService < ::BaseProjectService
SCAN_VARIABLES = {
'secret_detection' => {
'SECRET_DETECTION_HISTORIC_SCAN' => 'true',
'SECRET_DETECTION_DISABLED' => nil
}
}.freeze
def execute
service = Ci::CreatePipelineService.new(project, current_user, ref: params[:branch])
result = service.execute(:security_orchestration_policy, content: ci_configuration.to_yaml)
pipeline = result.payload
if pipeline.created_successfully?
success(payload: pipeline)
else
error(pipeline.full_error_messages)
end
end
private
def ci_configuration
ci_content = ::Security::SecurityOrchestrationPolicies::CiConfigurationService.new.execute(action, SCAN_VARIABLES[scan_type])
{ "#{scan_type}" => ci_content }
end
def action
params[:action]
end
def scan_type
action[:scan]
end
end
end
end
......@@ -7,8 +7,7 @@ module Security
schedule.schedule_next_run!
branches = schedule.applicable_branches
actions_for(schedule)
.each { |action| process_action(action, branches) }
actions_for(schedule).each { |action| process_action(action, branches) }
end
private
......@@ -22,10 +21,19 @@ module Security
def process_action(action, branches)
case action[:scan]
when 'secret_detection' then schedule_scan(action, branches)
when 'dast' then schedule_dast_on_demand_scan(action, branches)
end
end
def schedule_scan(action, branches)
branches.each do |branch|
::Security::SecurityOrchestrationPolicies::CreatePipelineService
.new(project: container, current_user: current_user, params: { action: action, branch: branch })
.execute
end
end
def schedule_dast_on_demand_scan(action, branches)
dast_site_profile = find_dast_site_profile(container, action[:site_profile])
dast_scanner_profile = find_dast_scanner_profile(container, action[:scanner_profile])
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::SecurityOrchestrationPolicies::CreatePipelineService do
describe '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { project.owner }
let_it_be(:branch) { project.default_branch }
let_it_be(:action) { { scan: 'secret_detection' } }
let_it_be(:service) do
described_class.new(project: project, current_user: current_user, params: {
action: action, branch: branch
})
end
subject { service.execute }
context 'when scan type is valid' do
let(:status) { subject[:status] }
let(:pipeline) { subject[:payload] }
let(:message) { subject[:message] }
context 'when action is valid' do
it 'returns a success status' do
expect(status).to eq(:success)
end
it 'returns a pipeline' do
expect(pipeline).to be_a(Ci::Pipeline)
end
it 'creates a pipeline' do
expect { subject }.to change(Ci::Pipeline, :count).by(1)
end
it 'sets the pipeline ref to the branch' do
expect(pipeline.ref).to eq(branch)
end
it 'sets the source to security_orchestration_policy' do
expect(pipeline.source).to eq('security_orchestration_policy')
end
it 'creates a stage' do
expect { subject }.to change(Ci::Stage, :count).by(1)
end
it 'creates a build' do
expect { subject }.to change(Ci::Build, :count).by(1)
end
it 'sets the build name to secret_detection' do
build = pipeline.builds.first
expect(build.name).to eq('secret_detection')
end
it 'creates a build with appropriate variables' do
build = pipeline.builds.first
expected_variables = [
{
key: 'SECRET_DETECTION_HISTORIC_SCAN',
value: 'true',
public: true,
masked: false
}
]
expect(build.variables.to_runner_variables).to include(*expected_variables)
end
end
end
end
end
......@@ -24,8 +24,8 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
subject(:service) { described_class.new(container: project, current_user: current_user) }
shared_examples 'does not execute DAST on demand-scan' do
it 'does not create a DAST on demand-scan pipeline but updates next_run_at' do
shared_examples 'does not execute scan' do
it 'does not create scan pipeline but updates next_run_at' do
expect { service.execute(schedule) }.to change(Ci::Pipeline, :count).by(0)
expect(schedule.next_run_at).to be > Time.zone.now
......@@ -42,8 +42,25 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
end
end
context 'when scan type is dast' do
it 'invokes DastOnDemandScans::CreateService' do
expect(::DastOnDemandScans::CreateService).to receive(:new).twice.and_call_original
service.execute(schedule)
end
end
context 'when scan type is secret_detection' do
it 'invokes Security::SecurityOrchestrationPolicies::CreatePipelineService' do
policy[:actions] = [{ scan: 'secret_detection' }]
expect(::Security::SecurityOrchestrationPolicies::CreatePipelineService).to receive(:new).twice.and_call_original
service.execute(schedule)
end
end
context 'when policy actions exists and there are multiple matching branches' do
it 'creates multiple DAST on demand-scan pipelines and updates next_run_at' do
it 'creates multiple scan pipelines and updates next_run_at' do
expect { service.execute(schedule) }.to change(Ci::Pipeline, :count).by(2)
expect(schedule.next_run_at).to be > Time.zone.now
......@@ -61,7 +78,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
}
end
it_behaves_like 'does not execute DAST on demand-scan'
it_behaves_like 'does not execute scan'
end
context 'when policy actions does not exist' do
......@@ -75,7 +92,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
}
end
it_behaves_like 'does not execute DAST on demand-scan'
it_behaves_like 'does not execute scan'
end
context 'when policy scan type is invalid' do
......@@ -91,13 +108,13 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
}
end
it_behaves_like 'does not execute DAST on demand-scan'
it_behaves_like 'does not execute scan'
end
context 'when policy does not exist' do
let(:policy) { nil }
it_behaves_like 'does not execute DAST on demand-scan'
it_behaves_like 'does not execute scan'
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