Hello, this patch adds utils/test/test-network.py with tests for NetworkRule and NetworkRuleset.
The tests are hopefully self-explaining, so let me just mention the most important things: - I started to play with namedtuple, which looks very useful (see "exp") - the test loops make the tests much more readable (compare with test-capability.py!) and make it easy to add some more tests - 100% coverage :-) [ 45-add-tests-for-NetworkRule.diff ] === added file 'utils/test/test-network.py' --- utils/test/test-network.py 1970-01-01 00:00:00 +0000 +++ utils/test/test-network.py 2015-04-14 21:19:41 +0000 @@ -0,0 +1,428 @@ +#!/usr/bin/env python +# ---------------------------------------------------------------------- +# Copyright (C) 2015 Christian Boltz <appar...@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 common_test import AATest, setup_all_tests +from collections import namedtuple + +from apparmor.rule.network import NetworkRule, NetworkRuleset +from apparmor.rule import BaseRule, parse_modifiers +from apparmor.common import AppArmorException, AppArmorBug +from apparmor.logparser import ReadLog + +import re + +exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', + 'domain', 'all_domains', 'type_or_protocol', 'all_type_or_protocols']) + +# --- tests for single NetworkRule --- # + +class NetworkTest(AATest): + def _compare_obj(self, obj, expected): + self.assertEqual(expected.allow_keyword, obj.allow_keyword) + self.assertEqual(expected.audit, obj.audit) + self.assertEqual(expected.domain, obj.domain) + self.assertEqual(expected.type_or_protocol, obj.type_or_protocol) + self.assertEqual(expected.all_domains, obj.all_domains) + self.assertEqual(expected.all_type_or_protocols, obj.all_type_or_protocols) + self.assertEqual(expected.deny, obj.deny) + self.assertEqual(expected.comment, obj.comment) + +class NetworkTestParse(NetworkTest): + tests = [ + # rawrule audit allow deny comment domain all? type/proto all? + ('network,' , exp(False, False, False, '' , None , True , None , True )), + ('network inet,' , exp(False, False, False, '' , 'inet', False, None , True )), + ('network inet stream,' , exp(False, False, False, '' , 'inet', False, 'stream' , False)), + ('deny network inet stream, # comment' , exp(False, False, True , ' # comment' , 'inet', False, 'stream' , False)), + ('audit allow network tcp,' , exp(True , True , False, '' , None , True , 'tcp' , False)), + ] + + def _run_test(self, rawrule, expected): + obj = NetworkRule.parse(rawrule) + self.assertEqual(rawrule.strip(), obj.raw_rule) + self._compare_obj(obj, expected) + +class NetworkTestParseInvalid(NetworkTest): + tests = [ + ('network stream,' , AppArmorException), # domain missing + ('network foo,' , AppArmorException), + ('network foo bar,' , AppArmorException), + ('network foo tcp,' , AppArmorException), + ('network inet bar,' , AppArmorException), + ] + + def _run_test(self, rawrule, expected): + with self.assertRaises(expected): + NetworkRule.parse(rawrule) + +class NetworkTestParseFromLog(NetworkTest): + def test_net_from_log(self): + parser = ReadLog('', '', '', '', '') + event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="create" profile="/bin/ping" pid=10589 comm="ping" family="inet" sock_type="raw" protocol=1' + + parsed_event = parser.parse_event(event) + + self.assertEqual(parsed_event, { + 'request_mask': set(), + 'denied_mask': set(), + 'error_code': 0, + 'family': 'inet', + 'magic_token': 0, + 'parent': 0, + 'profile': '/bin/ping', + 'protocol': 'icmp', + 'sock_type': 'raw', + 'operation': 'create', + 'resource': None, + 'info': None, + 'aamode': 'REJECTING', + 'time': 1428699242, + 'active_hat': None, + 'pid': 10589, + 'task': 0, + 'attr': None, + 'name2': None, + 'name': None, + }) + + obj = NetworkRule(parsed_event['family'], parsed_event['sock_type'], log_event=parsed_event) + + # audit allow deny comment domain all? type/proto all? + expected = exp(False, False, False, '' , 'inet', False, 'raw' , False) + + self._compare_obj(obj, expected) + + self.assertEqual(obj.get_raw(1), ' network inet raw,') + + +class NetworkFromInit(NetworkTest): + tests = [ + # NetworkRule object audit allow deny comment domain all? type/proto all? + (NetworkRule('inet', 'raw', deny=True) , exp(False, False, True , '' , 'inet', False, 'raw' , False)), + (NetworkRule('inet', 'raw') , exp(False, False, False, '' , 'inet', False, 'raw' , False)), + (NetworkRule('inet', NetworkRule.ALL) , exp(False, False, False, '' , 'inet', False, None , True )), + (NetworkRule(NetworkRule.ALL, NetworkRule.ALL) , exp(False, False, False, '' , None , True , None , True )), + (NetworkRule(NetworkRule.ALL, 'tcp') , exp(False, False, False, '' , None , True , 'tcp' , False)), + ] + + def _run_test(self, obj, expected): + self._compare_obj(obj, expected) + + +class InvalidNetworkInit(AATest): + tests = [ + # init params expected exception + (['inet', '' ] , AppArmorBug), # empty type_or_protocol + (['' , 'tcp' ] , AppArmorBug), # empty domain + ([' ', 'tcp' ] , AppArmorBug), # whitespace domain + (['inet', ' ' ] , AppArmorBug), # whitespace type_or_protocol + (['xyxy', 'tcp' ] , AppArmorBug), # invalid domain + (['inet', 'xyxy' ] , AppArmorBug), # invalid type_or_protocol + ([dict(), 'tcp' ] , AppArmorBug), # wrong type for domain + (['inet', dict() ] , AppArmorBug), # wrong type for type_or_protocol + ([NetworkRule.ALL, 'stream'] , AppArmorException), # stream requires a domain + ] + + def _run_test(self, params, expected): + with self.assertRaises(expected): + NetworkRule(params[0], params[1]) + + def test_missing_params_1(self): + with self.assertRaises(TypeError): + NetworkRule() + + def test_missing_params_2(self): + with self.assertRaises(TypeError): + NetworkRule('inet') + + +class InvalidNetworkTest(AATest): + def _check_invalid_rawrule(self, rawrule): + obj = None + with self.assertRaises(AppArmorException): + obj = NetworkRule(NetworkRule.parse(rawrule)) + + self.assertIsNone(obj, 'NetworkRule handed back an object unexpectedly') + + def test_invalid_net_missing_comma(self): + self._check_invalid_rawrule('network') # missing comma + + def test_invalid_net_non_NetworkRule(self): + self._check_invalid_rawrule('dbus,') # not a network rule + + def test_parse_modifiers_invalid(self): + regex = re.compile('^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+|invalid\s+)?') + matches = regex.search('audit invalid ') + + with self.assertRaises(AppArmorBug): + parse_modifiers(matches) + + def test_empty_net_data_1(self): + obj = NetworkRule('inet', 'stream') + obj.domain = '' + # no domain set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + def test_empty_net_data_2(self): + obj = NetworkRule('inet', 'stream') + obj.type_or_protocol = '' + # no type_or_protocol set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + +class WriteNetworkTestAATest(AATest): + def _run_test(self, rawrule, expected): + obj = NetworkRule.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') + + tests = [ + # raw rule clean rule + (' network , # foo ' , 'network, # foo'), + (' audit network inet,' , 'audit network inet,'), + (' deny network inet stream,# foo bar' , 'deny network inet stream, # foo bar'), + (' deny network inet ,# foo bar' , 'deny network inet, # foo bar'), + (' allow network tcp ,# foo bar' , 'allow network tcp, # foo bar'), + ] + + def test_write_manually(self): + obj = NetworkRule('inet', 'stream', allow_keyword=True) + + expected = ' allow network inet stream,' + + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + + +class NetworkCoveredTest(AATest): + def _run_test(self, param, expected): + obj = NetworkRule.parse(self.rule) + check_obj = NetworkRule.parse(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 NetworkCoveredTest_01(NetworkCoveredTest): + rule = 'network inet,' + + tests = [ + # rule equal strict equal covered covered exact + ('network,' , [ False , False , False , False ]), + ('network inet,' , [ True , True , True , True ]), + ('network inet, # comment' , [ True , False , True , True ]), + ('allow network inet,' , [ True , False , True , True ]), + ('network inet,' , [ True , False , True , True ]), + ('network inet stream,' , [ False , False , True , True ]), + ('network inet tcp,' , [ False , False , True , True ]), + ('audit network inet,' , [ False , False , False , False ]), + ('audit network,' , [ False , False , False , False ]), + ('network unix,' , [ False , False , False , False ]), + ('network tcp,' , [ False , False , False , False ]), + ('audit deny network inet,' , [ False , False , False , False ]), + ('deny network inet,' , [ False , False , False , False ]), + ] + +class NetworkCoveredTest_02(NetworkCoveredTest): + rule = 'audit network inet,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'network inet,' , [ False , False , True , False ]), + ('audit network inet,' , [ True , True , True , True ]), + ( 'network inet stream,' , [ False , False , True , False ]), + ('audit network inet stream,' , [ False , False , True , True ]), + ( 'network,' , [ False , False , False , False ]), + ('audit network,' , [ False , False , False , False ]), + ('network unix,' , [ False , False , False , False ]), + ] + + +class NetworkCoveredTest_03(NetworkCoveredTest): + rule = 'network inet stream,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'network inet stream,' , [ True , True , True , True ]), + ('allow network inet stream,' , [ True , False , True , True ]), + ( 'network inet,' , [ False , False , False , False ]), + ( 'network,' , [ False , False , False , False ]), + ( 'network inet tcp,' , [ False , False , False , False ]), + ('audit network,' , [ False , False , False , False ]), + ('audit network inet stream,' , [ False , False , False , False ]), + ( 'network unix,' , [ False , False , False , False ]), + ( 'network,' , [ False , False , False , False ]), + ] + +class NetworkCoveredTest_04(NetworkCoveredTest): + rule = 'network,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'network,' , [ True , True , True , True ]), + ('allow network,' , [ True , False , True , True ]), + ( 'network inet,' , [ False , False , True , True ]), + ( 'network inet6 stream,' , [ False , False , True , True ]), + ( 'network tcp,' , [ False , False , True , True ]), + ( 'network inet raw,' , [ False , False , True , True ]), + ('audit network,' , [ False , False , False , False ]), + ('deny network,' , [ False , False , False , False ]), + ] + +class NetworkCoveredTest_05(NetworkCoveredTest): + rule = 'deny network inet,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'deny network inet,' , [ True , True , True , True ]), + ('audit deny network inet,' , [ False , False , False , False ]), + ( 'network inet,' , [ False , False , False , False ]), # XXX should covered be true here? + ( 'deny network unix,' , [ False , False , False , False ]), + ( 'deny network,' , [ False , False , False , False ]), + ] + + +class NetworkCoveredTest_Invalid(AATest): + def test_borked_obj_is_covered_1(self): + obj = NetworkRule.parse('network inet,') + + testobj = NetworkRule('inet', 'stream') + testobj.domain = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_borked_obj_is_covered_2(self): + obj = NetworkRule.parse('network inet,') + + testobj = NetworkRule('inet', 'stream') + testobj.type_or_protocol = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_covered(self): + obj = NetworkRule.parse('network inet,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_equal(self): + obj = NetworkRule.parse('network inet,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_equal(testobj) + +## --- tests for NetworkRuleset --- # + +class NetworkRulesTest(AATest): + def test_empty_ruleset(self): + ruleset = NetworkRuleset() + ruleset_2 = NetworkRuleset() + 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 = NetworkRuleset() + rules = [ + 'network tcp,', + 'network inet,', + ] + + expected_raw = [ + 'network tcp,', + 'network inet,', + '', + ] + + expected_clean = [ + 'network inet,', + 'network tcp,', + '', + ] + + for rule in rules: + ruleset.add(NetworkRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw()) + self.assertEqual(expected_clean, ruleset.get_clean()) + + def test_ruleset_2(self): + ruleset = NetworkRuleset() + rules = [ + 'network inet6 raw,', + 'allow network inet,', + 'deny network udp, # example comment', + ] + + expected_raw = [ + ' network inet6 raw,', + ' allow network inet,', + ' deny network udp, # example comment', + '', + ] + + expected_clean = [ + ' deny network udp, # example comment', + '', + ' allow network inet,', + ' network inet6 raw,', + '', + ] + + for rule in rules: + ruleset.add(NetworkRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw(1)) + self.assertEqual(expected_clean, ruleset.get_clean(1)) + + +class NetworkGlobTestAATest(AATest): + def setUp(self): + self.maxDiff = None + self.ruleset = NetworkRuleset() + + def test_glob_1(self): + self.assertEqual(self.ruleset.get_glob('network inet,'), 'network,') + + # not supported or used yet + # def test_glob_2(self): + # self.assertEqual(self.ruleset.get_glob('network inet raw,'), 'network inet,') + + def test_glob_ext(self): + with self.assertRaises(AppArmorBug): + # get_glob_ext is not available for network rules + self.ruleset.get_glob_ext('network inet raw,') + +class NetworkDeleteTestAATest(AATest): + pass + +if __name__ == '__main__': + setup_all_tests() + unittest.main(verbosity=2) Regards, Christian Boltz -- Das ist halt der Unterschied: Unix ist ein Betriebssystem mit Tradition, die anderen sind einfach von sich aus unlogisch. [Anselm Lingnau] -- AppArmor mailing list AppArmor@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor