Blame SOURCES/scap-security-guide-0.1.58-fix_handling_of_variables_in_levels-PR_7226.patch

9be3b2
From 7901659fa169db8ac5ffd7c610a798c785a3556b Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Fri, 9 Jul 2021 14:41:03 +0200
9be3b2
Subject: [PATCH 01/12] ensure that higher policy levels can override variables
9be3b2
 of lower levels
9be3b2
9be3b2
---
9be3b2
 ssg/controls.py | 13 ++++++++++---
9be3b2
 1 file changed, 10 insertions(+), 3 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 297d80e46c5..165cdf0511a 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -202,9 +202,16 @@ def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
 
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
-        for c in all_policy_controls:
9be3b2
-            if len(level_ids.intersection(c.levels)) > 0:
9be3b2
-                eligible_controls.append(c)
9be3b2
+        defined_variables = []
9be3b2
+        # we will go level by level, from top to bottom
9be3b2
+        # this is done to enable overriding of variables by higher levels
9be3b2
+        for lv in level_ids:
9be3b2
+            for c in all_policy_controls:
9be3b2
+                if lv in c.levels:
9be3b2
+                    # if the control has a variable, check if it is not already defined
9be3b2
+                    if c.variables.keys().isdisjoint(defined_variables):
9be3b2
+                        eligible_controls.append(c)
9be3b2
+                        defined_variables += [*c.variables.keys()]
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From 66e612a9668009cc553fcf1abbf2c9477155c0c2 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 5 Aug 2021 14:02:25 +0200
9be3b2
Subject: [PATCH 02/12] use ordered sets emulated by ordereddict
9be3b2
9be3b2
because of compatibility with python2
9be3b2
---
9be3b2
 ssg/controls.py | 21 ++++++++++++++-------
9be3b2
 1 file changed, 14 insertions(+), 7 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 165cdf0511a..611a647e125 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -2,6 +2,7 @@
9be3b2
 import logging
9be3b2
 import os
9be3b2
 from glob import glob
9be3b2
+from collections import OrderedDict
9be3b2
 
9be3b2
 import ssg.build_yaml
9be3b2
 import ssg.yaml
9be3b2
@@ -152,16 +153,18 @@ def get_level(self, level_id):
9be3b2
             raise ValueError(msg)
9be3b2
 
9be3b2
     def get_level_with_ancestors(self, level_id):
9be3b2
-        levels = set()
9be3b2
+        # use OrderedDict for Python2 compatibility instead of ordered set
9be3b2
+        levels = OrderedDict()
9be3b2
         level = self.get_level(level_id)
9be3b2
-        levels.add(level)
9be3b2
+        levels[level] = ""
9be3b2
         if level.inherits_from:
9be3b2
             for lv in level.inherits_from:
9be3b2
-                levels.update(self.get_level_with_ancestors(lv))
9be3b2
+                eligible_levels = [l for l in self.get_level_with_ancestors(lv).keys() if l not in levels.keys()]
9be3b2
+                for l in eligible_levels:
9be3b2
+                    levels[l] = ""
9be3b2
         return levels
9be3b2
 
9be3b2
 
9be3b2
-
9be3b2
 class ControlsManager():
9be3b2
     def __init__(self, controls_dir, env_yaml=None):
9be3b2
         self.controls_dir = os.path.abspath(controls_dir)
9be3b2
@@ -198,20 +201,24 @@ def _get_policy(self, policy_id):
9be3b2
     def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
-        level_ids = set([lv.id for lv in levels])
9be3b2
+        # we use OrderedDict here with empty values instead of ordered set
9be3b2
+        # cause we want to be compatible with python 2
9be3b2
+        level_ids = OrderedDict()
9be3b2
+        for lv in levels.keys():
9be3b2
+            level_ids[lv.id] = ""
9be3b2
 
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
         defined_variables = []
9be3b2
         # we will go level by level, from top to bottom
9be3b2
         # this is done to enable overriding of variables by higher levels
9be3b2
-        for lv in level_ids:
9be3b2
+        for lv in level_ids.keys():
9be3b2
             for c in all_policy_controls:
9be3b2
                 if lv in c.levels:
9be3b2
                     # if the control has a variable, check if it is not already defined
9be3b2
                     if c.variables.keys().isdisjoint(defined_variables):
9be3b2
                         eligible_controls.append(c)
9be3b2
-                        defined_variables += [*c.variables.keys()]
9be3b2
+                        defined_variables += list(c.variables.keys())
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From 95a23a31293a0a63361ddf1831866cd5ae1ab61e Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 5 Aug 2021 16:30:10 +0200
9be3b2
Subject: [PATCH 03/12] rework handling of variables when returning all
9be3b2
 controls of a level
9be3b2
9be3b2
currently only the top most level variables are kept in the controls
9be3b2
if there is a control with lower level which has the same variable defined, it is deep copied and the variable definition is removed only from the resulting control
9be3b2
the original control stays in tact
9be3b2
---
9be3b2
 ssg/controls.py | 27 +++++++++++++++++++++------
9be3b2
 1 file changed, 21 insertions(+), 6 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 611a647e125..4ebb8bda3d7 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -1,8 +1,8 @@
9be3b2
 import collections
9be3b2
 import logging
9be3b2
 import os
9be3b2
+import copy
9be3b2
 from glob import glob
9be3b2
-from collections import OrderedDict
9be3b2
 
9be3b2
 import ssg.build_yaml
9be3b2
 import ssg.yaml
9be3b2
@@ -154,7 +154,7 @@ def get_level(self, level_id):
9be3b2
 
9be3b2
     def get_level_with_ancestors(self, level_id):
9be3b2
         # use OrderedDict for Python2 compatibility instead of ordered set
9be3b2
-        levels = OrderedDict()
9be3b2
+        levels = collections.OrderedDict()
9be3b2
         level = self.get_level(level_id)
9be3b2
         levels[level] = ""
9be3b2
         if level.inherits_from:
9be3b2
@@ -201,24 +201,39 @@ def _get_policy(self, policy_id):
9be3b2
     def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
+        print ("getting levels of " + level_id)
9be3b2
+        print ([ l.id for l in levels.keys()])
9be3b2
         # we use OrderedDict here with empty values instead of ordered set
9be3b2
         # cause we want to be compatible with python 2
9be3b2
-        level_ids = OrderedDict()
9be3b2
+        level_ids = collections.OrderedDict()
9be3b2
         for lv in levels.keys():
9be3b2
             level_ids[lv.id] = ""
9be3b2
-
9be3b2
+        print (level_ids.keys())
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
         defined_variables = []
9be3b2
         # we will go level by level, from top to bottom
9be3b2
         # this is done to enable overriding of variables by higher levels
9be3b2
         for lv in level_ids.keys():
9be3b2
+            print ("going through level " +lv)
9be3b2
             for c in all_policy_controls:
9be3b2
+                print (c.levels)
9be3b2
                 if lv in c.levels:
9be3b2
                     # if the control has a variable, check if it is not already defined
9be3b2
-                    if c.variables.keys().isdisjoint(defined_variables):
9be3b2
+                    variables = list(c.variables.keys())
9be3b2
+                    if len(variables) == 0:
9be3b2
                         eligible_controls.append(c)
9be3b2
-                        defined_variables += list(c.variables.keys())
9be3b2
+                    for var in variables:
9be3b2
+                        if var in defined_variables:
9be3b2
+                            # if it is, create new instance of the control and remove the variable
9be3b2
+                            # we are going from the top level to the bottom
9be3b2
+                            # so we don't want to overwrite variables
9be3b2
+                            new_c = copy.deepcopy(c)
9be3b2
+                            del new_c.variables[var]
9be3b2
+                            eligible_controls.append(new_c)
9be3b2
+                        else:
9be3b2
+                            defined_variables.append(var)
9be3b2
+                            eligible_controls.append(c)
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From a2dd7e9386c757a523b57646bdc5a9ffa99f68c5 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 5 Aug 2021 16:31:25 +0200
9be3b2
Subject: [PATCH 04/12] add tests for defining of variables
9be3b2
9be3b2
---
9be3b2
 tests/unit/ssg-module/data/controls_dir/abcd-levels.yml | 6 ++++++
9be3b2
 tests/unit/ssg-module/test_controls.py                  | 5 +++++
9be3b2
 2 files changed, 11 insertions(+)
9be3b2
9be3b2
diff --git a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
index aded77c12a6..b98a7cd4e19 100644
9be3b2
--- a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
+++ b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
@@ -19,10 +19,14 @@ controls:
9be3b2
   - id: S2
9be3b2
     levels:
9be3b2
     - low
9be3b2
+    rules:
9be3b2
+      - var_password_pam_minlen=1
9be3b2
 
9be3b2
   - id: S3
9be3b2
     levels:
9be3b2
     - medium
9be3b2
+    rules:
9be3b2
+      - var_password_pam_minlen=2
9be3b2
 
9be3b2
   - id: S4
9be3b2
     title: Configure authentication
9be3b2
@@ -36,3 +40,5 @@ controls:
9be3b2
         title: Enforce password quality standards
9be3b2
         levels:
9be3b2
         - high
9be3b2
+        rules:
9be3b2
+          - var_password_pam_minlen=3
9be3b2
diff --git a/tests/unit/ssg-module/test_controls.py b/tests/unit/ssg-module/test_controls.py
9be3b2
index ff9b04f26c9..06fcb0c375d 100644
9be3b2
--- a/tests/unit/ssg-module/test_controls.py
9be3b2
+++ b/tests/unit/ssg-module/test_controls.py
9be3b2
@@ -87,6 +87,11 @@ def test_controls_levels():
9be3b2
     assert len(low_controls) == 4
9be3b2
     assert len(medium_controls) == 5
9be3b2
 
9be3b2
+    # test overriding of variables in levels
9be3b2
+    assert c_2.variables["var_password_pam_minlen"] == "1"
9be3b2
+    assert c_3.variables["var_password_pam_minlen"] == "2"
9be3b2
+    assert c_4b.variables["var_password_pam_minlen"] == "3"
9be3b2
+
9be3b2
 
9be3b2
 def test_controls_load_product():
9be3b2
     ssg_root = \
9be3b2
9be3b2
From 82b90a9720dadab7d6060f0ccbcd902b1c097904 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Fri, 6 Aug 2021 09:30:47 +0200
9be3b2
Subject: [PATCH 05/12] make overriding of variables optional
9be3b2
9be3b2
---
9be3b2
 ssg/controls.py | 38 +++++++++++++++++++-------------------
9be3b2
 1 file changed, 19 insertions(+), 19 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 4ebb8bda3d7..90639fbe4c7 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -198,42 +198,42 @@ def _get_policy(self, policy_id):
9be3b2
             raise ValueError(msg)
9be3b2
         return policy
9be3b2
 
9be3b2
-    def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
+    def get_all_controls_of_level(self, policy_id, level_id, override_vars=True):
9be3b2
+        # if override_vars is enabled, then variables from higher levels will
9be3b2
+        # override variables efined in controls of lower levels
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
-        print ("getting levels of " + level_id)
9be3b2
-        print ([ l.id for l in levels.keys()])
9be3b2
         # we use OrderedDict here with empty values instead of ordered set
9be3b2
         # cause we want to be compatible with python 2
9be3b2
         level_ids = collections.OrderedDict()
9be3b2
         for lv in levels.keys():
9be3b2
             level_ids[lv.id] = ""
9be3b2
-        print (level_ids.keys())
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
         defined_variables = []
9be3b2
         # we will go level by level, from top to bottom
9be3b2
         # this is done to enable overriding of variables by higher levels
9be3b2
         for lv in level_ids.keys():
9be3b2
-            print ("going through level " +lv)
9be3b2
             for c in all_policy_controls:
9be3b2
-                print (c.levels)
9be3b2
                 if lv in c.levels:
9be3b2
-                    # if the control has a variable, check if it is not already defined
9be3b2
-                    variables = list(c.variables.keys())
9be3b2
-                    if len(variables) == 0:
9be3b2
+                    if override_vars == False:
9be3b2
                         eligible_controls.append(c)
9be3b2
-                    for var in variables:
9be3b2
-                        if var in defined_variables:
9be3b2
-                            # if it is, create new instance of the control and remove the variable
9be3b2
-                            # we are going from the top level to the bottom
9be3b2
-                            # so we don't want to overwrite variables
9be3b2
-                            new_c = copy.deepcopy(c)
9be3b2
-                            del new_c.variables[var]
9be3b2
-                            eligible_controls.append(new_c)
9be3b2
-                        else:
9be3b2
-                            defined_variables.append(var)
9be3b2
+                    else:
9be3b2
+                        # if the control has a variable, check if it is not already defined
9be3b2
+                        variables = list(c.variables.keys())
9be3b2
+                        if len(variables) == 0:
9be3b2
                             eligible_controls.append(c)
9be3b2
+                        for var in variables:
9be3b2
+                            if var in defined_variables:
9be3b2
+                                # if it is, create new instance of the control and remove the variable
9be3b2
+                                # we are going from the top level to the bottom
9be3b2
+                                # so we don't want to overwrite variables
9be3b2
+                                new_c = copy.deepcopy(c)
9be3b2
+                                del new_c.variables[var]
9be3b2
+                                eligible_controls.append(new_c)
9be3b2
+                            else:
9be3b2
+                                defined_variables.append(var)
9be3b2
+                                eligible_controls.append(c)
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From 47df80d086e96deb4eab88d5f813bffb380006a8 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Wed, 11 Aug 2021 12:38:42 +0200
9be3b2
Subject: [PATCH 06/12] fix a typo
9be3b2
9be3b2
---
9be3b2
 ssg/controls.py | 2 +-
9be3b2
 1 file changed, 1 insertion(+), 1 deletion(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 90639fbe4c7..10a304bf8c2 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -200,7 +200,7 @@ def _get_policy(self, policy_id):
9be3b2
 
9be3b2
     def get_all_controls_of_level(self, policy_id, level_id, override_vars=True):
9be3b2
         # if override_vars is enabled, then variables from higher levels will
9be3b2
-        # override variables efined in controls of lower levels
9be3b2
+        # override variables defined in controls of lower levels
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
         # we use OrderedDict here with empty values instead of ordered set
9be3b2
9be3b2
From 8e59037ed07aad33a55e8297ee5bce0f51c0dee6 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Wed, 11 Aug 2021 17:02:11 +0200
9be3b2
Subject: [PATCH 07/12] update tests to check that overriding of variables
9be3b2
 works
9be3b2
9be3b2
---
9be3b2
 .../ssg-module/data/controls_dir/abcd-levels.yml |  4 +---
9be3b2
 tests/unit/ssg-module/test_controls.py           | 16 ++++++++++++++--
9be3b2
 2 files changed, 15 insertions(+), 5 deletions(-)
9be3b2
9be3b2
diff --git a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
index b98a7cd4e19..99efafd832e 100644
9be3b2
--- a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
+++ b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
@@ -25,8 +25,6 @@ controls:
9be3b2
   - id: S3
9be3b2
     levels:
9be3b2
     - medium
9be3b2
-    rules:
9be3b2
-      - var_password_pam_minlen=2
9be3b2
 
9be3b2
   - id: S4
9be3b2
     title: Configure authentication
9be3b2
@@ -41,4 +39,4 @@ controls:
9be3b2
         levels:
9be3b2
         - high
9be3b2
         rules:
9be3b2
-          - var_password_pam_minlen=3
9be3b2
+          - var_password_pam_minlen=2
9be3b2
diff --git a/tests/unit/ssg-module/test_controls.py b/tests/unit/ssg-module/test_controls.py
9be3b2
index 06fcb0c375d..124b344d141 100644
9be3b2
--- a/tests/unit/ssg-module/test_controls.py
9be3b2
+++ b/tests/unit/ssg-module/test_controls.py
9be3b2
@@ -89,8 +89,20 @@ def test_controls_levels():
9be3b2
 
9be3b2
     # test overriding of variables in levels
9be3b2
     assert c_2.variables["var_password_pam_minlen"] == "1"
9be3b2
-    assert c_3.variables["var_password_pam_minlen"] == "2"
9be3b2
-    assert c_4b.variables["var_password_pam_minlen"] == "3"
9be3b2
+    assert "var_password_pam_minlen" not in c_3.variables.keys()
9be3b2
+    assert c_4b.variables["var_password_pam_minlen"] == "2"
9be3b2
+
9be3b2
+    for c in low_controls:
9be3b2
+        if "var_password_pam_minlen" in c.variables.keys():
9be3b2
+            assert c.variables["var_password_pam_minlen"] == "1"
9be3b2
+
9be3b2
+    for c in medium_controls:
9be3b2
+        if "var_password_pam_minlen" in c.variables.keys():
9be3b2
+            assert c.variables["var_password_pam_minlen"] == "1"
9be3b2
+
9be3b2
+    for c in high_controls:
9be3b2
+        if "var_password_pam_minlen" in c.variables.keys():
9be3b2
+            assert c.variables["var_password_pam_minlen"] == "2"
9be3b2
 
9be3b2
 
9be3b2
 def test_controls_load_product():
9be3b2
9be3b2
From dae4fc52a627eac6595bb73e3ffb1a0c50e78fdd Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Wed, 11 Aug 2021 17:02:32 +0200
9be3b2
Subject: [PATCH 08/12] make overriding of variables hardcoded when requesting
9be3b2
 controls of a certain level
9be3b2
9be3b2
---
9be3b2
 ssg/controls.py | 34 +++++++++++++++-------------------
9be3b2
 1 file changed, 15 insertions(+), 19 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 10a304bf8c2..7923f0cb379 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -198,9 +198,7 @@ def _get_policy(self, policy_id):
9be3b2
             raise ValueError(msg)
9be3b2
         return policy
9be3b2
 
9be3b2
-    def get_all_controls_of_level(self, policy_id, level_id, override_vars=True):
9be3b2
-        # if override_vars is enabled, then variables from higher levels will
9be3b2
-        # override variables defined in controls of lower levels
9be3b2
+    def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
         # we use OrderedDict here with empty values instead of ordered set
9be3b2
@@ -216,24 +214,22 @@ def get_all_controls_of_level(self, policy_id, level_id, override_vars=True):
9be3b2
         for lv in level_ids.keys():
9be3b2
             for c in all_policy_controls:
9be3b2
                 if lv in c.levels:
9be3b2
-                    if override_vars == False:
9be3b2
+                    # if the control has a variable, check if it is not already defined
9be3b2
+                    variables = list(c.variables.keys())
9be3b2
+                    if len(variables) == 0:
9be3b2
                         eligible_controls.append(c)
9be3b2
-                    else:
9be3b2
-                        # if the control has a variable, check if it is not already defined
9be3b2
-                        variables = list(c.variables.keys())
9be3b2
-                        if len(variables) == 0:
9be3b2
+                        continue
9be3b2
+                    for var in variables:
9be3b2
+                        if var in defined_variables:
9be3b2
+                            # if it is, create new instance of the control and remove the variable
9be3b2
+                            # we are going from the top level to the bottom
9be3b2
+                            # so we don't want to overwrite variables
9be3b2
+                            new_c = copy.deepcopy(c)
9be3b2
+                            del new_c.variables[var]
9be3b2
+                            eligible_controls.append(new_c)
9be3b2
+                        else:
9be3b2
+                            defined_variables.append(var)
9be3b2
                             eligible_controls.append(c)
9be3b2
-                        for var in variables:
9be3b2
-                            if var in defined_variables:
9be3b2
-                                # if it is, create new instance of the control and remove the variable
9be3b2
-                                # we are going from the top level to the bottom
9be3b2
-                                # so we don't want to overwrite variables
9be3b2
-                                new_c = copy.deepcopy(c)
9be3b2
-                                del new_c.variables[var]
9be3b2
-                                eligible_controls.append(new_c)
9be3b2
-                            else:
9be3b2
-                                defined_variables.append(var)
9be3b2
-                                eligible_controls.append(c)
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From c051e11c70b7e23ce3d4a8e0670da4fae72833c6 Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 12 Aug 2021 15:30:39 +0200
9be3b2
Subject: [PATCH 09/12] get rid of one ordereddict
9be3b2
9be3b2
---
9be3b2
 ssg/controls.py | 9 ++-------
9be3b2
 1 file changed, 2 insertions(+), 7 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 7923f0cb379..891b13c891c 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -201,19 +201,14 @@ def _get_policy(self, policy_id):
9be3b2
     def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         levels = policy.get_level_with_ancestors(level_id)
9be3b2
-        # we use OrderedDict here with empty values instead of ordered set
9be3b2
-        # cause we want to be compatible with python 2
9be3b2
-        level_ids = collections.OrderedDict()
9be3b2
-        for lv in levels.keys():
9be3b2
-            level_ids[lv.id] = ""
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
         defined_variables = []
9be3b2
         # we will go level by level, from top to bottom
9be3b2
         # this is done to enable overriding of variables by higher levels
9be3b2
-        for lv in level_ids.keys():
9be3b2
+        for lv in levels.keys():
9be3b2
             for c in all_policy_controls:
9be3b2
-                if lv in c.levels:
9be3b2
+                if lv.id in c.levels:
9be3b2
                     # if the control has a variable, check if it is not already defined
9be3b2
                     variables = list(c.variables.keys())
9be3b2
                     if len(variables) == 0:
9be3b2
9be3b2
From 4dd5cb1326932cf020785a8c2472998eb2e7775e Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 12 Aug 2021 16:44:57 +0200
9be3b2
Subject: [PATCH 10/12] fix overriding of variables
9be3b2
9be3b2
when there were multiple variables overridden, it caused problems by creating multiple copies of controls
9be3b2
---
9be3b2
 ssg/controls.py | 16 +++++++++-------
9be3b2
 1 file changed, 9 insertions(+), 7 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 891b13c891c..8b69676313c 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -214,17 +214,19 @@ def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
                     if len(variables) == 0:
9be3b2
                         eligible_controls.append(c)
9be3b2
                         continue
9be3b2
+                    variables_to_remove = [] # contains list of variables which are already defined and should be removed from the control
9be3b2
                     for var in variables:
9be3b2
                         if var in defined_variables:
9be3b2
-                            # if it is, create new instance of the control and remove the variable
9be3b2
-                            # we are going from the top level to the bottom
9be3b2
-                            # so we don't want to overwrite variables
9be3b2
-                            new_c = copy.deepcopy(c)
9be3b2
-                            del new_c.variables[var]
9be3b2
-                            eligible_controls.append(new_c)
9be3b2
+                            variables_to_remove.append(var)
9be3b2
                         else:
9be3b2
                             defined_variables.append(var)
9be3b2
-                            eligible_controls.append(c)
9be3b2
+                    if len(variables_to_remove) == 0:
9be3b2
+                        eligible_controls.append(c)
9be3b2
+                    else:
9be3b2
+                        new_c = copy.deepcopy(c)
9be3b2
+                        for var in variables_to_remove:
9be3b2
+                            del new_c.variables[var]
9be3b2
+                        eligible_controls.append(new_c)
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
9be3b2
From fbebba524cab090bc4c2f92b75257a7cc881ef5e Mon Sep 17 00:00:00 2001
9be3b2
From: Vojtech Polasek <vpolasek@redhat.com>
9be3b2
Date: Thu, 12 Aug 2021 16:45:38 +0200
9be3b2
Subject: [PATCH 11/12] extended tests to test for multiple overridden
9be3b2
 variables
9be3b2
9be3b2
---
9be3b2
 .../data/controls_dir/abcd-levels.yml         |  2 ++
9be3b2
 tests/unit/ssg-module/test_controls.py        | 19 +++++++++++++++++++
9be3b2
 2 files changed, 21 insertions(+)
9be3b2
9be3b2
diff --git a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
index 99efafd832e..2e60ec43532 100644
9be3b2
--- a/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
+++ b/tests/unit/ssg-module/data/controls_dir/abcd-levels.yml
9be3b2
@@ -21,6 +21,7 @@ controls:
9be3b2
     - low
9be3b2
     rules:
9be3b2
       - var_password_pam_minlen=1
9be3b2
+      - var_some_variable=1
9be3b2
 
9be3b2
   - id: S3
9be3b2
     levels:
9be3b2
@@ -40,3 +41,4 @@ controls:
9be3b2
         - high
9be3b2
         rules:
9be3b2
           - var_password_pam_minlen=2
9be3b2
+          - var_some_variable=3
9be3b2
diff --git a/tests/unit/ssg-module/test_controls.py b/tests/unit/ssg-module/test_controls.py
9be3b2
index 124b344d141..1465661b04a 100644
9be3b2
--- a/tests/unit/ssg-module/test_controls.py
9be3b2
+++ b/tests/unit/ssg-module/test_controls.py
9be3b2
@@ -104,6 +104,25 @@ def test_controls_levels():
9be3b2
         if "var_password_pam_minlen" in c.variables.keys():
9be3b2
             assert c.variables["var_password_pam_minlen"] == "2"
9be3b2
 
9be3b2
+    # now test if controls of lower level has the variable definition correctly removed
9be3b2
+    # because it is overriden by higher level controls
9be3b2
+    s2_high = [c for c in high_controls if c.id == "S2"]
9be3b2
+    assert len(s2_high) == 1
9be3b2
+    assert "var_some_variable" not in s2_high[0].variables.keys()
9be3b2
+    assert "var_password_pam_minlen" not in s2_high[0].variables.keys()
9be3b2
+    s4b_high = [c for c in high_controls if c.id == "S4.b"]
9be3b2
+    assert len(s4b_high) == 1
9be3b2
+    assert s4b_high[0].variables["var_some_variable"] == "3"
9be3b2
+    assert s4b_high[0].variables["var_password_pam_minlen"] == "2"
9be3b2
+
9be3b2
+    # check that in low level the variable is correctly placed there in S2
9be3b2
+    s2_low = [c for c in low_controls if c.id == "S2"]
9be3b2
+    assert len(s2_low) == 1
9be3b2
+    assert s2_low[0].variables["var_some_variable"] == "1"
9be3b2
+    assert s2_low[0].variables["var_password_pam_minlen"] == "1"
9be3b2
+
9be3b2
+
9be3b2
+
9be3b2
 
9be3b2
 def test_controls_load_product():
9be3b2
     ssg_root = \
9be3b2
9be3b2
From 369de6b8374084d9d607979b712285912dbb65aa Mon Sep 17 00:00:00 2001
9be3b2
From: Matej Tyc <matyc@redhat.com>
9be3b2
Date: Mon, 16 Aug 2021 10:39:22 +0200
9be3b2
Subject: [PATCH 12/12] Style improvements
9be3b2
9be3b2
- Renamed get_level_with_ancestors to get_level_with_ancestors_sequence,
9be3b2
  and made it return a list - a dictionary result is quite confusing.
9be3b2
- Removed some optimization in the variable deletion loops.
9be3b2
- Extracted functionality to a _get_control_without_variables static
9be3b2
  method.
9be3b2
- Defined variable removal steps using set operations.
9be3b2
---
9be3b2
 ssg/controls.py | 54 +++++++++++++++++++++++++------------------------
9be3b2
 1 file changed, 28 insertions(+), 26 deletions(-)
9be3b2
9be3b2
diff --git a/ssg/controls.py b/ssg/controls.py
9be3b2
index 8b69676313c..ca3187d5b16 100644
9be3b2
--- a/ssg/controls.py
9be3b2
+++ b/ssg/controls.py
9be3b2
@@ -152,17 +152,17 @@ def get_level(self, level_id):
9be3b2
             )
9be3b2
             raise ValueError(msg)
9be3b2
 
9be3b2
-    def get_level_with_ancestors(self, level_id):
9be3b2
+    def get_level_with_ancestors_sequence(self, level_id):
9be3b2
         # use OrderedDict for Python2 compatibility instead of ordered set
9be3b2
         levels = collections.OrderedDict()
9be3b2
         level = self.get_level(level_id)
9be3b2
         levels[level] = ""
9be3b2
         if level.inherits_from:
9be3b2
             for lv in level.inherits_from:
9be3b2
-                eligible_levels = [l for l in self.get_level_with_ancestors(lv).keys() if l not in levels.keys()]
9be3b2
+                eligible_levels = [l for l in self.get_level_with_ancestors_sequence(lv) if l not in levels.keys()]
9be3b2
                 for l in eligible_levels:
9be3b2
                     levels[l] = ""
9be3b2
-        return levels
9be3b2
+        return list(levels.keys())
9be3b2
 
9be3b2
 
9be3b2
 class ControlsManager():
9be3b2
@@ -200,35 +200,37 @@ def _get_policy(self, policy_id):
9be3b2
 
9be3b2
     def get_all_controls_of_level(self, policy_id, level_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
-        levels = policy.get_level_with_ancestors(level_id)
9be3b2
+        levels = policy.get_level_with_ancestors_sequence(level_id)
9be3b2
         all_policy_controls = self.get_all_controls(policy_id)
9be3b2
         eligible_controls = []
9be3b2
-        defined_variables = []
9be3b2
+        already_defined_variables = set()
9be3b2
         # we will go level by level, from top to bottom
9be3b2
         # this is done to enable overriding of variables by higher levels
9be3b2
-        for lv in levels.keys():
9be3b2
-            for c in all_policy_controls:
9be3b2
-                if lv.id in c.levels:
9be3b2
-                    # if the control has a variable, check if it is not already defined
9be3b2
-                    variables = list(c.variables.keys())
9be3b2
-                    if len(variables) == 0:
9be3b2
-                        eligible_controls.append(c)
9be3b2
-                        continue
9be3b2
-                    variables_to_remove = [] # contains list of variables which are already defined and should be removed from the control
9be3b2
-                    for var in variables:
9be3b2
-                        if var in defined_variables:
9be3b2
-                            variables_to_remove.append(var)
9be3b2
-                        else:
9be3b2
-                            defined_variables.append(var)
9be3b2
-                    if len(variables_to_remove) == 0:
9be3b2
-                        eligible_controls.append(c)
9be3b2
-                    else:
9be3b2
-                        new_c = copy.deepcopy(c)
9be3b2
-                        for var in variables_to_remove:
9be3b2
-                            del new_c.variables[var]
9be3b2
-                        eligible_controls.append(new_c)
9be3b2
+        for lv in levels:
9be3b2
+            for control in all_policy_controls:
9be3b2
+                if lv.id not in control.levels:
9be3b2
+                    continue
9be3b2
+
9be3b2
+                variables = set(control.variables.keys())
9be3b2
+
9be3b2
+                variables_to_remove = variables.intersection(already_defined_variables)
9be3b2
+                already_defined_variables.update(variables)
9be3b2
+
9be3b2
+                new_c = self._get_control_without_variables(variables_to_remove, control)
9be3b2
+                eligible_controls.append(new_c)
9be3b2
+
9be3b2
         return eligible_controls
9be3b2
 
9be3b2
+    @staticmethod
9be3b2
+    def _get_control_without_variables(variables_to_remove, control):
9be3b2
+        if not variables_to_remove:
9be3b2
+            return control
9be3b2
+
9be3b2
+        new_c = copy.deepcopy(control)
9be3b2
+        for var in variables_to_remove:
9be3b2
+            del new_c.variables[var]
9be3b2
+        return new_c
9be3b2
+
9be3b2
     def get_all_controls(self, policy_id):
9be3b2
         policy = self._get_policy(policy_id)
9be3b2
         return policy.controls_by_id.values()