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
Gitlab::Ci::Pipeline::Chain::Build::Associations,
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
Gitlab::Ci::Pipeline::Chain::Config::Content,
Gitlab::Ci::Pipeline::Chain::Config::Process,
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
......
......@@ -7,6 +7,7 @@ module Security
self.table_name = 'security_orchestration_policy_configurations'
POLICY_PATH = '.gitlab/security-policies/policy.yml'
POLICY_SCHEMA_PATH = 'ee/app/validators/json_schemas/security_orchestration_policy.json'
POLICY_LIMIT = 5
ON_DEMAND_SCANS = %w[dast].freeze
......@@ -26,10 +27,20 @@ module Security
::Feature.enabled?(:security_orchestration_policies_configuration, project)
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
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
def on_demand_scan_actions(branch)
......@@ -74,9 +85,17 @@ module Security
end
end
def scan_execution_policy_at(path)
policy_repo.blob_data_at(default_branch_or_main, path)
.then { |config| Gitlab::Config::Loader::Yaml.new(config).load!.fetch(:scan_execution_policy, []) }
def scan_execution_policy
return [] if policy_hash.blank?
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
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'
RSpec.describe Security::OrchestrationPolicyConfiguration do
let_it_be(:security_policy_management_project) { create(:project, :repository) }
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
let(:default_branch) { security_policy_management_project.default_branch }
......@@ -46,21 +46,107 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
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
let(:enforce_dast_yaml) do
<<-EOS
type: 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
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
......@@ -208,20 +294,20 @@ RSpec.describe Security::OrchestrationPolicyConfiguration 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
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile 2
- 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
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile 2
EOS
end
......@@ -239,21 +325,21 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
let(:enforce_dast_yaml) do
<<-EOS
scan_execution_policy:
type: 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
- scan: dast
site_profile: Site Profile 2
scanner_profile: Scanner Profile
- type: 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
- scan: dast
site_profile: Site Profile 2
scanner_profile: Scanner Profile
EOS
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 ""
msgid "satisfied"
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"
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