Commit b1cd5a86 authored by Zamir Martins's avatar Zamir Martins Committed by Dmitriy Zaporozhets (DZ)

Add feature flag and scan_result_policies

parent 69715295
...@@ -10,6 +10,8 @@ class ProtectedBranch < ApplicationRecord ...@@ -10,6 +10,8 @@ class ProtectedBranch < ApplicationRecord
scope :allowing_force_push, scope :allowing_force_push,
-> { where(allow_force_push: true) } -> { where(allow_force_push: true) }
scope :get_ids_by_name, -> (name) { where(name: name).pluck(:id) }
protected_ref_access_levels :merge, :push protected_ref_access_levels :merge, :push
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil) def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
......
...@@ -457,6 +457,7 @@ class User < ApplicationRecord ...@@ -457,6 +457,7 @@ class User < ApplicationRecord
scope :dormant, -> { active.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) } scope :dormant, -> { active.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
scope :with_no_activity, -> { active.where(last_activity_on: nil) } scope :with_no_activity, -> { active.where(last_activity_on: nil) }
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
scope :get_ids_by_username, -> (username) { where(username: username).pluck(:id) }
def preferred_language def preferred_language
read_attribute('preferred_language') || read_attribute('preferred_language') ||
......
...@@ -21,7 +21,8 @@ module ApprovalRuleLike ...@@ -21,7 +21,8 @@ module ApprovalRuleLike
enum report_type: { enum report_type: {
vulnerability: 1, vulnerability: 1,
license_scanning: 2, license_scanning: 2,
code_coverage: 3 code_coverage: 3,
scan_finding: 4
} }
validates :name, presence: true validates :name, presence: true
......
# frozen_string_literal: true
module Security
module ScanResultPolicy
extend ActiveSupport::Concern
LIMIT = 5
SCAN_FINDING = 'scan_finding'
REQUIRE_APPROVAL = 'require_approval'
included do
delegate :approval_rules, to: :project
def active_scan_result_policies
return [] unless ::Feature.enabled?(:scan_result_policy, project)
scan_result_policies&.select { |config| config[:enabled] }&.first(LIMIT)
end
def scan_result_policies
policy_by_type(:scan_result_policy)
end
end
end
end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Security module Security
class OrchestrationPolicyConfiguration < ApplicationRecord class OrchestrationPolicyConfiguration < ApplicationRecord
include Security::ScanExecutionPolicy include Security::ScanExecutionPolicy
include Security::ScanResultPolicy
include EachBatch include EachBatch
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
...@@ -10,7 +11,7 @@ module Security ...@@ -10,7 +11,7 @@ module Security
POLICY_PATH = '.gitlab/security-policies/policy.yml' POLICY_PATH = '.gitlab/security-policies/policy.yml'
POLICY_SCHEMA_PATH = 'ee/app/validators/json_schemas/security_orchestration_policy.json' POLICY_SCHEMA_PATH = 'ee/app/validators/json_schemas/security_orchestration_policy.json'
AVAILABLE_POLICY_TYPES = %i{scan_execution_policy}.freeze AVAILABLE_POLICY_TYPES = %i{scan_execution_policy scan_result_policy}.freeze
belongs_to :project, inverse_of: :security_orchestration_policy_configuration belongs_to :project, inverse_of: :security_orchestration_policy_configuration
belongs_to :security_policy_management_project, class_name: 'Project', foreign_key: 'security_policy_management_project_id' belongs_to :security_policy_management_project, class_name: 'Project', foreign_key: 'security_policy_management_project_id'
......
# frozen_string_literal: true
module Security
module SecurityOrchestrationPolicies
class ProcessScanResultPolicyService
MAX_LENGTH = 25
def initialize(policy_configuration:, policy:)
@policy_configuration = policy_configuration
@policy = policy
@project = policy_configuration.project
@author = policy_configuration.policy_last_updated_by
end
def execute
return if ::Feature.disabled?(:scan_result_policy, project)
create_new_approval_rules
end
private
attr_reader :policy_configuration, :policy, :project, :author
def create_new_approval_rules
action_info = policy[:actions].find { |action| action[:type] == Security::ScanResultPolicy::REQUIRE_APPROVAL }
return unless action_info
policy[:rules].each_with_index do |rule, rule_index|
next if rule[:type] != Security::ScanResultPolicy::SCAN_FINDING
::ApprovalRules::CreateService.new(project, author, rule_params(rule, rule_index, action_info)).execute
end
end
def rule_params(rule, rule_index, action_info)
{
approvals_required: action_info[:approvals_required],
name: rule_name(policy[:name], rule_index),
protected_branch_ids: project.protected_branches.get_ids_by_name(rule[:branches]),
scanners: rule[:scanners],
rule_type: :report_approver,
severity_levels: rule[:severity_levels],
user_ids: project.users.get_ids_by_username(action_info[:approvers]),
vulnerabilities_allowed: rule[:vulnerabilities_allowed],
report_type: :scan_finding
}
end
def rule_name(policy_name, rule_index)
truncated = policy_name.truncate(MAX_LENGTH, omission: '')
return truncated if rule_index == 0
"#{truncated} #{rule_index + 1}"
end
end
end
end
{ {
"required": [
"scan_execution_policy"
],
"type": "object", "type": "object",
"anyOf":[
{"required": ["scan_execution_policy"]},
{"required": ["scan_result_policy"]}
],
"properties": { "properties": {
"scan_execution_policy": { "scan_execution_policy": {
"type": "array", "type": "array",
...@@ -188,6 +189,121 @@ ...@@ -188,6 +189,121 @@
"additionalProperties": false "additionalProperties": false
} }
} }
}
}
},
"scan_result_policy": {
"type": "array",
"additionalItems": false,
"items": {
"maxItems": 5,
"required": [
"name",
"enabled",
"rules",
"actions"
],
"type": "object",
"properties": {
"name": {
"minLength": 1,
"type": "string"
},
"description": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"rules": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"required": [
"branches",
"scanners",
"vulnerabilities_allowed",
"severity_levels"
],
"properties": {
"type": {
"enum": [
"scan_finding"
],
"type": "string"
},
"branches": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "string"
}
},
"scanners": {
"type": "array",
"additionalItems": false,
"items":{
"minLength": 1,
"type": "string"
}
},
"vulnerabilities_allowed":{
"type": "integer"
},
"severity_levels":{
"type": "array",
"additionalItems": false,
"items":{
"type": {
"enum": [
"critical",
"high",
"medium",
"low",
"info",
"unknown"
],
"type": "string"
}
}
}
},
"additionalProperties": false
}
},
"actions": {
"type": "array",
"additionalItems": false,
"items": {
"required": [
"type",
"approvals_required",
"approvers"
],
"type": "object",
"properties": {
"type": {
"enum": [
"require_approval"
],
"type": "string"
},
"approvals_required": {
"type": "integer"
},
"approvers": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "string"
}
}
}
}
}
}, },
"additionalProperties": false "additionalProperties": false
} }
......
---
name: scan_result_policy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70631
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339338
milestone: '14.4'
type: development
group: group::container security
default_enabled: false
...@@ -25,13 +25,45 @@ FactoryBot.define do ...@@ -25,13 +25,45 @@ FactoryBot.define do
end end
end end
factory :scan_execution_policy_yaml, class: Struct.new(:scan_execution_policy) do factory :scan_result_policy, class: Struct.new(:name, :description, :enabled, :actions, :rules) do
skip_create skip_create
initialize_with do initialize_with do
policies = attributes[:policies] name = attributes[:name]
description = attributes[:description]
enabled = attributes[:enabled]
actions = attributes[:actions]
rules = attributes[:rules]
new(name, description, enabled, actions, rules).to_h
end
sequence(:name) { |n| "test-policy-#{n}" }
description { 'This policy considers only container scanning and critical severities' }
enabled { true }
rules do
[
{
type: 'scan_finding',
branches: %w[master],
scanners: %w[container_scanning],
vulnerabilities_allowed: 0,
severity_levels: %w[critical]
}
]
end
actions { [{ type: 'require_approval', approvals_required: 1, approvers: %w[admin] }] }
end
factory :orchestration_policy_yaml, class: Struct.new(:scan_execution_policy, :scan_result_policy) do
skip_create
initialize_with do
scan_execution_policy = attributes[:scan_execution_policy]
scan_result_policy = attributes[:scan_result_policy]
YAML.dump(new(policies).to_h.deep_stringify_keys) YAML.dump(new(scan_execution_policy, scan_result_policy).to_h.compact.deep_stringify_keys)
end end
end end
end end
...@@ -74,4 +74,165 @@ scan_execution_policy: ...@@ -74,4 +74,165 @@ scan_execution_policy:
actions: actions:
- scan: dast - scan: dast
site_profile: Site Profile site_profile: Site Profile
scanner_profile: Scanner Profile scanner_profile: Scanner Profile
\ No newline at end of file scan_result_policy:
- name: critical vulnerability CS approvals
description: critical severity level only for container scanning
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- container_scanning
vulnerabilities_allowed: 0
severity_levels:
- critical
- type: scan_finding
branches:
- master
scanners:
- dast
vulnerabilities_allowed: 1
severity_levels:
- info
- type: scan_finding
branches:
- master
scanners:
- container_scanning
vulnerabilities_allowed: 10
severity_levels:
- info
actions:
- type: require_approval
approvals_required: 1
approvers:
- admin
- name: Enabled DAST policy
description: enabled police with low and medium severity levels only for DAST
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- dast
vulnerabilities_allowed: 1
severity_levels:
- medium
- low
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- developer.local
- name: Disabled DAST policy
description: disabled police with low and medium severity levels only for DAST
enabled: false
rules:
- type: scan_finding
branches:
- master
scanners:
- dast
vulnerabilities_allowed: 1
severity_levels:
- medium
- low
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- developer.local
- name: Enabled SAST policy
description: enabled police with low and medium severity levels only for SAST
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- sast
vulnerabilities_allowed: 1
severity_levels:
- medium
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- name: Enabled CS policy
description: enabled police with low severity levels only for CS
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- container_scanning
vulnerabilities_allowed: 1
severity_levels:
- low
actions:
- type: require_approval
approvals_required: 2
approvers:
- developer.local
- name: Enabled DAST and SAST policy
description: disabled police with unknown severity levels only for DAST and SAST
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- dast
- sast
vulnerabilities_allowed: 1
severity_levels:
- unknown
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- developer.local
- name: Enabled dependency scanning policy
description: disabled police with unknown severity levels only for dependency scanning
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- dependency_scanning
vulnerabilities_allowed: 1
severity_levels:
- unknown
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- developer.local
- name: Enabled secret detection policy
description: disabled police with unknown severity levels only for secret detection
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- secret_detection
vulnerabilities_allowed: 1
severity_levels:
- unknown
actions:
- type: require_approval
approvals_required: 2
approvers:
- admin
- developer.local
\ No newline at end of file
...@@ -12,7 +12,7 @@ RSpec.describe Resolvers::ScanExecutionPolicyResolver do ...@@ -12,7 +12,7 @@ RSpec.describe Resolvers::ScanExecutionPolicyResolver do
let_it_be(:user) { policy_management_project.owner } let_it_be(:user) { policy_management_project.owner }
let(:policy) { build(:scan_execution_policy, name: 'Run DAST in every pipeline') } let(:policy) { build(:scan_execution_policy, name: 'Run DAST in every pipeline') }
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy]) }
let(:repository) { instance_double(Repository, root_ref: 'master', empty?: false) } let(:repository) { instance_double(Repository, root_ref: 'master', empty?: false) }
......
...@@ -87,7 +87,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do ...@@ -87,7 +87,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do
]) ])
end end
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy1, policy2]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy1, policy2]) }
before do before do
create_list(:dast_scanner_profile, 30, project: project) create_list(:dast_scanner_profile, 30, project: project)
......
...@@ -152,7 +152,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do ...@@ -152,7 +152,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do
]) ])
end end
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy1, policy2]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy1, policy2]) }
before do before do
create_list(:dast_site_profile, 30, project: project) create_list(:dast_site_profile, 30, project: project)
......
...@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Config do ...@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Config do
let_it_be(:policies_repository) { create(:project, :repository) } let_it_be(:policies_repository) { create(:project, :repository) }
let_it_be(:security_orchestration_policy_configuration) { create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: policies_repository) } let_it_be(:security_orchestration_policy_configuration) { create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: policies_repository) }
let_it_be(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy)]) } let_it_be(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy)]) }
subject(:config) { described_class.new(ci_yml, source_ref_path: ref, project: project, source: source) } subject(:config) { described_class.new(ci_yml, source_ref_path: ref, project: project, source: source) }
......
...@@ -23,7 +23,7 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do ...@@ -23,7 +23,7 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do
]) ])
end end
let_it_be(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy]) } let_it_be(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy]) }
before do before do
allow_next_instance_of(Repository) do |repository| allow_next_instance_of(Repository) do |repository|
...@@ -47,7 +47,7 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do ...@@ -47,7 +47,7 @@ RSpec.describe Gitlab::Ci::Config::SecurityOrchestrationPolicies::Processor do
shared_examples 'when policy is invalid' do shared_examples 'when policy is invalid' do
let_it_be(:policy_yaml) do let_it_be(:policy_yaml) do
build(:scan_execution_policy_yaml, policies: build(:orchestration_policy_yaml, scan_execution_policy:
[build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: 'production' }])]) [build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: 'production' }])])
end end
......
...@@ -11,7 +11,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -11,7 +11,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:default_branch) { security_policy_management_project.default_branch } let(:default_branch) { security_policy_management_project.default_branch }
let(:repository) { instance_double(Repository, root_ref: 'master', empty?: false) } let(:repository) { instance_double(Repository, root_ref: 'master', empty?: false) }
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy, name: 'Run DAST in every pipeline')]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy, name: 'Run DAST in every pipeline')], scan_result_policy: [build(:scan_result_policy, name: 'Containe security critical severities')]) }
before do before do
allow(security_policy_management_project).to receive(:repository).and_return(repository) allow(security_policy_management_project).to receive(:repository).and_return(repository)
...@@ -118,7 +118,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -118,7 +118,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
end end
context 'when policy is present' do context 'when policy is present' do
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy, name: 'Run DAST in every pipeline' )]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy, name: 'Run DAST in every pipeline' )]) }
it 'retrieves policy by type' do it 'retrieves policy by type' do
expect(subject.first[:name]).to eq('Run DAST in every pipeline') expect(subject.first[:name]).to eq('Run DAST in every pipeline')
...@@ -139,7 +139,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -139,7 +139,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
context 'when file is invalid' do context 'when file is invalid' do
let(:policy_yaml) do let(:policy_yaml) do
build(:scan_execution_policy_yaml, policies: build(:orchestration_policy_yaml, scan_execution_policy:
[build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: 'production' }])]) [build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: 'production' }])])
end end
...@@ -172,8 +172,8 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -172,8 +172,8 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
end end
end end
describe '#active_policies' do describe '#active_scan_execution_policies' do
let(:enforce_dast_yaml) { build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy)]) } let(:enforce_dast_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy)]) }
let(:policy_yaml) { fixture_file('security_orchestration.yml', dir: 'ee') } let(:policy_yaml) { fixture_file('security_orchestration.yml', dir: 'ee') }
let(:expected_active_policies) do let(:expected_active_policies) do
...@@ -203,7 +203,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -203,7 +203,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:policy2) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['release/*'] }], actions: [{ scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile 2' }]) } let(:policy2) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['release/*'] }], actions: [{ scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile 2' }]) }
let(:policy3) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['*'] }], actions: [{ scan: 'dast', site_profile: 'Site Profile 3', scanner_profile: 'Scanner Profile 3' }]) } let(:policy3) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['*'] }], actions: [{ scan: 'dast', site_profile: 'Site Profile 3', scanner_profile: 'Scanner Profile 3' }]) }
let(:policy4) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['release/*'] }], actions: [{ scan: 'sast' }]) } let(:policy4) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['release/*'] }], actions: [{ scan: 'sast' }]) }
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy1, policy2, policy3, policy4]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy1, policy2, policy3, policy4]) }
let(:expected_actions) do let(:expected_actions) do
[ [
...@@ -236,7 +236,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -236,7 +236,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:policy2) { build(:scan_execution_policy, actions: [{ scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile 2' }, { scan: 'secret_detection' }]) } let(:policy2) { build(:scan_execution_policy, actions: [{ scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile 2' }, { scan: 'secret_detection' }]) }
let(:policy3) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['*'] }], actions: [{ scan: 'secret_detection' }]) } let(:policy3) { build(:scan_execution_policy, rules: [{ type: 'pipeline', branches: ['*'] }], actions: [{ scan: 'secret_detection' }]) }
let(:policy4) { build(:scan_execution_policy, :with_schedule, actions: [{ scan: 'secret_detection' }]) } let(:policy4) { build(:scan_execution_policy, :with_schedule, actions: [{ scan: 'secret_detection' }]) }
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy1, policy2, policy3, policy4]) } let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy1, policy2, policy3, policy4]) }
let(:expected_actions) do let(:expected_actions) do
[{ scan: 'secret_detection' }, { scan: 'secret_detection' }] [{ scan: 'secret_detection' }, { scan: 'secret_detection' }]
...@@ -253,7 +253,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -253,7 +253,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
describe '#active_policy_names_with_dast_site_profile' do describe '#active_policy_names_with_dast_site_profile' do
let(:policy_yaml) do let(:policy_yaml) do
build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy, name: 'Run DAST in every pipeline', actions: [ build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy, name: 'Run DAST in every pipeline', actions: [
{ scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile' }, { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile' },
{ scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile 2' } { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile 2' }
])]) ])])
...@@ -266,7 +266,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -266,7 +266,7 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
describe '#active_policy_names_with_dast_scanner_profile' do describe '#active_policy_names_with_dast_scanner_profile' do
let(:enforce_dast_yaml) do let(:enforce_dast_yaml) do
build(:scan_execution_policy_yaml, policies: [build(:scan_execution_policy, name: 'Run DAST in every pipeline', actions: [ build(:orchestration_policy_yaml, scan_execution_policy: [build(:scan_execution_policy, name: 'Run DAST in every pipeline', actions: [
{ scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile' }, { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile' },
{ scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile' } { scan: 'dast', site_profile: 'Site Profile 2', scanner_profile: 'Scanner Profile' }
])]) ])])
...@@ -340,4 +340,44 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -340,4 +340,44 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
expect(security_orchestration_policy_configuration.rule_schedules).to be_empty expect(security_orchestration_policy_configuration.rule_schedules).to be_empty
end end
end end
describe '#active_result_execution_policies' do
let(:scan_result_yaml) { build(:orchestration_policy_yaml, scan_result_policy: [build(:scan_result_policy)]) }
let(:policy_yaml) { fixture_file('security_orchestration.yml', dir: 'ee') }
subject(:active_scan_result_policies) { security_orchestration_policy_configuration.active_scan_result_policies }
before do
allow(security_policy_management_project).to receive(:repository).and_return(repository)
allow(repository).to receive(:blob_data_at).with(default_branch, Security::OrchestrationPolicyConfiguration::POLICY_PATH).and_return(policy_yaml)
end
it 'returns only enabled policies' do
expect(active_scan_result_policies.pluck(:enabled).uniq).to contain_exactly(true)
end
it 'returns only 5 from all active policies' do
expect(active_scan_result_policies.count).to be(5)
end
context 'when scan_result_policy feature flag is disabled' do
before do
stub_feature_flags(scan_result_policy: false)
end
it 'returns empty array' do
expect(active_scan_result_policies).to match_array([])
end
end
end
describe '#scan_result_policies' do
let(:policy_yaml) { fixture_file('security_orchestration.yml', dir: 'ee') }
subject(:scan_result_policies) { security_orchestration_policy_configuration.scan_result_policies }
it 'returns all scan result policies' do
expect(scan_result_policies.pluck(:enabled)).to contain_exactly(true, true, false, true, true, true, true, true)
end
end
end end
...@@ -7,7 +7,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::FetchPolicyService do ...@@ -7,7 +7,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::FetchPolicyService do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:policy_configuration) { create(:security_orchestration_policy_configuration, project: project) } let(:policy_configuration) { create(:security_orchestration_policy_configuration, project: project) }
let(:policy) { build(:scan_execution_policy) } let(:policy) { build(:scan_execution_policy) }
let(:policy_blob) { build(:scan_execution_policy_yaml, policies: [policy]) } let(:policy_blob) { build(:orchestration_policy_yaml, scan_execution_policy: [policy]) }
let(:type) { :scan_execution_policy } let(:type) { :scan_execution_policy }
let(:name) { policy[:name] } let(:name) { policy[:name] }
......
...@@ -10,7 +10,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do ...@@ -10,7 +10,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do
let(:policy_hash) { build(:scan_execution_policy, name: 'Test Policy') } let(:policy_hash) { build(:scan_execution_policy, name: 'Test Policy') }
let(:input_policy_yaml) { policy_hash.merge(type: 'scan_execution_policy').to_yaml } let(:input_policy_yaml) { policy_hash.merge(type: 'scan_execution_policy').to_yaml }
let(:policy_yaml) { build(:scan_execution_policy_yaml, policies: [policy_hash])} let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy_hash])}
let(:operation) { :append } let(:operation) { :append }
let(:params) { { policy_yaml: input_policy_yaml, operation: operation } } let(:params) { { policy_yaml: input_policy_yaml, operation: operation } }
......
...@@ -8,7 +8,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyConfigurationValid ...@@ -8,7 +8,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyConfigurationValid
let(:policy_configuration) { create(:security_orchestration_policy_configuration, project: project) } let(:policy_configuration) { create(:security_orchestration_policy_configuration, project: project) }
let(:policy) { build(:scan_execution_policy) } let(:policy) { build(:scan_execution_policy) }
let(:policy_blob) { build(:scan_execution_policy_yaml, policies: [policy]) } let(:policy_blob) { build(:orchestration_policy_yaml, scan_execution_policy: [policy]) }
let(:type) { :scan_execution_policy } let(:type) { :scan_execution_policy }
let(:environment_id) { nil } let(:environment_id) { nil }
......
...@@ -14,12 +14,12 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do ...@@ -14,12 +14,12 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do
let(:repository_with_existing_policy_yaml) do let(:repository_with_existing_policy_yaml) do
pipeline_policy = build(:scan_execution_policy, name: 'Test Policy') pipeline_policy = build(:scan_execution_policy, name: 'Test Policy')
build(:scan_execution_policy_yaml, policies: [pipeline_policy, scheduled_policy]) build(:orchestration_policy_yaml, scan_execution_policy: [pipeline_policy, scheduled_policy])
end end
let(:repository_policy_yaml) do let(:repository_policy_yaml) do
pipeline_policy = build(:scan_execution_policy, name: "Execute DAST in every pipeline") pipeline_policy = build(:scan_execution_policy, name: "Execute DAST in every pipeline")
build(:scan_execution_policy_yaml, policies: [pipeline_policy, scheduled_policy]) build(:orchestration_policy_yaml, scan_execution_policy: [pipeline_policy, scheduled_policy])
end end
subject(:service) { described_class.new(policy_configuration: policy_configuration, params: { policy: policy_yaml, operation: operation, type: type }) } subject(:service) { described_class.new(policy_configuration: policy_configuration, params: { policy: policy_yaml, operation: operation, type: type }) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::SecurityOrchestrationPolicies::ProcessScanResultPolicyService do
describe '#execute' do
let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration) }
let(:approver) { create(:user) }
let(:policy) { build(:scan_result_policy, name: 'Test Policy') }
let(:policy_yaml) { Gitlab::Config::Loader::Yaml.new(policy.to_yaml).load! }
let(:project) { policy_configuration.project }
let(:service) { described_class.new(policy_configuration: policy_configuration, policy: policy) }
before do
allow(policy_configuration).to receive(:policy_last_updated_by).and_return(project.owner)
end
subject { service.execute }
context 'when feature flag is disabled' do
before do
stub_feature_flags(scan_result_policy: false)
end
it 'does not change approval project rules' do
expect { subject }.not_to change { project.approval_rules.count }
end
end
context 'without any require_approval action' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', actions: [{ type: 'another_one' }]) }
it 'does not create approval project rules' do
expect { subject }.not_to change { project.approval_rules.count }
end
end
context 'without any rule of the scan_finding type' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', rules: [{ type: 'another_one' }]) }
it 'does not create approval project rules' do
expect { subject }.not_to change { project.approval_rules.count }
end
end
it 'creates a new project approval rule' do
expect { subject }.to change { project.approval_rules.count }.by(1)
end
it 'sets project approval rules names based on policy name', :aggregate_failures do
subject
scan_finding_rule = project.approval_rules.first
first_rule = policy[:rules].first
first_action = policy[:actions].first
expect(policy[:name]).to include(scan_finding_rule.name)
expect(scan_finding_rule.report_type).to eq(Security::ScanResultPolicy::SCAN_FINDING)
expect(scan_finding_rule.rule_type).to eq('report_approver')
expect(scan_finding_rule.scanners).to eq(first_rule[:scanners])
expect(scan_finding_rule.severity_levels).to eq(first_rule[:severity_levels])
expect(scan_finding_rule.vulnerabilities_allowed).to eq(first_rule[:vulnerabilities_allowed])
expect(scan_finding_rule.approvals_required).to eq(first_action[:approvals_required])
end
end
end
...@@ -308,4 +308,15 @@ RSpec.describe ProtectedBranch do ...@@ -308,4 +308,15 @@ RSpec.describe ProtectedBranch do
expect(described_class.by_name('')).to be_empty expect(described_class.by_name('')).to be_empty
end end
end end
describe '.get_ids_by_name' do
let(:branch_name) { 'branch_name' }
let!(:protected_branch) { create(:protected_branch, name: branch_name) }
let(:branch_id) { protected_branch.id }
it 'returns the id for each protected branch matching name' do
expect(described_class.get_ids_by_name([branch_name]))
.to match_array([branch_id])
end
end
end end
...@@ -6180,4 +6180,14 @@ RSpec.describe User do ...@@ -6180,4 +6180,14 @@ RSpec.describe User do
it_behaves_like 'groups_with_developer_maintainer_project_access examples' it_behaves_like 'groups_with_developer_maintainer_project_access examples'
end end
end end
describe '.get_ids_by_username' do
let(:user_name) { 'user_name' }
let!(:user) { create(:user, username: user_name) }
let(:user_id) { user.id }
it 'returns the id of each record matching username' do
expect(described_class.get_ids_by_username([user_name])).to match_array([user_id])
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