diff --git a/sediff b/sediff index 025451c..550d0b5 100755 --- a/sediff +++ b/sediff @@ -395,6 +395,36 @@ try: print() + if all_differences or args.level: + if diff.added_levels or diff.removed_levels or \ + diff.modified_levels or args.level: + print("Levels ({0} Added, {1} Removed, {2} Modified)".format( + len(diff.added_levels), len(diff.removed_levels), + len(diff.modified_levels))) + if diff.added_levels and not args.stats: + print(" Added Levels: {0}".format(len(diff.added_levels))) + for l in sorted(diff.added_levels): + print(" + {0}".format(l)) + if diff.removed_levels and not args.stats: + print(" Removed Levels: {0}".format(len(diff.removed_levels))) + for l in sorted(diff.removed_levels): + print(" - {0}".format(l)) + if diff.modified_levels and not args.stats: + print(" Modified Levels: {0}".format(len(diff.modified_levels))) + for level, added_categories, removed_categories, _ in sorted(diff.modified_levels): + change = [] + if added_categories: + change.append("{0} Added Categories".format(len(added_categories))) + if removed_categories: + change.append("{0} Removed Categories".format(len(removed_categories))) + + print(" * {0} ({1})".format(level.sensitivity, ", ".join(change))) + for c in sorted(added_categories): + print(" + {0}".format(c)) + for c in sorted(removed_categories): + print(" - {0}".format(c)) + print() + if all_differences or args.allow: if diff.added_allows or diff.removed_allows or diff.modified_allows or args.allow: print("Allow Rules ({0} Added, {1} Removed, {2} Modified)".format( diff --git a/setools/diff/__init__.py b/setools/diff/__init__.py index 2f36614..69bc22b 100644 --- a/setools/diff/__init__.py +++ b/setools/diff/__init__.py @@ -21,7 +21,7 @@ from .commons import CommonDifference from .fsuse import FSUsesDifference from .genfscon import GenfsconsDifference from .initsid import InitialSIDsDifference -from .mls import CategoriesDifference, SensitivitiesDifference +from .mls import CategoriesDifference, LevelDeclsDifference, SensitivitiesDifference from .mlsrules import MLSRulesDifference from .objclass import ObjClassDifference from .rbacrules import RBACRulesDifference @@ -40,6 +40,7 @@ class PolicyDifference(BooleansDifference, FSUsesDifference, GenfsconsDifference, InitialSIDsDifference, + LevelDeclsDifference, MLSRulesDifference, ObjClassDifference, RBACRulesDifference, diff --git a/setools/diff/mls.py b/setools/diff/mls.py index a440353..c9142ba 100644 --- a/setools/diff/mls.py +++ b/setools/diff/mls.py @@ -29,6 +29,11 @@ modified_sens_record = namedtuple("modified_sensitivity", ["added_aliases", "removed_aliases", "matched_aliases"]) +modified_level_record = namedtuple("modified_level", ["level", + "added_categories", + "removed_categories", + "matched_categories"]) + class CategoriesDifference(Difference): @@ -116,6 +121,71 @@ class SensitivitiesDifference(Difference): self.modified_sensitivities = None +class LevelDeclsDifference(Difference): + + """Determine the difference in levels between two policies.""" + + added_levels = DiffResultDescriptor("diff_levels") + removed_levels = DiffResultDescriptor("diff_levels") + modified_levels = DiffResultDescriptor("diff_levels") + + def diff_levels(self): + """Generate the difference in levels between the policies.""" + + self.log.info( + "Generating level decl differences from {0.left_policy} to {0.right_policy}". + format(self)) + + self.added_levels, self.removed_levels, matched_levels = \ + self._set_diff( + (LevelDeclWrapper(s) for s in self.left_policy.levels()), + (LevelDeclWrapper(s) for s in self.right_policy.levels())) + + self.modified_levels = [] + + for left_level, right_level in matched_levels: + # Criteria for modified levels + # 1. change to allowed categories + added_categories, removed_categories, matched_categories = self._set_diff( + (SymbolWrapper(c) for c in left_level.categories()), + (SymbolWrapper(c) for c in right_level.categories())) + + if added_categories or removed_categories: + self.modified_levels.append(modified_level_record( + left_level, added_categories, removed_categories, matched_categories)) + + # + # Internal functions + # + def _reset_diff(self): + """Reset diff results on policy changes.""" + self.log.debug("Resetting sensitivity differences") + self.added_levels = None + self.removed_levels = None + self.modified_levels = None + + +class LevelDeclWrapper(Wrapper): + + """Wrap level declarations to allow comparisons.""" + + def __init__(self, level): + self.origin = level + self.sensitivity = SymbolWrapper(level.sensitivity) + self.key = hash(level) + + def __hash__(self): + return self.key + + def __eq__(self, other): + # non-MLS policies have no level declarations so there + # should be no AttributeError possiblity here + return self.sensitivity == other.sensitivity + + def __lt__(self, other): + return self.sensitivity < other.sensitivity + + class LevelWrapper(Wrapper): """Wrap levels to allow comparisons.""" diff --git a/tests/diff.py b/tests/diff.py index 72058a0..e03e251 100644 --- a/tests/diff.py +++ b/tests/diff.py @@ -1071,6 +1071,36 @@ class PolicyDifferenceTest(ValidateRule, unittest.TestCase): self.assertEqual("added_user:object_r:system:s0", added_context) self.assertEqual("removed_user:object_r:system:s0", removed_context) + # + # level decl + # + def test_added_levels(self): + """Diff: added levels.""" + l = sorted(self.diff.added_levels) + self.assertEqual(1, len(l)) + self.assertEqual("s46:c0.c4", l[0]) + + def test_removed_levels(self): + """Diff: removed levels.""" + l = sorted(self.diff.removed_levels) + self.assertEqual(1, len(l)) + self.assertEqual("s47:c0.c4", l[0]) + + def test_modified_levels(self): + """Diff: modified levels.""" + l = sorted(self.diff.modified_levels) + self.assertEqual(2, len(l)) + + level = l[0] + self.assertEqual("s40", level.level.sensitivity) + self.assertSetEqual(set(["c3"]), level.added_categories) + self.assertFalse(level.removed_categories) + + level = l[1] + self.assertEqual("s41", level.level.sensitivity) + self.assertFalse(level.added_categories) + self.assertSetEqual(set(["c4"]), level.removed_categories) + class PolicyDifferenceTestNoDiff(unittest.TestCase): @@ -1343,3 +1373,15 @@ class PolicyDifferenceTestNoDiff(unittest.TestCase): def test_modified_genfscons(self): """NoDiff: no modified genfscons.""" self.assertFalse(self.diff.modified_genfscons) + + def test_added_levels(self): + """NoDiff: no added levels.""" + self.assertFalse(self.diff.added_levels) + + def test_removed_levels(self): + """NoDiff: no removed levels.""" + self.assertFalse(self.diff.removed_levels) + + def test_modified_levels(self): + """NoDiff: no modified levels.""" + self.assertFalse(self.diff.modified_levels) diff --git a/tests/diff_left.conf b/tests/diff_left.conf index 1003a1d..d7832ea 100644 --- a/tests/diff_left.conf +++ b/tests/diff_left.conf @@ -121,7 +121,7 @@ level s0:c0.c4; level s1:c0.c4; level s2:c0.c4; level s3:c0.c4; -level s40:c0.c4; +level s40:c1; level s41:c0.c4; level s42:c0.c4; level s43:c0.c4; diff --git a/tests/diff_right.conf b/tests/diff_right.conf index 6387dd5..d81698e 100644 --- a/tests/diff_right.conf +++ b/tests/diff_right.conf @@ -121,8 +121,8 @@ level s0:c0.c4; level s1:c0.c4; level s2:c0.c4; level s3:c0.c4; -level s40:c0.c4; -level s41:c0.c4; +level s40:c1,c3; +level s41:c0.c3; level s42:c0.c4; level s43:c0.c4; level s44:c0.c4;