Commit cf8c5ac6 authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Nikola Milojevic

Validate Scan Policy schema with JSON Schema

parent e837d5a0
...@@ -10,6 +10,7 @@ module Ci ...@@ -10,6 +10,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Build::Associations, Gitlab::Ci::Pipeline::Chain::Build::Associations,
Gitlab::Ci::Pipeline::Chain::Validate::Abilities, Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
Gitlab::Ci::Pipeline::Chain::Validate::Repository, Gitlab::Ci::Pipeline::Chain::Validate::Repository,
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
Gitlab::Ci::Pipeline::Chain::Config::Content, Gitlab::Ci::Pipeline::Chain::Config::Content,
Gitlab::Ci::Pipeline::Chain::Config::Process, Gitlab::Ci::Pipeline::Chain::Config::Process,
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs, Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
......
...@@ -7,6 +7,7 @@ module Security ...@@ -7,6 +7,7 @@ module Security
self.table_name = 'security_orchestration_policy_configurations' self.table_name = 'security_orchestration_policy_configurations'
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_LIMIT = 5 POLICY_LIMIT = 5
ON_DEMAND_SCANS = %w[dast].freeze ON_DEMAND_SCANS = %w[dast].freeze
...@@ -26,10 +27,20 @@ module Security ...@@ -26,10 +27,20 @@ module Security
::Feature.enabled?(:security_orchestration_policies_configuration, project) ::Feature.enabled?(:security_orchestration_policies_configuration, project)
end end
def policy_configuration_exists?
policy_hash.present?
end
def policy_configuration_valid?
JSONSchemer
.schema(Rails.root.join(POLICY_SCHEMA_PATH))
.valid?(policy_hash.to_h.deep_stringify_keys)
end
def active_policies def active_policies
return [] unless enabled? return [] unless enabled?
scan_execution_policy_at(POLICY_PATH).select { |config| config[:enabled] }.first(POLICY_LIMIT) scan_execution_policy.select { |config| config[:enabled] }.first(POLICY_LIMIT)
end end
def on_demand_scan_actions(branch) def on_demand_scan_actions(branch)
...@@ -74,9 +85,17 @@ module Security ...@@ -74,9 +85,17 @@ module Security
end end
end end
def scan_execution_policy_at(path) def scan_execution_policy
policy_repo.blob_data_at(default_branch_or_main, path) return [] if policy_hash.blank?
.then { |config| Gitlab::Config::Loader::Yaml.new(config).load!.fetch(:scan_execution_policy, []) }
policy_hash.fetch(:scan_execution_policy, [])
end
def policy_hash
blob_data = policy_repo.blob_data_at(default_branch_or_main, POLICY_PATH)
return if blob_data.blank?
Gitlab::Config::Loader::Yaml.new(blob_data).load!
end end
def applicable_for_branch?(policy, ref) def applicable_for_branch?(policy, ref)
......
{
"required": [
"scan_execution_policy"
],
"type": "object",
"properties": {
"scan_execution_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": [],
"properties": {
"type": {
"enum": [
"pipeline"
],
"type": "string"
},
"branches": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "string"
}
}
},
"additionalProperties": false
}
},
"actions": {
"type": "array",
"additionalItems": false,
"items": {
"required": [
"scan",
"site_profile"
],
"type": "object",
"properties": {
"scan": {
"enum": [
"dast"
],
"type": "string"
},
"scanner_profile": {
"type": "string"
},
"site_profile": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
# frozen_string_literal: true
module EE
module Gitlab
module Ci
module Pipeline
module Chain
module Validate
module SecurityOrchestrationPolicy
extend ::Gitlab::Utils::Override
include ::Gitlab::Ci::Pipeline::Chain::Helpers
override :perform!
def perform!
return unless project&.feature_available?(:security_orchestration_policies)
return unless security_orchestration_policy_configuration&.enabled?
if !security_orchestration_policy_configuration.policy_configuration_exists?
warning(_('scan-execution-policy: policy not applied, %{policy_path} file is missing') % { policy_path: ::Security::OrchestrationPolicyConfiguration::POLICY_PATH })
elsif !security_orchestration_policy_configuration.policy_configuration_valid?
warning(_('scan-execution-policy: policy not applied, %{policy_path} file is invalid') % { policy_path: ::Security::OrchestrationPolicyConfiguration::POLICY_PATH })
end
end
delegate :security_orchestration_policy_configuration, to: :project, allow_nil: true
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:security_orchestration_policy_configuration) { build(:security_orchestration_policy_configuration, project: project) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
let(:ci_yaml) do
<<-CI_YAML
job:
script: ls
CI_YAML
end
let(:yaml_processor_result) do
::Gitlab::Ci::YamlProcessor.new(
ci_yaml, {
project: project,
sha: pipeline.sha,
user: user
}
).execute
end
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: true
)
end
let(:step) { described_class.new(pipeline, command) }
describe '#perform' do
subject(:warning_messages) { pipeline.warning_messages.map(&:content) }
context 'when security policies feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
end
it 'does not return warning' do
step.perform!
expect(warning_messages).to be_empty
end
end
context 'when security policies feature is licensed' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
context 'when policy is disabled' do
before do
allow(security_orchestration_policy_configuration).to receive(:enabled?).and_return(false)
end
it 'does not return warning' do
step.perform!
expect(warning_messages).to be_empty
end
end
context 'when policy is enabled' do
before do
allow(security_orchestration_policy_configuration).to receive(:enabled?).and_return(true)
end
context 'when policy file is missing' do
before do
allow(security_orchestration_policy_configuration).to receive(:policy_configuration_exists?).and_return(false)
end
it 'returns warning' do
step.perform!
expect(warning_messages).to include('scan-execution-policy: policy not applied, .gitlab/security-policies/policy.yml file is missing')
end
end
context 'when policy file is present' do
before do
allow(security_orchestration_policy_configuration).to receive(:policy_configuration_exists?).and_return(true)
end
context 'when policy file is invalid' do
before do
allow(security_orchestration_policy_configuration).to receive(:policy_configuration_valid?).and_return(false)
end
it 'returns warning' do
step.perform!
expect(warning_messages).to include('scan-execution-policy: policy not applied, .gitlab/security-policies/policy.yml file is invalid')
end
end
context 'when policy file is valid' do
before do
allow(security_orchestration_policy_configuration).to receive(:policy_configuration_valid?).and_return(true)
end
it 'does not return warning' do
step.perform!
expect(warning_messages).to be_empty
end
end
end
end
end
end
end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Security::OrchestrationPolicyConfiguration do RSpec.describe Security::OrchestrationPolicyConfiguration do
let_it_be(:security_policy_management_project) { create(:project, :repository) } let_it_be(:security_policy_management_project) { create(:project, :repository) }
let_it_be(:security_orchestration_policy_configuration) do let_it_be(:security_orchestration_policy_configuration) do
create( :security_orchestration_policy_configuration, security_policy_management_project: security_policy_management_project) create(:security_orchestration_policy_configuration, security_policy_management_project: security_policy_management_project)
end end
let(:default_branch) { security_policy_management_project.default_branch } let(:default_branch) { security_policy_management_project.default_branch }
...@@ -46,21 +46,107 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -46,21 +46,107 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
end end
end end
describe '#policy_configuration_exists?' do
subject { security_orchestration_policy_configuration.policy_configuration_exists? }
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
context 'when file is missing' do
let(:policy_yaml) { nil }
it { is_expected.to eq(false) }
end
context 'when policy is present' do
let(:policy_yaml) do
<<-EOS
scan_execution_policy:
- name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project
enabled: true
rules:
- type: pipeline
branches:
- "production"
actions:
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile
EOS
end
it { is_expected.to eq(true) }
end
end
describe '#policy_configuration_valid?' do
subject { security_orchestration_policy_configuration.policy_configuration_valid? }
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
context 'when file is invalid' do
let(:policy_yaml) do
<<-EOS
scan_execution_policy:
- name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project
enabled: true
rules:
- type: pipeline
branch: "production"
actions:
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile
EOS
end
it { is_expected.to eq(false) }
end
context 'when file is valid' do
let(:policy_yaml) do
<<-EOS
scan_execution_policy:
- name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project
enabled: true
rules:
- type: pipeline
branches:
- "production"
actions:
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile
EOS
end
it { is_expected.to eq(true) }
end
end
describe '#active_policies' do describe '#active_policies' do
let(:enforce_dast_yaml) do let(:enforce_dast_yaml) do
<<-EOS <<-EOS
type: scan_execution_policy scan_execution_policy:
name: Run DAST in every pipeline - name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project description: This policy enforces to run DAST for every pipeline within the project
enabled: true enabled: true
rules: rules:
- type: pipeline - type: pipeline
branches: branches:
- "production" - "production"
actions: actions:
- scan: dast - scan: dast
site_profile: Site Profile site_profile: Site Profile
scanner_profile: Scanner Profile scanner_profile: Scanner Profile
EOS EOS
end end
...@@ -208,20 +294,20 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -208,20 +294,20 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:policy_yaml) do let(:policy_yaml) do
<<-EOS <<-EOS
scan_execution_policy: scan_execution_policy:
- name: Run DAST in every pipeline - name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project description: This policy enforces to run DAST for every pipeline within the project
enabled: true enabled: true
rules: rules:
- type: pipeline - type: pipeline
branches: branches:
- "production" - "production"
actions: actions:
- scan: dast - scan: dast
site_profile: Site Profile site_profile: Site Profile
scanner_profile: Scanner Profile scanner_profile: Scanner Profile
- scan: dast - scan: dast
site_profile: Site Profile site_profile: Site Profile
scanner_profile: Scanner Profile 2 scanner_profile: Scanner Profile 2
EOS EOS
end end
...@@ -239,21 +325,21 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -239,21 +325,21 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:enforce_dast_yaml) do let(:enforce_dast_yaml) do
<<-EOS <<-EOS
scan_execution_policy: scan_execution_policy:
type: scan_execution_policy - type: scan_execution_policy
- name: Run DAST in every pipeline name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project description: This policy enforces to run DAST for every pipeline within the project
enabled: true enabled: true
rules: rules:
- type: pipeline - type: pipeline
branches: branches:
- "production" - "production"
actions: actions:
- scan: dast - scan: dast
site_profile: Site Profile site_profile: Site Profile
scanner_profile: Scanner Profile scanner_profile: Scanner Profile
- scan: dast - scan: dast
site_profile: Site Profile 2 site_profile: Site Profile 2
scanner_profile: Scanner Profile scanner_profile: Scanner Profile
EOS EOS
end end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Chain
module Validate
class SecurityOrchestrationPolicy < Chain::Base
include Chain::Helpers
def perform!
# no-op
end
def break?
false
end
end
end
end
end
end
end
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy.prepend_if_ee('EE::Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy')
...@@ -38712,6 +38712,12 @@ msgstr "" ...@@ -38712,6 +38712,12 @@ msgstr ""
msgid "satisfied" msgid "satisfied"
msgstr "" msgstr ""
msgid "scan-execution-policy: policy not applied, %{policy_path} file is invalid"
msgstr ""
msgid "scan-execution-policy: policy not applied, %{policy_path} file is missing"
msgstr ""
msgid "security Reports|There was an error creating the merge request" msgid "security Reports|There was an error creating the merge request"
msgstr "" msgstr ""
......
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