Hello,

On Wed, Apr 15, 2015 at 3:07 AM, Christian Boltz <appar...@cboltz.de> wrote:

> 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 ;-)
>
In theory you can have a script generate a python module that solely
contains these keywords, which can be imported in here. Sounds pretty
trivial ;-)


> - 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
>
yay!


> - 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 <kgupta8...@gmail.com>
> +#    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 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'
> +
>
comments from others on the missing types?

+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) + ')'
>
neat!


> +
> +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
>
exercising new/larger monitor privileges?  ;-)
can be split into two lines I think.

+        '|' + # or
> +        '(?P<protocol>' + RE_NETWORK_PROTOCOL + ')' + # protocol only
> +    ')\s*$')
>
The leading and trailing \s* become superfluous as __parse has:
rule_details = matches.group('details').strip()

+

+
> +
> +
> +
> +
> +
>
what is the standard for whitespace separation? 7 seems a bit much.

+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))
>
str(domain) is unnecessary, as type of domain is known to be str.


> +        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))
>

str(type_or_protocol) not required.

+                self.type_or_protocol = type_or_protocol
> +            else:
> +                raise AppArmorBug('Passed unknown type_or_protocol to
> NetworkRule: %s' % str(type_or_protocol))
>
str(type_or_protocol) not required. #copy paste reviews

+        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))
>
Passes or Passed?

+
> +        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,'
>
>
>
> Thanks.

Regards,

Kshitij Gupta

> 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]
>
>
> --
> AppArmor mailing list
> AppArmor@lists.ubuntu.com
> Modify settings or unsubscribe at:
> https://lists.ubuntu.com/mailman/listinfo/apparmor
>
-- 
AppArmor mailing list
AppArmor@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to