[apparmor] [2/4] Add tests for ChangeProfileRule and ChangeProfileRuleset
Christian Boltz
apparmor at cboltz.de
Sat May 9 20:37:29 UTC 2015
Hello,
this patch adds tests for ChangeProfileRule and ChangeProfileRuleset.
As usual, those classes have 100% test coverage.
[ 02-add-tests-for-ChangeProfileRule.diff ]
=== modified file utils/test/test-change_profile.py
--- utils/test/test-change_profile.py 2015-05-09 22:07:32.450220141 +0200
+++ utils/test/test-change_profile.py 2015-05-09 20:23:18.967205925 +0200
@@ -0,0 +1,443 @@
+#!/usr/bin/env python
+# ----------------------------------------------------------------------
+# Copyright (C) 2015 Christian Boltz <apparmor at cboltz.de>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+#
+# This program 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.
+#
+# ----------------------------------------------------------------------
+
+import unittest
+from collections import namedtuple
+from common_test import AATest, setup_all_loops
+
+from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset
+from apparmor.rule import BaseRule
+from apparmor.common import AppArmorException, AppArmorBug
+from apparmor.logparser import ReadLog
+
+exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment',
+ 'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles'])
+
+# --- tests for single ChangeProfileRule --- #
+
+class ChangeProfileTest(AATest):
+ def _compare_obj(self, obj, expected):
+ self.assertEqual(expected.allow_keyword, obj.allow_keyword)
+ self.assertEqual(expected.audit, obj.audit)
+ self.assertEqual(expected.execcond, obj.execcond)
+ self.assertEqual(expected.targetprofile, obj.targetprofile)
+ self.assertEqual(expected.all_execconds, obj.all_execconds)
+ self.assertEqual(expected.all_targetprofiles, obj.all_targetprofiles)
+ self.assertEqual(expected.deny, obj.deny)
+ self.assertEqual(expected.comment, obj.comment)
+
+class ChangeProfileTestParse(ChangeProfileTest):
+ tests = [
+ # rawrule audit allow deny comment execcond all? targetprof all?
+ ('change_profile,' , exp(False, False, False, '' , None , True , None , True )),
+ ('change_profile /foo,' , exp(False, False, False, '' , '/foo', False, None , True )),
+ ('change_profile /foo -> /bar,' , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
+ ('deny change_profile /foo -> /bar, # comment' , exp(False, False, True , ' # comment' , '/foo', False, '/bar' , False)),
+ ('audit allow change_profile /foo,' , exp(True , True , False, '' , '/foo', False, None , True )),
+ ('change_profile -> /bar,' , exp(False, False, False, '' , None , True , '/bar' , False)),
+ ('audit allow change_profile -> /bar,' , exp(True , True , False, '' , None , True , '/bar' , False)),
+ # quoted versions
+ ('change_profile "/foo",' , exp(False, False, False, '' , '/foo', False, None , True )),
+ ('change_profile "/foo" -> "/bar",' , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
+ ('deny change_profile "/foo" -> "/bar", # cmt' , exp(False, False, True, ' # cmt' , '/foo', False, '/bar' , False)),
+ ('audit allow change_profile "/foo",' , exp(True , True , False, '' , '/foo', False, None , True )),
+ ('change_profile -> "/bar",' , exp(False, False, False, '' , None , True , '/bar' , False)),
+ ('audit allow change_profile -> "/bar",' , exp(True , True , False, '' , None , True , '/bar' , False)),
+ # with globbing and/or named profiles
+ ('change_profile,' , exp(False, False, False, '' , None , True , None , True )),
+ ('change_profile /*,' , exp(False, False, False, '' , '/*' , False, None , True )),
+ ('change_profile /* -> bar,' , exp(False, False, False, '' , '/*' , False, 'bar' , False)),
+ ('deny change_profile /** -> bar, # comment' , exp(False, False, True , ' # comment' , '/**' , False, 'bar' , False)),
+ ('audit allow change_profile /**,' , exp(True , True , False, '' , '/**' , False, None , True )),
+ ('change_profile -> "ba r",' , exp(False, False, False, '' , None , True , 'ba r' , False)),
+ ('audit allow change_profile -> "ba r",' , exp(True , True , False, '' , None , True , 'ba r' , False)),
+ ]
+
+ def _run_test(self, rawrule, expected):
+ self.assertTrue(ChangeProfileRule.match(rawrule))
+ obj = ChangeProfileRule.parse(rawrule)
+ self.assertEqual(rawrule.strip(), obj.raw_rule)
+ self._compare_obj(obj, expected)
+
+class ChangeProfileTestParseInvalid(ChangeProfileTest):
+ tests = [
+ ('change_profile -> ,' , AppArmorException),
+ ('change_profile foo -> ,' , AppArmorException),
+ ]
+
+ def _run_test(self, rawrule, expected):
+ self.assertFalse(ChangeProfileRule.match(rawrule))
+ with self.assertRaises(expected):
+ ChangeProfileRule.parse(rawrule)
+
+class ChangeProfileTestParseFromLog(ChangeProfileTest):
+ def test_net_from_log(self):
+ parser = ReadLog('', '', '', '', '')
+
+ event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"'
+
+ # libapparmor doesn't understand this log format (from JJ)
+ # event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"'
+
+ parsed_event = parser.parse_event(event)
+
+ self.assertEqual(parsed_event, {
+ 'request_mask': None,
+ 'denied_mask': None,
+ 'error_code': 0,
+ #'family': 'inet',
+ 'magic_token': 0,
+ 'parent': 0,
+ 'profile': '/foo/changeprofile',
+ 'operation': 'change_profile',
+ 'resource': None,
+ 'info': None,
+ 'aamode': 'REJECTING',
+ 'time': 1428699242,
+ 'active_hat': None,
+ 'pid': 3459,
+ 'task': 0,
+ 'attr': None,
+ 'name2': '/foo/rename', # target
+ 'name': None,
+ })
+
+ obj = ChangeProfileRule(ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event)
+
+ # audit allow deny comment execcond all? targetprof all?
+ expected = exp(False, False, False, '' , None, True, '/foo/rename', False)
+
+ self._compare_obj(obj, expected)
+
+ self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,')
+
+
+class ChangeProfileFromInit(ChangeProfileTest):
+ tests = [
+ # ChangeProfileRule object audit allow deny comment execcond all? targetprof all?
+ (ChangeProfileRule('/foo', '/bar', deny=True) , exp(False, False, True , '' , '/foo', False, '/bar' , False)),
+ (ChangeProfileRule('/foo', '/bar') , exp(False, False, False, '' , '/foo', False, '/bar' , False)),
+ (ChangeProfileRule('/foo', ChangeProfileRule.ALL) , exp(False, False, False, '' , '/foo', False, None , True )),
+ (ChangeProfileRule(ChangeProfileRule.ALL, '/bar') , exp(False, False, False, '' , None , True , '/bar' , False)),
+ (ChangeProfileRule(ChangeProfileRule.ALL,
+ ChangeProfileRule.ALL) , exp(False, False, False, '' , None , True , None , True )),
+ ]
+
+ def _run_test(self, obj, expected):
+ self._compare_obj(obj, expected)
+
+
+class InvalidChangeProfileInit(AATest):
+ tests = [
+ # init params expected exception
+ (['/foo', '' ] , AppArmorBug), # empty targetprofile
+ (['' , '/bar' ] , AppArmorBug), # empty execcond
+ ([' ', '/bar' ] , AppArmorBug), # whitespace execcond
+ (['/foo', ' ' ] , AppArmorBug), # whitespace targetprofile
+ (['xyxy', '/bar' ] , AppArmorException), # invalid execcond
+ ([dict(), '/bar' ] , AppArmorBug), # wrong type for execcond
+ ([None , '/bar' ] , AppArmorBug), # wrong type for execcond
+ (['/foo', dict() ] , AppArmorBug), # wrong type for targetprofile
+ (['/foo', None ] , AppArmorBug), # wrong type for targetprofile
+ ]
+
+ def _run_test(self, params, expected):
+ with self.assertRaises(expected):
+ ChangeProfileRule(params[0], params[1])
+
+ def test_missing_params_1(self):
+ with self.assertRaises(TypeError):
+ ChangeProfileRule()
+
+ def test_missing_params_2(self):
+ with self.assertRaises(TypeError):
+ ChangeProfileRule('inet')
+
+
+class InvalidChangeProfileTest(AATest):
+ def _check_invalid_rawrule(self, rawrule):
+ obj = None
+ self.assertFalse(ChangeProfileRule.match(rawrule))
+ with self.assertRaises(AppArmorException):
+ obj = ChangeProfileRule(ChangeProfileRule.parse(rawrule))
+
+ self.assertIsNone(obj, 'ChangeProfileRule handed back an object unexpectedly')
+
+ def test_invalid_net_missing_comma(self):
+ self._check_invalid_rawrule('change_profile') # missing comma
+
+ def test_invalid_net_non_ChangeProfileRule(self):
+ self._check_invalid_rawrule('dbus,') # not a change_profile rule
+
+ def test_empty_net_data_1(self):
+ obj = ChangeProfileRule('/foo', '/bar')
+ obj.execcond = ''
+ # no execcond set, and ALL not set
+ with self.assertRaises(AppArmorBug):
+ obj.get_clean(1)
+
+ def test_empty_net_data_2(self):
+ obj = ChangeProfileRule('/foo', '/bar')
+ obj.targetprofile = ''
+ # no targetprofile set, and ALL not set
+ with self.assertRaises(AppArmorBug):
+ obj.get_clean(1)
+
+
+class WriteChangeProfileTestAATest(AATest):
+ tests = [
+ # raw rule clean rule
+ (' change_profile , # foo ' , 'change_profile, # foo'),
+ (' audit change_profile /foo,' , 'audit change_profile /foo,'),
+ (' deny change_profile /foo -> bar,# foo bar' , 'deny change_profile /foo -> bar, # foo bar'),
+ (' deny change_profile /foo ,# foo bar' , 'deny change_profile /foo, # foo bar'),
+ (' allow change_profile -> /bar ,# foo bar' , 'allow change_profile -> /bar, # foo bar'),
+ (' allow change_profile /** -> /bar ,# foo bar' , 'allow change_profile /** -> /bar, # foo bar'),
+ (' allow change_profile "/fo o" -> "/b ar",' , 'allow change_profile "/fo o" -> "/b ar",'),
+ ]
+
+ def _run_test(self, rawrule, expected):
+ self.assertTrue(ChangeProfileRule.match(rawrule))
+ obj = ChangeProfileRule.parse(rawrule)
+ clean = obj.get_clean()
+ raw = obj.get_raw()
+
+ self.assertEqual(expected.strip(), clean, 'unexpected clean rule')
+ self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
+
+ def test_write_manually(self):
+ obj = ChangeProfileRule('/foo', 'bar', allow_keyword=True)
+
+ expected = ' allow change_profile /foo -> bar,'
+
+ self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
+ self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
+
+
+class ChangeProfileCoveredTest(AATest):
+ def _run_test(self, param, expected):
+ obj = ChangeProfileRule.parse(self.rule)
+ check_obj = ChangeProfileRule.parse(param)
+
+ self.assertTrue(ChangeProfileRule.match(param))
+
+ self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0])
+ self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1])
+
+ self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2])
+ self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3])
+
+class ChangeProfileCoveredTest_01(ChangeProfileCoveredTest):
+ rule = 'change_profile /foo,'
+
+ tests = [
+ # rule equal strict equal covered covered exact
+ (' change_profile,' , [ False , False , False , False ]),
+ (' change_profile /foo,' , [ True , True , True , True ]),
+ (' change_profile /foo, # comment', [ True , False , True , True ]),
+ (' allow change_profile /foo,' , [ True , False , True , True ]),
+ (' change_profile /foo,' , [ True , False , True , True ]),
+ (' change_profile /foo -> /bar,' , [ False , False , True , True ]),
+ (' change_profile /foo -> bar,' , [ False , False , True , True ]),
+ ('audit change_profile /foo,' , [ False , False , False , False ]),
+ ('audit change_profile,' , [ False , False , False , False ]),
+ (' change_profile /asdf,' , [ False , False , False , False ]),
+ (' change_profile -> /bar,' , [ False , False , False , False ]),
+ ('audit deny change_profile /foo,' , [ False , False , False , False ]),
+ (' deny change_profile /foo,' , [ False , False , False , False ]),
+ ]
+
+class ChangeProfileCoveredTest_02(ChangeProfileCoveredTest):
+ rule = 'audit change_profile /foo,'
+
+ tests = [
+ # rule equal strict equal covered covered exact
+ ( 'change_profile /foo,' , [ False , False , True , False ]),
+ ('audit change_profile /foo,' , [ True , True , True , True ]),
+ ( 'change_profile /foo -> /bar,' , [ False , False , True , False ]),
+ ('audit change_profile /foo -> /bar,' , [ False , False , True , True ]), # XXX is "covered exact" correct here?
+ ( 'change_profile,' , [ False , False , False , False ]),
+ ('audit change_profile,' , [ False , False , False , False ]),
+ (' change_profile -> /bar,' , [ False , False , False , False ]),
+ ]
+
+
+class ChangeProfileCoveredTest_03(ChangeProfileCoveredTest):
+ rule = 'change_profile /foo -> /bar,'
+
+ tests = [
+ # rule equal strict equal covered covered exact
+ ( 'change_profile /foo -> /bar,' , [ True , True , True , True ]),
+ ('allow change_profile /foo -> /bar,' , [ True , False , True , True ]),
+ ( 'change_profile /foo,' , [ False , False , False , False ]),
+ ( 'change_profile,' , [ False , False , False , False ]),
+ ( 'change_profile /foo -> /xyz,' , [ False , False , False , False ]),
+ ('audit change_profile,' , [ False , False , False , False ]),
+ ('audit change_profile /foo -> /bar,' , [ False , False , False , False ]),
+ ( 'change_profile -> /bar,' , [ False , False , False , False ]),
+ ( 'change_profile,' , [ False , False , False , False ]),
+ ]
+
+class ChangeProfileCoveredTest_04(ChangeProfileCoveredTest):
+ rule = 'change_profile,'
+
+ tests = [
+ # rule equal strict equal covered covered exact
+ ( 'change_profile,' , [ True , True , True , True ]),
+ ('allow change_profile,' , [ True , False , True , True ]),
+ ( 'change_profile /foo,' , [ False , False , True , True ]),
+ ( 'change_profile /xyz -> bar,' , [ False , False , True , True ]),
+ ( 'change_profile -> /bar,' , [ False , False , True , True ]),
+ ( 'change_profile /foo -> /bar,' , [ False , False , True , True ]),
+ ('audit change_profile,' , [ False , False , False , False ]),
+ ('deny change_profile,' , [ False , False , False , False ]),
+ ]
+
+class ChangeProfileCoveredTest_05(ChangeProfileCoveredTest):
+ rule = 'deny change_profile /foo,'
+
+ tests = [
+ # rule equal strict equal covered covered exact
+ ( 'deny change_profile /foo,' , [ True , True , True , True ]),
+ ('audit deny change_profile /foo,' , [ False , False , False , False ]),
+ ( 'change_profile /foo,' , [ False , False , False , False ]), # XXX should covered be true here?
+ ( 'deny change_profile /bar,' , [ False , False , False , False ]),
+ ( 'deny change_profile,' , [ False , False , False , False ]),
+ ]
+
+
+class ChangeProfileCoveredTest_Invalid(AATest):
+ def test_borked_obj_is_covered_1(self):
+ obj = ChangeProfileRule.parse('change_profile /foo,')
+
+ testobj = ChangeProfileRule('/foo', '/bar')
+ testobj.execcond = ''
+
+ with self.assertRaises(AppArmorBug):
+ obj.is_covered(testobj)
+
+ def test_borked_obj_is_covered_2(self):
+ obj = ChangeProfileRule.parse('change_profile /foo,')
+
+ testobj = ChangeProfileRule('/foo', '/bar')
+ testobj.targetprofile = ''
+
+ with self.assertRaises(AppArmorBug):
+ obj.is_covered(testobj)
+
+ def test_invalid_is_covered(self):
+ obj = ChangeProfileRule.parse('change_profile /foo,')
+
+ testobj = BaseRule() # different type
+
+ with self.assertRaises(AppArmorBug):
+ obj.is_covered(testobj)
+
+ def test_invalid_is_equal(self):
+ obj = ChangeProfileRule.parse('change_profile -> /bar,')
+
+ testobj = BaseRule() # different type
+
+ with self.assertRaises(AppArmorBug):
+ obj.is_equal(testobj)
+
+# --- tests for ChangeProfileRuleset --- #
+
+class ChangeProfileRulesTest(AATest):
+ def test_empty_ruleset(self):
+ ruleset = ChangeProfileRuleset()
+ ruleset_2 = ChangeProfileRuleset()
+ self.assertEqual([], ruleset.get_raw(2))
+ self.assertEqual([], ruleset.get_clean(2))
+ self.assertEqual([], ruleset_2.get_raw(2))
+ self.assertEqual([], ruleset_2.get_clean(2))
+
+ def test_ruleset_1(self):
+ ruleset = ChangeProfileRuleset()
+ rules = [
+ 'change_profile -> /bar,',
+ 'change_profile /foo,',
+ ]
+
+ expected_raw = [
+ 'change_profile -> /bar,',
+ 'change_profile /foo,',
+ '',
+ ]
+
+ expected_clean = [
+ 'change_profile -> /bar,',
+ 'change_profile /foo,',
+ '',
+ ]
+
+ for rule in rules:
+ ruleset.add(ChangeProfileRule.parse(rule))
+
+ self.assertEqual(expected_raw, ruleset.get_raw())
+ self.assertEqual(expected_clean, ruleset.get_clean())
+
+ def test_ruleset_2(self):
+ ruleset = ChangeProfileRuleset()
+ rules = [
+ 'change_profile /foo -> /bar,',
+ 'allow change_profile /asdf,',
+ 'deny change_profile -> xy, # example comment',
+ ]
+
+ expected_raw = [
+ ' change_profile /foo -> /bar,',
+ ' allow change_profile /asdf,',
+ ' deny change_profile -> xy, # example comment',
+ '',
+ ]
+
+ expected_clean = [
+ ' deny change_profile -> xy, # example comment',
+ '',
+ ' allow change_profile /asdf,',
+ ' change_profile /foo -> /bar,',
+ '',
+ ]
+
+ for rule in rules:
+ ruleset.add(ChangeProfileRule.parse(rule))
+
+ self.assertEqual(expected_raw, ruleset.get_raw(1))
+ self.assertEqual(expected_clean, ruleset.get_clean(1))
+
+
+class ChangeProfileGlobTestAATest(AATest):
+ def setUp(self):
+ self.ruleset = ChangeProfileRuleset()
+
+ def test_glob_1(self):
+ self.assertEqual(self.ruleset.get_glob('change_profile /foo,'), 'change_profile,')
+
+ # not supported or used yet, glob behaviour not decided yet
+ # def test_glob_2(self):
+ # self.assertEqual(self.ruleset.get_glob('change_profile /foo -> /bar,'), 'change_profile -> /bar,')
+
+ def test_glob_ext(self):
+ with self.assertRaises(AppArmorBug):
+ # get_glob_ext is not available for change_profile rules
+ self.ruleset.get_glob_ext('change_profile /foo -> /bar,')
+
+class ChangeProfileDeleteTestAATest(AATest):
+ pass
+
+setup_all_loops(__name__)
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
Regards,
Christian Boltz
--
Fsck, I'm remembering back to the pre-archived-by-Google era of Usenet;
some local newbie was asking how to compile RM COBOL programs in the
Unix environment, and my anti-COBOL bias might have shown through as I
explained that the RM COBOL compiler on Unix was named "rm". [AdB]
More information about the AppArmor
mailing list