checker: Implement RBAC rule assertion check.

Signed-off-by: Chris PeBenito <pebenito@ieee.org>
This commit is contained in:
Chris PeBenito 2020-08-23 11:18:46 -04:00
parent 56b2fd1f47
commit 83f91adb2c
5 changed files with 584 additions and 0 deletions

View File

@ -108,6 +108,42 @@ in the expect_source list or exempt_source list to pass. Similarly, if a rule h
an attribute target, all of the member types must be in the expect_target list or
exempt_target list to pass.
.SH "ROLE BASED ACCESS CONTROL ALLOW RULE ASSERTION"
This checks for the nonexistence of role based access control (RBAC) allow rules. The
check_type is \fBassert_rbac\fR. It will run the query and any unexpected results
from the query, removing any exempted sources or targets, will be listed as failures.
Any expected results that are not seen will also be listed as failures.
.PP
Criteria options:
.IP "source = <role"
The source role criteria for the query.
.IP "target = <role>"
The target role criteria for the query.
.PP
\fBA least one of the above options must be set in this check.\fR
.PP
Additional Options:
.IP "expect_source = <role>[ ....]"
A space-separated list of roles. Each of these
roles must be seen as the source of a rule that matches the criteria.
At the end of the query, each unseen role in this list will be reported
as a failure. This is optional.
.IP "expect_target = <role>[ ....]"
A space-separated list of roles. Each of these
roles must be seen as the target of a rule that matches the criteria.
At the end of the query, each unseen role in this list will be reported
as a failure. This is optional.
.IP "exempt_source = <role>[ ....]"
A space-separated list of roles. Rules with these
as the source will be ignored. This is optional.
.IP "exempt_target = <role>[ ....]"
A space-separated list of roles. Rules with these
as the target will be ignored. This is optional.
.SH "EMPTY TYPE ATTRIBUTE ASSERTION"
This checks that the specified attribute is empty. This can optionally
be set to also pass if the attribute does not exist.

View File

@ -0,0 +1,116 @@
# Copyright 2020, Microsoft Corporation
# Copyright 2020, Chris PeBenito <pebenito@ieee.org>
#
# 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 ..exception import InvalidCheckValue, InvalidClass
from ..rbacrulequery import RBACRuleQuery
from .checkermodule import CheckerModule
from .descriptors import ConfigDescriptor, ConfigSetDescriptor
SOURCE_OPT = "source"
TARGET_OPT = "target"
EXEMPT_SRC_OPT = "exempt_source"
EXEMPT_TGT_OPT = "exempt_target"
EXPECT_SRC_OPT = "expect_source"
EXPECT_TGT_OPT = "expect_target"
class AssertRBAC(CheckerModule):
"""Checker module for asserting a RBAC allow rule exists (or not)."""
check_type = "assert_rbac"
check_config = frozenset((SOURCE_OPT, TARGET_OPT, EXEMPT_SRC_OPT, EXEMPT_TGT_OPT,
EXPECT_SRC_OPT, EXPECT_TGT_OPT))
source = ConfigDescriptor("lookup_role")
target = ConfigDescriptor("lookup_role")
exempt_source = ConfigSetDescriptor("lookup_role", strict=False, expand=True)
exempt_target = ConfigSetDescriptor("lookup_role", strict=False, expand=True)
expect_source = ConfigSetDescriptor("lookup_role", strict=True, expand=True)
expect_target = ConfigSetDescriptor("lookup_role", strict=True, expand=True)
def __init__(self, policy, checkname, config):
super().__init__(policy, checkname, config)
self.log = logging.getLogger(__name__)
self.source = config.get(SOURCE_OPT)
self.target = config.get(TARGET_OPT)
self.exempt_source = config.get(EXEMPT_SRC_OPT)
self.exempt_target = config.get(EXEMPT_TGT_OPT)
self.expect_source = config.get(EXPECT_SRC_OPT)
self.expect_target = config.get(EXPECT_TGT_OPT)
if not any((self.source, self.target)):
raise InvalidCheckValue(
"At least one of source or target options must be set.")
source_exempt_expect_overlap = self.exempt_source & self.expect_source
if source_exempt_expect_overlap:
self.log.info("Overlap in expect_source and exempt_source: {}".
format(", ".join(i.name for i in source_exempt_expect_overlap)))
target_exempt_expect_overlap = self.exempt_target & self.expect_target
if target_exempt_expect_overlap:
self.log.info("Overlap in expect_target and exempt_target: {}".
format(", ".join(i.name for i in target_exempt_expect_overlap)))
def run(self):
assert any((self.source, self.target)), "AssertRBAC no options set, this is a bug."
self.log.info("Checking RBAC allow rule assertion.")
query = RBACRuleQuery(self.policy,
source=self.source,
target=self.target,
ruletype=("allow",))
unseen_sources = set(self.expect_source)
unseen_targets = set(self.expect_target)
failures = []
for rule in sorted(query.results()):
srcs = set(rule.source.expand())
tgts = set(rule.target.expand())
unseen_sources -= srcs
unseen_targets -= tgts
if (srcs - self.expect_source - self.exempt_source) and \
(tgts - self.expect_target - self.exempt_target):
self.log_fail(str(rule))
failures.append(rule)
else:
self.log_ok(str(rule))
for item in unseen_sources:
failure = "Expected rule with source \"{}\" not found.".format(item)
self.log_fail(failure)
failures.append(failure)
for item in unseen_targets:
failure = "Expected rule with target \"{}\" not found.".format(item)
self.log_fail(failure)
failures.append(failure)
self.log.debug("{} failure(s)".format(failures))
return failures

View File

@ -17,6 +17,7 @@
# <http://www.gnu.org/licenses/>.
#
from . import assertrbac
from . import assertte
from . import checker
from . import emptyattr

View File

@ -0,0 +1,181 @@
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;
type system;
role system;
role system types system;
################################################################################
# Type enforcement declarations and rules
type test1a;
type test1b;
allow test1a test1b:infoflow hi_w;
################################################################################
# Roles for config tests:
role src;
role tgt;
role exempt_src1;
role exempt_tgt1;
role exempt_src2;
role exempt_tgt2;
role exempt_source_role;
role exempt_target_role;
# pass, exempt source
role source1;
role target1;
allow source1 target1;
# pass, exempt target
role source2;
role target2;
allow source2 target2;
# pass, expect source
role source3a;
role source3b;
role target3;
allow source3a target3;
allow source3b target3;
# pass, expect target
role source4;
role target4a;
role target4b;
allow source4 target4a;
allow source4 target4b;
# pass, expected and exempt sources
role source5a;
role source5b;
role target5;
allow source5a target5;
allow source5b target5;
# pass, expected and exempt targets
role source6;
role target6a;
role target6b;
allow source6 target6a;
allow source6 target6b;
# fail
role source7;
role target7a;
role target7b;
role target7c;
allow source7 target7a;
allow source7 target7b;
allow source7 target7c;
# fail, expect source
role source8;
role target8;
# fail, expect target
role source9;
role target9;
#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

250
tests/checker/assertrbac.py Normal file
View File

@ -0,0 +1,250 @@
# Copyright 2020, Microsoft Corporation
# Copyright 2020, Chris PeBenito <pebenito@ieee.org>
#
# 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 os
import unittest
from ..policyrep.util import compile_policy
from setools import RBACRuletype
from setools.checker.assertrbac import AssertRBAC
from setools.exception import InvalidCheckValue, InvalidCheckOption
class AssertRBACTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.p = compile_policy("tests/checker/assertrbac.conf")
@classmethod
def tearDownClass(cls):
os.unlink(cls.p.path)
def test_unconfigured(self):
"""Test unconfigured."""
with self.assertRaises(InvalidCheckValue):
config = {}
check = AssertRBAC(self.p, "test_unconfigured", config)
def test_invalid_option(self):
"""Test invalid option"""
with self.assertRaises(InvalidCheckOption):
config = {"INVALID": "option"}
check = AssertRBAC(self.p, "test_invalid_option", config)
def test_source(self):
"""Test source setting."""
with self.subTest("Success"):
config = {"source": "src"}
check = AssertRBAC(self.p, "test_source", config)
expected = self.p.lookup_role("src")
self.assertEqual(expected, check.source)
with self.subTest("Failure"):
with self.assertRaises(InvalidCheckValue):
config = {"source": "FAIL"}
check = AssertRBAC(self.p, "test_source_fail", config)
def test_target(self):
"""Test target setting."""
with self.subTest("Success"):
config = {"target": "tgt"}
check = AssertRBAC(self.p, "test_target", config)
expected = self.p.lookup_role("tgt")
self.assertEqual(expected, check.target)
with self.subTest("Failure"):
with self.assertRaises(InvalidCheckValue):
config = {"target": "FAIL2"}
check = AssertRBAC(self.p, "test_target_fail", config)
def test_exempt_source(self):
"""Test exempt_source setting."""
with self.subTest("Success"):
config = {"source": "system",
"exempt_source": " exempt_src1 exempt_src2 "}
check = AssertRBAC(self.p, "test_exempt_source", config)
# exempt_src2 is an attr
expected = set((self.p.lookup_role("exempt_src1"),
self.p.lookup_role("exempt_src2")))
self.assertIsInstance(check.exempt_source, frozenset)
self.assertSetEqual(expected, check.exempt_source)
with self.subTest("Success, missing role ignored"):
"""Test exempt_source missing role is ignroed."""
config = {"source": "system",
"exempt_source": "FAIL exempt_src2"}
check = AssertRBAC(self.p, "test_source_missing_ignored", config)
# exempt_src2 is an attr
expected = set((self.p.lookup_role("exempt_src2"),))
self.assertIsInstance(check.exempt_source, frozenset)
self.assertSetEqual(expected, check.exempt_source)
def test_exempt_target(self):
"""Test exempt_target setting."""
with self.subTest("Success"):
config = {"target": "system",
"exempt_target": " exempt_tgt1 exempt_tgt2 "}
check = AssertRBAC(self.p, "test_exempt_target", config)
# exempt_tgt2 is an attr
expected = set((self.p.lookup_role("exempt_tgt1"),
self.p.lookup_role("exempt_tgt2")))
self.assertIsInstance(check.exempt_target, frozenset)
self.assertSetEqual(expected, check.exempt_target)
with self.subTest("Success, missing role ignored"):
config = {"target": "system",
"exempt_target": "FAIL exempt_tgt2"}
check = AssertRBAC(self.p, "test_target_missing_ignored", config)
# exempt_tgt2 is an attr
expected = set((self.p.lookup_role("exempt_tgt2"),))
self.assertIsInstance(check.exempt_target, frozenset)
self.assertSetEqual(expected, check.exempt_target)
def test_expect_source(self):
"""Test expect_source setting."""
with self.subTest("Success"):
config = {"target": "tgt",
"expect_source": " exempt_src1 exempt_src2 "}
check = AssertRBAC(self.p, "test_expect_source", config)
# exempt_src2 is an attr
expected = set((self.p.lookup_role("exempt_src1"),
self.p.lookup_role("exempt_src2")))
self.assertIsInstance(check.expect_source, frozenset)
self.assertSetEqual(expected, check.expect_source)
with self.subTest("Failure"):
with self.assertRaises(InvalidCheckValue):
config = {"target": "tgt",
"expect_source": " source1 INVALID "}
check = AssertRBAC(self.p, "test_expect_source_fail", config)
def test_expect_target(self):
"""Test expect_target setting."""
with self.subTest("Success"):
config = {"source": "src",
"expect_target": " exempt_tgt1 exempt_tgt2 "}
check = AssertRBAC(self.p, "test_expect_target", config)
# exempt_tgt2 is an attr
expected = set((self.p.lookup_role("exempt_tgt1"),
self.p.lookup_role("exempt_tgt2")))
self.assertIsInstance(check.expect_target, frozenset)
self.assertSetEqual(expected, check.expect_target)
with self.subTest("Failure"):
with self.assertRaises(InvalidCheckValue):
config = {"source": "src",
"expect_target": " target1 INVALID "}
check = AssertRBAC(self.p, "test_expect_target_fail", config)
def test_check_passes(self):
"""Test the check passes, no matches"""
config = {"source": "src",
"target": "tgt"}
check = AssertRBAC(self.p, "test_check_passes", config)
self.assertFalse(check.run())
def test_check_passes_exempt_source_role(self):
"""Test the check passes, exempt_source_role"""
config = {"target": "target1",
"exempt_source": "source1"}
check = AssertRBAC(self.p, "test_check_passes_exempt_source_role", config)
self.assertFalse(check.run())
def test_check_passes_exempt_target_role(self):
"""Test the check passes, exempt_target_role"""
config = {"target": "target2",
"exempt_source": "source2"}
check = AssertRBAC(self.p, "test_check_passes_exempt_target_role", config)
self.assertFalse(check.run())
def test_check_passes_expect_source(self):
"""Test the check passes, expect_source"""
config = {"target": "target3",
"expect_source": "source3a source3b"}
check = AssertRBAC(self.p, "test_check_passes_expect_source", config)
self.assertFalse(check.run())
def test_check_passes_expect_target(self):
"""Test the check passes, expect_target"""
config = {"source": "source4",
"expect_target": "target4a target4b"}
check = AssertRBAC(self.p, "test_check_passes_expect_target", config)
self.assertFalse(check.run())
def test_check_passes_expect_exempt_source(self):
""""Test the check passes with both expected and exempted sources."""
config = {"target": "target5",
"expect_source": "source5a",
"exempt_source": "source5b"}
check = AssertRBAC(self.p, "test_check_passes_expect_exempt_source", config)
self.assertFalse(check.run())
def test_check_passes_expect_exempt_target(self):
""""Test the check passes with both expected and exempted targets."""
config = {"source": "source6",
"expect_target": "target6a",
"exempt_target": "target6b"}
check = AssertRBAC(self.p, "test_check_passes_expect_exempt_target", config)
self.assertFalse(check.run())
def test_check_fails(self):
"""Test the check fails"""
with open("/dev/null", "w") as fd:
config = {"source": "source7",
"expect_target": "target7a",
"exempt_target": "target7b"}
check = AssertRBAC(self.p, "test_check_passes_exempt_target_attr", config)
check.output = fd
result = check.run()
self.assertEqual(1, len(result), msg=result)
rule = result[0]
self.assertEqual(RBACRuletype.allow, rule.ruletype)
self.assertEqual("source7", rule.source)
self.assertEqual("target7c", rule.target)
def test_check_fails_expect_source(self):
"""Test the check fails, expect_source"""
config = {"target": "target8",
"expect_source": "source8"}
check = AssertRBAC(self.p, "test_check_fails_expect_source", config)
result = check.run()
self.assertEqual(1, len(result), msg=result)
self.assertIn("source8", result[0])
def test_check_fails_expect_target(self):
"""Test the check fails, expect_target"""
config = {"source": "source9",
"expect_target": "target9"}
check = AssertRBAC(self.p, "test_check_fails_expect_target", config)
result = check.run()
self.assertEqual(1, len(result), msg=result)
self.assertIn("target9", result[0])