[apparmor] [patch 1/3] Add NetworkRule and NetworkRuleset classes

Christian Boltz apparmor at cboltz.de
Tue Apr 14 21:37:30 UTC 2015


Hello,

this patch adds utils/apparmor/rule/network.py with the NetworkRule and
NetworkRuleset classes. These classes are meant to handle network rules.

In comparison to the existing code in aa.py, relevant news are:
- the keywords are checked against a list of allowed domains, types and
  protocols (these lists are based on what the utils/vim/Makefile
  generates - on the long term an autogenerated file with the keywords
  for all rule types would be nice ;-)
- there are variables for domain and type_or_protocol instead of
  first_param and second_param. (If someone is bored enough to map the
  protocol "shortcuts" to their expanded meaning, that shouldn't be too
  hard.)
- (obviously) more readable code because we have everything at one place
  now
- some bugs are fixed along the way (for example, "network foo" will now
  be kept, not "network foo bar" - see my last mail about
  write_net_rules() for details)



[ 44-add-NetworkRule-and-NetworkRuleset-classes.diff ]

=== added file 'utils/apparmor/rule/network.py'
--- utils/apparmor/rule/network.py      1970-01-01 00:00:00 +0000
+++ utils/apparmor/rule/network.py      2015-04-14 20:47:40 +0000
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# ----------------------------------------------------------------------
+#    Copyright (C) 2013 Kshitij Gupta <kgupta8592 at gmail.com>
+#    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 re
+
+from apparmor.regex import RE_PROFILE_NETWORK
+from apparmor.common import AppArmorBug, AppArmorException
+from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers
+
+# setup module translations
+from apparmor.translations import init_translation
+_ = init_translation()
+
+
+network_domain_keywords   = [ 'unix', 'inet', 'ax25', 'ipx', 'appletalk', 'netrom', 'bridge', 'atmpvc', 'x25', 'inet6',
+                              'rose', 'netbeui', 'security', 'key', 'netlink', 'packet', 'ash', 'econet', 'atmsvc', 'rds', 'sna',
+                              'irda', 'pppox', 'wanpipe', 'llc', 'can', 'tipc', 'bluetooth', 'iucv', 'rxrpc', 'isdn', 'phonet',
+                              'ieee802154', 'caif', 'alg', 'nfc', 'vsock' ]
+    # missing in manpage:     'unix', 'rds', 'llc', 'can', 'tipc', 'iucv', 'rxrpc', 'isdn', 'phonet', 'ieee802154', 'caif', 'alg', 'nfc', 'vsock'
+
+network_type_keywords     = ['stream', 'dgram', 'seqpacket', 'rdm', 'raw', 'packet']
+network_protocol_keywords = ['tcp', 'udp', 'icmp']
+
+
+RE_NETWORK_DOMAIN   = '(' + '|'.join(network_domain_keywords) + ')'
+RE_NETWORK_TYPE     = '(' + '|'.join(network_type_keywords) + ')'
+RE_NETWORK_PROTOCOL = '(' + '|'.join(network_protocol_keywords) + ')'
+
+RE_NETWORK_DETAILS  = re.compile(
+    '^\s*(' +
+        '(?P<domain>' + RE_NETWORK_DOMAIN + ')(\s+(?P<type_or_protocol>' + RE_NETWORK_TYPE + '|' + RE_NETWORK_PROTOCOL + '))?' + # domain, optional type or protocol
+        '|' + # or
+        '(?P<protocol>' + RE_NETWORK_PROTOCOL + ')' + # protocol only
+    ')\s*$')
+
+
+
+
+
+
+
+class NetworkRule(BaseRule):
+    '''Class to handle and store a single network rule'''
+
+    # Nothing external should reference this class, all external users
+    # should reference the class field NetworkRule.ALL
+    class __NetworkAll(object):
+        pass
+
+    ALL = __NetworkAll
+
+    def __init__(self, domain, type_or_protocol, audit=False, deny=False, allow_keyword=False,
+                 comment='', log_event=None):
+
+        '''
+           NETWORK RULE = 'network' [ [ DOMAIN [ TYPE | PROTOCOL ] ] | [ PROTOCOL ] ] ','
+        '''
+
+        super(NetworkRule, self).__init__(audit=audit, deny=deny,
+                                             allow_keyword=allow_keyword,
+                                             comment=comment,
+                                             log_event=log_event)
+
+        self.domain = None
+        self.all_domains = False
+        if domain == NetworkRule.ALL:
+            self.all_domains = True
+        elif type(domain) == str:
+            if domain in network_domain_keywords:
+                self.domain = domain
+            else:
+                raise AppArmorBug('Passed unknown domain to NetworkRule: %s' % str(domain))
+        else:
+            raise AppArmorBug('Passed unknown object to NetworkRule: %s' % str(domain))
+
+        self.type_or_protocol = None
+        self.all_type_or_protocols = False
+        if type_or_protocol == NetworkRule.ALL:
+            self.all_type_or_protocols = True
+        elif type(type_or_protocol) == str:
+            if type_or_protocol in network_protocol_keywords:
+                self.type_or_protocol = type_or_protocol
+            elif type_or_protocol in network_type_keywords:
+                if self.all_domains:
+                    raise AppArmorException('Passing type %s to NetworkRule without specifying a domain keyword is not allowed' % str(type_or_protocol))
+                self.type_or_protocol = type_or_protocol
+            else:
+                raise AppArmorBug('Passed unknown type_or_protocol to NetworkRule: %s' % str(type_or_protocol))
+        else:
+            raise AppArmorBug('Passed unknown object to NetworkRule: %s' % str(type_or_protocol))
+
+    @classmethod
+    def _parse(cls, raw_rule):
+        '''parse raw_rule and return NetworkRule'''
+
+        matches = RE_PROFILE_NETWORK.search(raw_rule)
+        if not matches:
+            raise AppArmorException(_("Invalid network rule '%s'") % raw_rule)
+
+        audit, deny, allow_keyword, comment = parse_modifiers(matches)
+
+        rule_details = ''
+        if matches.group('details'):
+            rule_details = matches.group('details').strip()
+
+        if rule_details:
+            details = RE_NETWORK_DETAILS.search(rule_details)
+            if not details:
+                raise AppArmorException(_("Invalid or unknown keywords in 'network %s" % rule_details))
+
+            if details.group('domain'):
+                domain = details.group('domain')
+            else:
+                domain = NetworkRule.ALL
+
+            if details.group('type_or_protocol'):
+                type_or_protocol = details.group('type_or_protocol')
+            elif details.group('protocol'):
+                type_or_protocol = details.group('protocol')
+            else:
+                type_or_protocol = NetworkRule.ALL
+        else:
+            domain = NetworkRule.ALL
+            type_or_protocol = NetworkRule.ALL
+
+        return NetworkRule(domain, type_or_protocol,
+                           audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
+
+    def get_clean(self, depth=0):
+        '''return rule (in clean/default formatting)'''
+
+        space = '  ' * depth
+
+        if self.all_domains:
+            domain = ''
+        elif self.domain:
+            domain = ' %s' % self.domain
+        else:
+            raise AppArmorBug('Empty domain in network rule')
+
+        if self.all_type_or_protocols:
+            type_or_protocol = ''
+        elif self.type_or_protocol:
+            type_or_protocol = ' %s' % self.type_or_protocol
+        else:
+            raise AppArmorBug('Empty type or protocol in network rule')
+
+        return('%s%snetwork%s%s,%s' % (space, self.modifiers_str(), domain, type_or_protocol, self.comment))
+
+    def is_covered_localvars(self, other_rule):
+        '''check if other_rule is covered by this rule object'''
+
+        if not other_rule.domain and not other_rule.all_domains:
+            raise AppArmorBug('No domain specified in other network rule')
+
+        if not other_rule.type_or_protocol and not other_rule.all_type_or_protocols:
+            raise AppArmorBug('No type or protocol specified in other network rule')
+
+        if not self.all_domains:
+            if other_rule.all_domains:
+                return False
+            if other_rule.domain != self.domain:
+                return False
+
+        if not self.all_type_or_protocols:
+            if other_rule.all_type_or_protocols:
+                return False
+            if other_rule.type_or_protocol != self.type_or_protocol:
+                return False
+
+        # still here? -> then it is covered
+        return True
+
+    def is_equal_localvars(self, rule_obj):
+        '''compare if rule-specific variables are equal'''
+
+        if not type(rule_obj) == NetworkRule:
+            raise AppArmorBug('Passes non-network rule: %s' % str(rule_obj))
+
+        if (self.domain != rule_obj.domain
+                or self.all_domains != rule_obj.all_domains):
+            return False
+
+        if (self.type_or_protocol != rule_obj.type_or_protocol
+                or self.all_type_or_protocols != rule_obj.all_type_or_protocols):
+            return False
+
+        return True
+
+
+class NetworkRuleset(BaseRuleset):
+    '''Class to handle and store a collection of network rules'''
+
+    def get_glob(self, path_or_rule):
+        '''Return the next possible glob. For network rules, that's "network DOMAIN," or "network," (all network)'''
+        # XXX return 'network DOMAIN,'
+        return 'network,'



Regards,

Christian Boltz
-- 
We break the translation consistently (wow, consistent break, I like
that wording) [from https://bugzilla.novell.com/show_bug.cgi?id=165509]




More information about the AppArmor mailing list