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 ...@@ -38,7 +38,8 @@ module Enums
external_pull_request_event: 11, external_pull_request_event: 11,
parent_pipeline: 12, parent_pipeline: 12,
ondemand_dast_scan: 13, ondemand_dast_scan: 13,
ondemand_dast_validation: 14 ondemand_dast_validation: 14,
security_orchestration_policy: 15
} }
end end
...@@ -52,7 +53,7 @@ module Enums ...@@ -52,7 +53,7 @@ module Enums
# - when an ondemand_dast_validation pipeline runs it is for validating a DAST site # - when an ondemand_dast_validation pipeline runs it is for validating a DAST site
# profile and should not affect the ref CI status. # profile and should not affect the ref CI status.
def self.dangling_sources 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 end
# CI sources are those pipeline events that affect the CI status of the ref # 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. ...@@ -129,9 +129,9 @@ rule in the defined policy are met.
| Field | Type | Possible values | Description | | Field | Type | Possible values | Description |
|-------|------|-----------------|-------------| |-------|------|-----------------|-------------|
| `scan` | `string` | `dast` | The action's type. | | `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. | | `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. | | `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: Note the following:
...@@ -144,6 +144,11 @@ 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 - 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 policy project's repository must have access to the scanner and site profiles. Otherwise, the scan
is not scheduled successfully. 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: Here's an example:
...@@ -161,8 +166,8 @@ scan_execution_policy: ...@@ -161,8 +166,8 @@ scan_execution_policy:
- scan: dast - scan: dast
scanner_profile: Scanner Profile A scanner_profile: Scanner Profile A
site_profile: Site Profile B site_profile: Site Profile B
- name: Enforce DAST scan every 10 minutes - name: Enforce DAST and secret detection scans every 10 minutes
description: This policy enforces a DAST scan to run every 10 minutes description: This policy enforces DAST and secret detection scans to run every 10 minutes
enabled: true enabled: true
rules: rules:
- type: schedule - type: schedule
...@@ -173,12 +178,25 @@ scan_execution_policy: ...@@ -173,12 +178,25 @@ scan_execution_policy:
- scan: dast - scan: dast
scanner_profile: Scanner Profile C scanner_profile: Scanner Profile C
site_profile: Site Profile D 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 In this example:
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 - For every pipeline executed on branches that match the `release/*` wildcard (for example, branch
the scanner profile `Scanner Profile C` and the site profile `Site Profile D` every 10 minutes. `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 ## 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 ...@@ -7,8 +7,7 @@ module Security
schedule.schedule_next_run! schedule.schedule_next_run!
branches = schedule.applicable_branches branches = schedule.applicable_branches
actions_for(schedule) actions_for(schedule).each { |action| process_action(action, branches) }
.each { |action| process_action(action, branches) }
end end
private private
...@@ -22,10 +21,19 @@ module Security ...@@ -22,10 +21,19 @@ module Security
def process_action(action, branches) def process_action(action, branches)
case action[:scan] case action[:scan]
when 'secret_detection' then schedule_scan(action, branches)
when 'dast' then schedule_dast_on_demand_scan(action, branches) when 'dast' then schedule_dast_on_demand_scan(action, branches)
end end
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) def schedule_dast_on_demand_scan(action, branches)
dast_site_profile = find_dast_site_profile(container, action[:site_profile]) dast_site_profile = find_dast_site_profile(container, action[:site_profile])
dast_scanner_profile = find_dast_scanner_profile(container, action[:scanner_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 ...@@ -24,8 +24,8 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
subject(:service) { described_class.new(container: project, current_user: current_user) } subject(:service) { described_class.new(container: project, current_user: current_user) }
shared_examples 'does not execute DAST on demand-scan' do shared_examples 'does not execute scan' do
it 'does not create a DAST on demand-scan pipeline but updates next_run_at' 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 { service.execute(schedule) }.to change(Ci::Pipeline, :count).by(0)
expect(schedule.next_run_at).to be > Time.zone.now expect(schedule.next_run_at).to be > Time.zone.now
...@@ -42,8 +42,25 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do ...@@ -42,8 +42,25 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
end end
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 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 { service.execute(schedule) }.to change(Ci::Pipeline, :count).by(2)
expect(schedule.next_run_at).to be > Time.zone.now expect(schedule.next_run_at).to be > Time.zone.now
...@@ -61,7 +78,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do ...@@ -61,7 +78,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
} }
end end
it_behaves_like 'does not execute DAST on demand-scan' it_behaves_like 'does not execute scan'
end end
context 'when policy actions does not exist' do context 'when policy actions does not exist' do
...@@ -75,7 +92,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do ...@@ -75,7 +92,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
} }
end end
it_behaves_like 'does not execute DAST on demand-scan' it_behaves_like 'does not execute scan'
end end
context 'when policy scan type is invalid' do context 'when policy scan type is invalid' do
...@@ -91,13 +108,13 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do ...@@ -91,13 +108,13 @@ RSpec.describe Security::SecurityOrchestrationPolicies::RuleScheduleService do
} }
end end
it_behaves_like 'does not execute DAST on demand-scan' it_behaves_like 'does not execute scan'
end end
context 'when policy does not exist' do context 'when policy does not exist' do
let(:policy) { nil } let(:policy) { nil }
it_behaves_like 'does not execute DAST on demand-scan' it_behaves_like 'does not execute scan'
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