Hello,

This patch adds four classes - two "base" classes and two specific for 
capabilities:

utils/apparmor/rule/__init__.py:

    class base_rule(object):
        Base class to handle and store a single rule

    class base_rules(object):
        Base class to handle and store a collection of rules

utils/apparmor/rule/capability.py:

    class capability_rule(base_rule):
        Class to handle and store a single capability rule

    class capability_rules(base_rules):
        Class to handle and store a collection of capability rules


Most of the code is in the base classes (in __init__.py) - that's code 
that will be reused by each rule class.

capability.py contains only code that is specific to capabilities.


The layout of the base_rules and capability_rules class is not too
different from the proposal I sent some weeks ago. 

The biggest difference is that the class doesn't store the full profile
and include list. When I tried to do this, aa-logprof started to eat 
*lots of* memory before I Ctrl-C'd it in a deepcopy() operation.
Either we find a way to implement this in a sane way that doesn't eat
memory, or we just keep it out ;-)

I didn't implement the functions to propose rules yet - that's something 
for the second round. The interesting question is if we can do this 
inside the class without having the list of include files inside the 
class.


The storage for single rules is more different - initially I planned to
use a dict at least for the simple rule types like capability, but it
turned out that using a class has some advantages.



Here's the list of functions in each class:

utils/apparmor/rule/__init__.py:

class base_rule(object):
    '''Base class to handle and store a single rule'''

    def get_raw(self, depth):
        '''return raw rule (with original formatting, and leading whitespace in 
the depth parameter)'''

    def audit_allow_str(self):
        '''return the allow/deny and audit keyword as string, including 
whitespace'''

    def parse_audit_allow(self, matches):
        '''returns audit, deny, allow_keyword and comment from the matches 
object

class base_rules(object):
    '''Base class to handle and store a collection of rules'''

    def __init__(self):
        self.delete_all_rules()

    def delete_all_rules(self):
        self.rules = []

    def add_raw(self, rawrule):
        '''parse rawrule (from profile file) and store it in a structured way'''

    def get_raw(self, depth):
        '''return all raw rules (if possible/not modified in their original 
formatting).

    def get_clean(self, depth):
        '''return all rules (in clean/default formatting)

    def covered_obj(self, rule_obj, check_allow_deny = True, check_audit = 
False):
        '''return True if rule_obj is covered by existing rules, otherwise 
False'''

    def covered_log(self, parsed_log_event, check_allow_deny = True, 
check_audit = False):
        '''return True if parsed_log_event is covered by existing rules, 
otherwise False'''

    def covered_raw(self, rawrule, check_allow_deny = True, check_audit = 
False):
        '''return True if rawrule is covered by existing rules, otherwise 
False'''

    def delete_obj(self, rule_obj):
        '''Delete rule_obj from rules'''

    def delete_raw(self, rawrule):
        '''Delete rawrule from rules'''

    def delete_duplicates(self, inccaps):
        '''Delete duplicate rules.

    def get_glob_ext(self, path_or_rule):
        '''returns the next possible glob with extension (for file rules only).


utils/apparmor/rule/capability.py:

class capability_rule(base_rule):
    '''Class to handle and store a single capability rule'''

    def __init__(self):
        self.capability = []

    def get_clean(self, depth):
        '''return rule (in clean/default formatting)'''

    def set_raw(self, rawrule):
        '''parse and store rawrule'''

    def set_log(self, parsed_log_event):
        '''parse and store log event'''

    def covered(self, rule_obj, check_allow_deny = True, check_audit = False):
        '''check if rule_obj is covered by this rule object'''

class capability_rules(base_rules):
    '''Class to handle and store a collection of capability rules'''

    def new_rule(self):
        '''tiny helper function that allows to keep several functions to parent 
class'''

    def get_glob(self, path_or_rule):
        '''Return the next possible glob. For capability rules, that's always 
"capability," (all capabilities)'''


Ah, I like it when grep writes my mails ;-)   [1]


Regards,

Christian Boltz

[1] grep -h -A1 '^class\|^ *def' __init__.py capability.py

-- 
Alt ist man, wenn man sich links neben der Leertaste befindet.
[http://twitter.com/nichtstefanraab/status/2799731380]
=== added directory 'utils/apparmor/rule'
=== added file 'utils/apparmor/rule/__init__.py'
--- utils/apparmor/rule/__init__.py	1970-01-01 00:00:00 +0000
+++ utils/apparmor/rule/__init__.py	2014-11-15 21:51:26 +0000
@@ -0,0 +1,217 @@
+# ----------------------------------------------------------------------
+#    Copyright (C) 2013 Kshitij Gupta <[email protected]>
+#    Copyright (C) 2014 Christian Boltz <[email protected]>
+#
+#    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.
+#
+# ----------------------------------------------------------------------
+
+from apparmor.common import AppArmorBug
+
+# setup module translations
+from apparmor.translations import init_translation
+_ = init_translation()
+
+# The key for representing bare rules such as "capability," or "file,"
+ALL = '\0ALL'
+
+
+class base_rule(object):
+    '''Base class to handle and store a single rule'''
+
+    audit = False
+    deny = False
+    allow_keyword = False
+
+    inlinecomment = ''
+
+    rawrule = ''
+    logmode = ''  # only set by set_log()
+
+    def get_raw(self, depth):
+        '''return raw rule (with original formatting, and leading whitespace in the depth parameter)'''
+        if self.rawrule:
+            return '%s%s' % ('  '*depth, self.rawrule)
+        else:
+            return self.get_clean(depth)
+
+    def audit_allow_str(self):
+        '''return the allow/deny and audit keyword as string, including whitespace'''
+
+        if self.audit:
+            auditstr = 'audit '
+        else:
+            auditstr = ''
+
+        if self.deny:
+            allowstr = 'deny '
+        elif self.allow_keyword:
+            allowstr = 'allow '
+        else:
+            allowstr = ''
+
+        return '%s%s' % (auditstr, allowstr)
+
+    def parse_audit_allow(self, matches):
+        '''returns audit, deny, allow_keyword and comment from the matches object
+           - audit, deny and allow_keyword are True/False
+           - comment is the comment with a leading space'''
+        audit = False
+        if matches.group('audit'):
+            audit = True
+
+        deny = False
+        allow_keyword = False
+
+        allowstr = matches.group('allow')
+        if allowstr:
+            if allowstr.strip() == 'allow':
+                allow_keyword = True
+            elif allowstr.strip() == 'deny':
+                deny = True
+            else:
+                raise AppArmorBug("Invalid allow/deny keyword %s" % allowstr)
+
+        comment = ''
+        if matches.group('comment'):
+            # include a space so that we don't need to add it everywhere when writing the rule
+            comment = ' %s' % matches.group('comment')
+
+        return (audit, deny, allow_keyword, comment)
+
+
+class base_rules(object):
+    '''Base class to handle and store a collection of rules'''
+
+    rules = []
+
+    # decides if the (G)lob and Glob w/ (E)xt options are displayed
+    can_glob = True
+    can_glob_ext = False
+
+    def __init__(self):
+        self.delete_all_rules()
+
+    def delete_all_rules(self):
+        self.rules = []
+
+    def add_raw(self, rawrule):
+        '''parse rawrule (from profile file) and store it in a structured way'''
+
+        newrule = self.new_rule()
+        newrule.set_raw(rawrule)
+        self.rules.append(newrule)
+
+    def get_raw(self, depth):
+        '''return all raw rules (if possible/not modified in their original formatting).
+           Returns an array of lines, with depth * leading whitespace'''
+
+        data = []
+        for rule in self.rules:
+            data.append(rule.get_raw(depth))
+
+        if data:
+            data.append('')
+
+        return data
+
+    def get_clean(self, depth):
+        '''return all rules (in clean/default formatting)
+           Returns an array of lines, with depth * leading whitespace'''
+
+        data = { 'allow': [], 'deny': [] }
+        for rule in self.rules:
+            if rule.deny:
+                data['deny'].append(rule.get_clean(depth))
+            else:
+                data['allow'].append(rule.get_clean(depth))
+
+        data['allow'].sort()
+        data['deny'].sort()
+
+        cleandata = []
+
+        if data['deny']:
+            cleandata += data['deny']
+            cleandata.append('')
+
+        if data['allow']:
+            cleandata += data['allow']
+            cleandata.append('')
+
+        return cleandata
+
+    def covered_obj(self, rule_obj, check_allow_deny = True, check_audit = False):
+        '''return True if rule_obj is covered by existing rules, otherwise False'''
+
+        for rule in self.rules:
+            if rule.covered(rule_obj, check_allow_deny, check_audit):
+                return True
+
+        return False
+
+    def covered_log(self, parsed_log_event, check_allow_deny = True, check_audit = False):
+        '''return True if parsed_log_event is covered by existing rules, otherwise False'''
+
+        rule_obj = self.new_rule()
+        rule_obj.set_log(parsed_log_event)
+
+        return self.covered_obj(rule_obj, check_allow_deny, check_audit)
+
+    def covered_raw(self, rawrule, check_allow_deny = True, check_audit = False):
+        '''return True if rawrule is covered by existing rules, otherwise False'''
+
+        rule_obj = self.new_rule()
+        rule_obj.set_raw(rawrule)
+
+        return self.covered_obj(rule_obj, check_allow_deny, check_audit)
+
+    def delete_obj(self, rule_obj):
+        '''Delete rule_obj from rules'''
+
+        rule_to_delete = None
+        i = 0
+        for rule in self.rules:
+            i = i + 1
+            if rule.covered(rule_obj, True, True):
+                rule_to_delete = i
+                break
+
+        if rule_to_delete:
+            self.rules.pop(i-1)
+        else:
+            raise AppArmorBug('Attemp to delete non-existing rule %s' % rule_obj.get_raw(0))
+
+    def delete_raw(self, rawrule):
+        '''Delete rawrule from rules'''
+
+        rule_obj = self.new_rule()
+        rule_obj.set_raw(rawrule)
+
+        return self.delete_obj(rule_obj)
+
+    def delete_duplicates(self, inccaps):
+        '''Delete duplicate rules.
+           inccaps must be a *_rules object'''
+        deleted = []
+        if inccaps:  # avoid breakage until we have a propoer function to ensure all profiles contain all *_rules objects
+            for cap_obj in self.rules:
+                if inccaps.covered_obj(cap_obj, True, True):
+                    self.delete_obj(cap_obj)
+                    deleted.append(cap_obj)
+
+        return len(deleted)
+
+    def get_glob_ext(self, path_or_rule):
+        '''returns the next possible glob with extension (for file rules only).
+           For all other rule types, raise an exception'''
+        raise AppArmorBug("get_glob_ext is not available for this rule type!")
+
+

=== added file 'utils/apparmor/rule/capability.py'
--- utils/apparmor/rule/capability.py	1970-01-01 00:00:00 +0000
+++ utils/apparmor/rule/capability.py	2014-11-15 22:01:34 +0000
@@ -0,0 +1,112 @@
+# ----------------------------------------------------------------------
+#    Copyright (C) 2013 Kshitij Gupta <[email protected]>
+#    Copyright (C) 2014 Christian Boltz <[email protected]>
+#
+#    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.
+#
+# ----------------------------------------------------------------------
+
+from apparmor.regex import RE_PROFILE_CAP
+from apparmor.common import AppArmorBug, AppArmorException
+from apparmor.rule import base_rule, base_rules, ALL
+import re
+
+# setup module translations
+from apparmor.translations import init_translation
+_ = init_translation()
+
+
+class capability_rule(base_rule):
+    '''Class to handle and store a single capability rule'''
+
+    capability = []
+
+    def __init__(self):
+        self.capability = []
+
+    def get_clean(self, depth):
+        '''return rule (in clean/default formatting)'''
+
+        space = '  '*depth
+        if ALL in self.capability:
+            # automatically cleanup if self.capability contains 'ALL'
+            return('%s%scapability,%s' % (space, self.audit_allow_str(), self.inlinecomment))
+        else:
+            allcaps = ' '.join(self.capability).strip()
+            if allcaps:
+                return('%s%scapability %s,%s' % (space, self.audit_allow_str(), ' '.join(sorted(self.capability)), self.inlinecomment))
+            else:
+                raise AppArmorBug("Empty capability rule")
+
+    def set_raw(self, rawrule):
+        '''parse and store rawrule'''
+
+        matches = RE_PROFILE_CAP.search(rawrule)
+        if not matches:
+            raise AppArmorException(_("Invalid capability rule '%s'") % rawrule)
+
+        self.rawrule = rawrule.strip()
+
+        self.audit, self.deny, self.allow_keyword, self.inlinecomment = self.parse_audit_allow(matches)
+
+        if matches.group('capability'):
+            capability = matches.group('capability').strip()
+            self.capability = re.split("[ \t]+", capability)
+        else:
+            self.capability = [ ALL ]
+
+    def set_log(self, parsed_log_event):
+        '''parse and store log event'''
+
+        if parsed_log_event['operation'] != 'capable':
+            raise AppArmorBug('Invalid capability log event - not a capability event')
+
+        if not parsed_log_event['name']:
+            raise AppArmorBug('Invalid capability log event - name is empty')
+
+        self.capability = [ parsed_log_event['name'] ]
+
+        self.logmode = parsed_log_event['aamode']
+
+    def covered(self, rule_obj, check_allow_deny = True, check_audit = False):
+        '''check if rule_obj is covered by this rule object'''
+
+        if check_allow_deny and self.deny != rule_obj.deny:
+            return False
+
+        if not rule_obj.capability:
+            raise AppArmorBug('No capability specified')
+
+        if ALL not in self.capability:
+            for cap in rule_obj.capability:
+                if not cap in self.capability:
+                    return False
+
+        if check_audit and rule_obj.audit != self.audit:
+            return False
+
+        if rule_obj.audit and not self.audit:
+            return False
+
+        # still here? -> then it is covered
+        return True
+
+
+class capability_rules(base_rules):
+    '''Class to handle and store a collection of capability rules'''
+
+    def new_rule(self):
+        '''tiny helper function that allows to keep several functions to parent class'''
+        return capability_rule()
+
+    def get_glob(self, path_or_rule):
+        '''Return the next possible glob. For capability rules, that's always "capability," (all capabilities)'''
+        return 'capability,'
+

-- 
AppArmor mailing list
[email protected]
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to