From ca742e3b5914979c73707ea87843e9179e83f6dd Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Thu, 25 Sep 2025 22:14:35 +0200 Subject: [PATCH 1/2] Add logical operator to policy_scope to support OR and AND Changelog: changed EE: true --- .../security_orchestration_policy.json | 8 + .../policy_scope_checker.rb | 26 ++- .../policy_scope_checker_spec.rb | 151 ++++++++++++++++++ 3 files changed, 181 insertions(+), 4 deletions(-) diff --git a/ee/app/validators/json_schemas/security_orchestration_policy.json b/ee/app/validators/json_schemas/security_orchestration_policy.json index 6bb8ad4877468b..ae480cf2b3af82 100644 --- a/ee/app/validators/json_schemas/security_orchestration_policy.json +++ b/ee/app/validators/json_schemas/security_orchestration_policy.json @@ -1781,6 +1781,14 @@ "policy_scope": { "type": "object", "properties": { + "operator": { + "description": "Specifies the logical operator used to combine multiple policy scope conditions. Use 'AND' to require all specified conditions to be met (more restrictive), or 'OR' to require at least one condition to be met (more permissive).", + "type": "string", + "enum": [ + "AND", + "OR" + ] + }, "compliance_frameworks": { "description": "Specifies for which compliance frameworks this policy should be applied to.", "type": "array", diff --git a/ee/lib/security/security_orchestration_policies/policy_scope_checker.rb b/ee/lib/security/security_orchestration_policies/policy_scope_checker.rb index 226c81d19603e3..59ecab056843ab 100644 --- a/ee/lib/security/security_orchestration_policies/policy_scope_checker.rb +++ b/ee/lib/security/security_orchestration_policies/policy_scope_checker.rb @@ -5,6 +5,8 @@ module SecurityOrchestrationPolicies class PolicyScopeChecker include Gitlab::InternalEventsTracking + OR_OPERATOR = 'OR' + def initialize(project:) @project = project end @@ -28,9 +30,19 @@ def security_policy_applicable?(security_policy) attr_accessor :project def scope_applicable?(policy_scope) - applicable_for_compliance_framework?(policy_scope) && - applicable_for_project?(policy_scope) && + [ + applicable_for_compliance_framework?(policy_scope), + applicable_for_project?(policy_scope), applicable_for_group?(policy_scope) + ].inject(or_operator?(policy_scope) ? :| : :&) + end + + def or_operator?(policy_scope) + policy_scope[:operator] == OR_OPERATOR + end + + def default_value_for_operator(policy_scope) + or_operator?(policy_scope) ? false : true end def applicable_for_compliance_framework?(policy_scope) @@ -38,7 +50,7 @@ def applicable_for_compliance_framework?(policy_scope) track_policy_scope_check(:compliance_framework, [policy_scope_compliance_frameworks]) - return true if policy_scope_compliance_frameworks.blank? + return default_value_for_operator(policy_scope) if policy_scope_compliance_frameworks.blank? compliance_framework_ids = project.compliance_framework_ids return false if compliance_framework_ids.blank? @@ -52,6 +64,10 @@ def applicable_for_project?(policy_scope) track_policy_scope_check(:project, [policy_scope_included_projects, policy_scope_excluded_projects]) + if policy_scope_excluded_projects.blank? && policy_scope_included_projects.blank? + return default_value_for_operator(policy_scope) + end + return false if policy_scope_excluded_projects.any? { |policy_project| policy_project[:id] == project.id } return true if policy_scope_included_projects.blank? @@ -64,7 +80,9 @@ def applicable_for_group?(policy_scope) track_policy_scope_check(:group, [policy_scope_included_groups, policy_scope_excluded_groups]) - return true if policy_scope_included_groups.blank? && policy_scope_excluded_groups.blank? + if policy_scope_included_groups.blank? && policy_scope_excluded_groups.blank? + return default_value_for_operator(policy_scope) + end ancestor_group_ids = project.group&.self_and_ancestor_ids.to_a diff --git a/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb b/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb index 2bdaec738957ac..d788c2b02fc910 100644 --- a/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb +++ b/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb @@ -5,6 +5,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyScopeChecker, feature_category: :security_policy_management do let_it_be_with_refind(:root_group) { create(:group) } let_it_be_with_refind(:group) { create(:group, parent: root_group) } + let_it_be_with_refind(:other_group) { create(:group) } let_it_be_with_refind(:project) { create(:project, group: group) } let_it_be(:compliance_framework) { create(:compliance_framework, namespace: root_group) } @@ -17,6 +18,12 @@ it { is_expected.to eq true } end + context 'when only operator is set in policy scope' do + let(:policy_scope) { { operator: 'and' } } + + it { is_expected.to eq true } + end + context 'when policy is scoped for compliance framework' do let(:policy_scope) do { @@ -59,6 +66,28 @@ end it { is_expected.to eq true } + + context 'and when AND operator is provided' do + let(:policy_scope) do + { + operator: 'AND', + compliance_frameworks: [{ id: compliance_framework_2.id }] + } + end + + it { is_expected.to eq true } + end + + context 'and when OR operator is provided' do + let(:policy_scope) do + { + operator: 'OR', + compliance_frameworks: [{ id: compliance_framework_2.id }] + } + end + + it { is_expected.to eq true } + end end context 'when policy additionally excludes the project from policy' do @@ -72,6 +101,34 @@ end it { is_expected.to eq false } + + context 'and when AND operator is provided' do + let(:policy_scope) do + { + operator: 'AND', + compliance_frameworks: [{ id: compliance_framework.id }], + projects: { + excluding: [{ id: project.id }] + } + } + end + + it { is_expected.to eq false } + end + + context 'and when OR operator is provided' do + let(:policy_scope) do + { + operator: 'OR', + compliance_frameworks: [{ id: compliance_framework.id }], + projects: { + excluding: [{ id: project.id }] + } + } + end + + it { is_expected.to eq true } + end end context 'when non-existing compliance framework is set' do @@ -130,6 +187,100 @@ it { is_expected.to eq false } end + + context 'when additionally excluding group scope is matching same group as project' do + let(:policy_scope) do + { + projects: { + including: [{ id: project.id }] + }, + groups: { + excluding: [{ id: group.id }] + } + } + end + + it { is_expected.to eq false } + + context 'and when AND operator is provided' do + let(:policy_scope) do + { + operator: 'AND', + projects: { + including: [{ id: project.id }] + }, + groups: { + excluding: [{ id: group.id }] + } + } + end + + it { is_expected.to eq false } + end + + context 'and when OR operator is provided' do + let(:policy_scope) do + { + operator: 'OR', + projects: { + including: [{ id: project.id }] + }, + groups: { + excluding: [{ id: group.id }] + } + } + end + + it { is_expected.to eq true } + end + end + + context 'when additionally including group scope is not matching same group as project' do + let(:policy_scope) do + { + projects: { + including: [{ id: project.id }] + }, + groups: { + including: [{ id: other_group.id }] + } + } + end + + it { is_expected.to eq false } + + context 'and when AND operator is provided' do + let(:policy_scope) do + { + operator: 'AND', + projects: { + including: [{ id: project.id }] + }, + groups: { + including: [{ id: other_group.id }] + } + } + end + + it { is_expected.to eq false } + end + + context 'and when OR operator is provided' do + let(:policy_scope) do + { + operator: 'OR', + projects: { + including: [{ id: project.id }] + }, + groups: { + including: [{ id: other_group.id }] + } + } + end + + it { is_expected.to eq true } + end + end end end -- GitLab From d8c4c44cc5358d4f063b201030343a9a50603259 Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Fri, 26 Sep 2025 12:05:43 +0200 Subject: [PATCH 2/2] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: GitLab Duo --- .../policy_scope_checker_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb b/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb index d788c2b02fc910..b61e74b7dc3c91 100644 --- a/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb +++ b/ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb @@ -19,7 +19,7 @@ end context 'when only operator is set in policy scope' do - let(:policy_scope) { { operator: 'and' } } + let(:policy_scope) { { operator: 'AND' } } it { is_expected.to eq true } end -- GitLab