setools: add PolicyDifference class

Begin diff functions by implementing type difference function.

Closes #32
This commit is contained in:
Chris PeBenito 2015-12-17 10:38:04 -05:00
parent 3f7b428b70
commit eae9ff97d5
6 changed files with 579 additions and 0 deletions

View File

@ -65,3 +65,6 @@ from .permmap import PermissionMap
# Domain Transition Analysis
from .dta import DomainTransitionAnalysis
# Policy difference
from .diff import PolicyDifference

183
setools/diff.py Normal file
View File

@ -0,0 +1,183 @@
# Copyright 2015, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 2.1 of
# the License, or (at your option) any later version.
#
# SETools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with SETools. If not, see
# <http://www.gnu.org/licenses/>.
#
import logging
from collections import namedtuple
from weakref import WeakKeyDictionary
__all__ = ['PolicyDifference']
modified_types_record = namedtuple("modified_type", ["added_attributes",
"removed_attributes",
"matched_attributes",
"modified_permissive",
"permissive",
"added_aliases",
"removed_aliases",
"matched_aliases"])
class DiffResultDescriptor(object):
"""Descriptor for managing diff results."""
# @properties could be used instead, but there are so
# many result attributes, this will keep the code more compact.
def __init__(self, diff_function):
self.diff_function = diff_function
# use weak references so instances can be
# garbage collected, rather than unnecessarily
# kept around due to this descriptor.
self.instances = WeakKeyDictionary()
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.instances.setdefault(obj, None) is None:
diff = getattr(obj, self.diff_function)
diff()
return self.instances[obj]
def __set__(self, obj, value):
self.instances[obj] = value
class PolicyDifference(object):
"""
Determine the differences between two policies.
All results are represented as str rather than the
original Python objects. This was done because the
source of the object becomes less clear (Python
set logic doesn't have any guarantees for set
intersection). Using str will prevent problems
if you expect to be using a symbol but it is
coming from the wrong policy.
"""
def __init__(self, left_policy, right_policy):
self.log = logging.getLogger(self.__class__.__name__)
self.left_policy = left_policy
self.right_policy = right_policy
#
# Policies to compare
#
@property
def left_policy(self):
return self._left_policy
@left_policy.setter
def left_policy(self, policy):
self._left_policy = policy
self._reset_diff()
@property
def right_policy(self):
return self._right_policy
@right_policy.setter
def right_policy(self, policy):
self._right_policy = policy
self._reset_diff()
#
# Type differences
#
added_types = DiffResultDescriptor("diff_types")
removed_types = DiffResultDescriptor("diff_types")
modified_types = DiffResultDescriptor("diff_types")
def diff_types(self):
"""Generate the difference in types between the policies."""
self.log.info(
"Generating type differences from {0.left_policy} to {0.right_policy}".format(self))
self.added_types, self.removed_types, matched_types = self._set_diff(
self.left_policy.types(), self.right_policy.types())
self.modified_types = dict()
for name in matched_types:
# Criteria for modified types
# 1. change to attribute set, or
# 2. change to alias set, or
# 3. different permissive setting
left_type = self.left_policy.lookup_type(name)
right_type = self.right_policy.lookup_type(name)
added_attr, removed_attr, matched_attr = self._set_diff(left_type.attributes(),
right_type.attributes())
added_aliases, removed_aliases, matched_aliases = self._set_diff(left_type.aliases(),
right_type.aliases())
left_permissive = left_type.ispermissive
right_permissive = right_type.ispermissive
mod_permissive = left_permissive != right_permissive
if added_attr or removed_attr or added_aliases or removed_aliases or mod_permissive:
self.modified_types[name] = modified_types_record(added_attr,
removed_attr,
matched_attr,
mod_permissive,
left_permissive,
added_aliases,
removed_aliases,
matched_aliases)
#
# Internal functions
#
def _reset_diff(self):
"""Reset diff results on policy changes."""
self.added_types = None
self.removed_types = None
self.modified_types = None
@staticmethod
def _set_diff(left, right):
"""
Standard diff of two sets.
Parameters:
left An iterable
right An iterable
Return:
tuple (added, removed, matched)
added Set of items in right but not left
removed Set of items in left but not right
matched Set of items in both left and right
"""
left_items = set(str(l) for l in left)
right_items = set(str(r) for r in right)
added_items = right_items - left_items
removed_items = left_items - right_items
matched_items = left_items & right_items
return added_items, removed_items, matched_items

View File

@ -19,6 +19,7 @@ from . import boolquery
from . import categoryquery
from . import constraintquery
from . import commonquery
from . import diff
from . import dta
from . import fsusequery
from . import genfsconquery

138
tests/diff.py Normal file
View File

@ -0,0 +1,138 @@
# Copyright 2015, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# SETools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SETools. If not, see <http://www.gnu.org/licenses/>.
#
import unittest
from setools import SELinuxPolicy, PolicyDifference
class PolicyDifferenceTest(unittest.TestCase):
"""Policy difference tests."""
def setUp(self):
self.diff = PolicyDifference(SELinuxPolicy("tests/diff_left.conf"),
SELinuxPolicy("tests/diff_right.conf"))
def test_001_added_types(self):
"""Diff: added type"""
self.assertSetEqual(set(["added_type"]), self.diff.added_types)
def test_002_removed_types(self):
"""Diff: modified type"""
self.assertSetEqual(set(["removed_type"]), self.diff.removed_types)
def test_003_modified_types_count(self):
"""Diff: total modified types"""
self.assertEqual(6, len(self.diff.modified_types))
def test_004_modified_types_remove_attr(self):
"""Diff: modified type with removed attribute."""
self.assertIn("modified_remove_attr", self.diff.modified_types)
removed_attrs = self.diff.modified_types["modified_remove_attr"].removed_attributes
self.assertSetEqual(set(["an_attr"]), removed_attrs)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].added_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].matched_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].modified_permissive)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].permissive)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].added_aliases)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].removed_aliases)
self.assertFalse(self.diff.modified_types["modified_remove_attr"].matched_aliases)
def test_005_modified_types_remove_alias(self):
"""Diff: modified type with removed alias."""
self.assertIn("modified_remove_alias", self.diff.modified_types)
removed_alias = self.diff.modified_types["modified_remove_alias"].removed_aliases
self.assertSetEqual(set(["an_alias"]), removed_alias)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].added_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].removed_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].matched_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].modified_permissive)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].permissive)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].added_aliases)
self.assertFalse(self.diff.modified_types["modified_remove_alias"].matched_aliases)
def test_006_modified_types_remove_permissive(self):
"""Diff: modified type with removed permissve."""
self.assertIn("modified_remove_permissive", self.diff.modified_types)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].added_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].removed_attributes)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].matched_attributes)
self.assertTrue(self.diff.modified_types["modified_remove_permissive"].modified_permissive)
self.assertTrue(self.diff.modified_types["modified_remove_permissive"].permissive)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].added_aliases)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].removed_aliases)
self.assertFalse(self.diff.modified_types["modified_remove_permissive"].matched_aliases)
def test_007_modified_types_add_attr(self):
"""Diff: modified type with added attribute."""
self.assertIn("modified_add_attr", self.diff.modified_types)
added_attrs = self.diff.modified_types["modified_add_attr"].added_attributes
self.assertSetEqual(set(["an_attr"]), added_attrs)
self.assertFalse(self.diff.modified_types["modified_add_attr"].removed_attributes)
self.assertFalse(self.diff.modified_types["modified_add_attr"].matched_attributes)
self.assertFalse(self.diff.modified_types["modified_add_attr"].modified_permissive)
self.assertFalse(self.diff.modified_types["modified_add_attr"].permissive)
self.assertFalse(self.diff.modified_types["modified_add_attr"].added_aliases)
self.assertFalse(self.diff.modified_types["modified_add_attr"].removed_aliases)
self.assertFalse(self.diff.modified_types["modified_add_attr"].matched_aliases)
def test_008_modified_types_add_alias(self):
"""Diff: modified type with added alias."""
self.assertIn("modified_add_alias", self.diff.modified_types)
added_alias = self.diff.modified_types["modified_add_alias"].added_aliases
self.assertSetEqual(set(["an_alias"]), added_alias)
self.assertFalse(self.diff.modified_types["modified_add_alias"].added_attributes)
self.assertFalse(self.diff.modified_types["modified_add_alias"].removed_attributes)
self.assertFalse(self.diff.modified_types["modified_add_alias"].matched_attributes)
self.assertFalse(self.diff.modified_types["modified_add_alias"].modified_permissive)
self.assertFalse(self.diff.modified_types["modified_add_alias"].permissive)
self.assertFalse(self.diff.modified_types["modified_add_alias"].removed_aliases)
self.assertFalse(self.diff.modified_types["modified_add_alias"].matched_aliases)
def test_009_modified_types_add_permissive(self):
"""Diff: modified type with added permissive."""
self.assertIn("modified_add_permissive", self.diff.modified_types)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].added_attributes)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].removed_attributes)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].matched_attributes)
self.assertTrue(self.diff.modified_types["modified_add_permissive"].modified_permissive)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].permissive)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].added_aliases)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].removed_aliases)
self.assertFalse(self.diff.modified_types["modified_add_permissive"].matched_aliases)
class PolicyDifferenceTestNoDiff(unittest.TestCase):
"""Policy difference test with no policy differences."""
def setUp(self):
self.diff = PolicyDifference(SELinuxPolicy("tests/diff_left.conf"),
SELinuxPolicy("tests/diff_left.conf"))
def test_001_added_types(self):
"""NoDiff: no added types"""
self.assertFalse(self.diff.added_types)
def test_002_removed_types(self):
"""NoDiff: no removed types"""
self.assertFalse(self.diff.removed_types)
def test_003_modified_types(self):
"""NoDiff: no modified types"""
self.assertFalse(self.diff.modified_types)

127
tests/diff_left.conf Normal file
View File

@ -0,0 +1,127 @@
class infoflow
class infoflow2
class infoflow3
class infoflow4
class infoflow5
class infoflow6
class infoflow7
sid kernel
sid security
common infoflow
{
low_w
med_w
hi_w
low_r
med_r
hi_r
}
class infoflow
inherits infoflow
class infoflow2
inherits infoflow
{
super_w
super_r
}
class infoflow3
{
null
}
class infoflow4
inherits infoflow
class infoflow5
inherits infoflow
class infoflow6
inherits infoflow
class infoflow7
inherits infoflow
{
super_w
super_r
super_none
super_both
super_unmapped
}
sensitivity low_s;
sensitivity medium_s alias med;
sensitivity high_s;
dominance { low_s med high_s }
category here;
category there;
category elsewhere alias lost;
#level decl
level low_s:here.there;
level med:here, elsewhere;
level high_s:here.lost;
#some constraints
mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt));
attribute mls_exempt;
attribute an_attr;
type system;
role system;
role system types system;
################################################################################
# Type enforcement declarations and rules
type removed_type;
type modified_remove_attr, an_attr;
type modified_remove_alias alias an_alias;
type modified_remove_permissive;
permissive modified_remove_permissive;
type modified_add_attr;
type modified_add_alias;
type modified_add_permissive;
################################################################################
#users
user system roles system level med range low_s - high_s:here.lost;
#normal constraints
constrain infoflow hi_w (u1 == u2);
#isids
sid kernel system:system:system:medium_s:here
sid security system:system:system:high_s:lost
#fs_use
fs_use_trans devpts system:object_r:system:low_s;
fs_use_xattr ext3 system:object_r:system:low_s;
fs_use_task pipefs system:object_r:system:low_s;
#genfscon
genfscon proc / system:object_r:system:med
genfscon proc /sys system:object_r:system:low_s
genfscon selinuxfs / system:object_r:system:high_s:here.there
portcon tcp 80 system:object_r:system:low_s
netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s
nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here
nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here

127
tests/diff_right.conf Normal file
View File

@ -0,0 +1,127 @@
class infoflow
class infoflow2
class infoflow3
class infoflow4
class infoflow5
class infoflow6
class infoflow7
sid kernel
sid security
common infoflow
{
low_w
med_w
hi_w
low_r
med_r
hi_r
}
class infoflow
inherits infoflow
class infoflow2
inherits infoflow
{
super_w
super_r
}
class infoflow3
{
null
}
class infoflow4
inherits infoflow
class infoflow5
inherits infoflow
class infoflow6
inherits infoflow
class infoflow7
inherits infoflow
{
super_w
super_r
super_none
super_both
super_unmapped
}
sensitivity low_s;
sensitivity medium_s alias med;
sensitivity high_s;
dominance { low_s med high_s }
category here;
category there;
category elsewhere alias lost;
#level decl
level low_s:here.there;
level med:here, elsewhere;
level high_s:here.lost;
#some constraints
mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt));
attribute mls_exempt;
attribute an_attr;
type system;
role system;
role system types system;
################################################################################
# Type enforcement declarations and rules
type added_type;
type modified_remove_attr;
type modified_remove_alias;
type modified_remove_permissive;
type modified_add_attr, an_attr;
type modified_add_alias alias an_alias;
type modified_add_permissive;
permissive modified_add_permissive;
################################################################################
#users
user system roles system level med range low_s - high_s:here.lost;
#normal constraints
constrain infoflow hi_w (u1 == u2);
#isids
sid kernel system:system:system:medium_s:here
sid security system:system:system:high_s:lost
#fs_use
fs_use_trans devpts system:object_r:system:low_s;
fs_use_xattr ext3 system:object_r:system:low_s;
fs_use_task pipefs system:object_r:system:low_s;
#genfscon
genfscon proc / system:object_r:system:med
genfscon proc /sys system:object_r:system:low_s
genfscon selinuxfs / system:object_r:system:high_s:here.there
portcon tcp 80 system:object_r:system:low_s
netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s
nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here
nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here