[apparmor] [patch 1/3] add base and capability rule classes

Christian Boltz apparmor at cboltz.de
Wed Nov 19 00:40:30 UTC 2014


Hello,

Am Dienstag, 18. November 2014 schrieb Steve Beattie:
> Thanks for starting this work. I haven't started looking at the code,
> but wanted to ask questions and comment about the proposed classes.
> 
> On Sat, Nov 15, 2014 at 11:43:51PM +0100, Christian Boltz wrote:
...
> > 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.
> 
> That all seems sensible.

:-)

> > 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 ;-)
> 
> Do you have a code snippet of how you went about this? Keeping
> references to things should be relatively lightweight. Perhaps it's
> an artifact of the specific implementation of the global hasher
> thingy.

Something like that (IIRC):

class capability_rules(base_rules):
    fullprofile = None
    includes = None
    def __init__(self, fullprofile, includes):
        self.fullprofile = fullprofile
        self.includes = includes

then use
    foo = capability_rules(aa[profile][hat], include)

That said - keeping the includes outside isn't as bad as I thought, so 
don't worry about that too much ;-)

The only "problem" with this way is that we need to keep the functions 
that check includes for covered rules outside - see aa.py 
profile_known_capability() and match_cap_include().

BTW: my long-term plan is to have profile_known_rule() and 
match_include() functions that work for all rule types.

> > 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'''
> 
> Are the fields that you expect to store in each rule? 'audit' and
> 'deny' seem like obvious candidates.

Yes, audit and deny (and some more) are defined and stored in base_rule.

...
> >     def __init__(self):
> >         self.delete_all_rules()
> >     
> >     def delete_all_rules(self):
> >         self.rules = []
> 
> Why is delete_all_rules() necessary? __init__() should probably just
> do the initialization of self.rules directly, and we really shouldn't
> be re-using an object to encapsulate a different set of rules.

I'm using it from aa.py when writing the profile in non-clean mode, and 
that code needs to clear the rules it already wrote. I could also use
    aa[profile][hat]['capability'] = capability_rules()
there. However, using
    aa[profile][hat]['capability'].delete_all_rules()
has a big advantage: We can replace 'capability' with a variable and 
easily loop over all rule types. You can't do that with 
    ... = capability_rules() ;-)

That said - the code to write a profile in non-clean mode (aka "(V)iew 
changes") is <diplomatic>not really nice</diplomatic>, but that's 
another rewrite ;-)

> And realistically, if you just declare 'rules = []' as a field in the
> class, then neither function is necessary (though it wouldn't surprise
> me if we needed __init__() for other reasons).

__init() is needed - try running the testsuite without it and you'll 
know the reason ;-)

(and yes, I was also surprised that I explicitely need self.rules = [] 
in __init__() - what python does without the __init__() isn't what I'd 
expect)

> >     def add_raw(self, rawrule):
> >         '''parse rawrule (from profile file) and store it in a
> >         structured way'''
> Is rawrule a string or a complex data structure that contains
> back-reference information to the file/line it came from?

rawrule is a string - basically a line from a profile (for example 
    capability      chown    ,      #  I       love       spaces
is a rawrule)

> >     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'''
> 
> Some of these feel like duplication, or overlapping of purpose.

I always have a set of ..._raw, ..._obj and ..._log. The difference is 
the parameter type - a rawrule for _raw, a rule object like 
capability_rule (without "s"!)  for _obj and a parsed log event (as 
returned by libapparmor logparser) for _log.

BTW: is ..._rule vs ..._rules ok or should we use a name that differs 
more? (If yes, any idea?)

> >     def delete_duplicates(self, inccaps):
> >         '''Delete duplicate rules.
> 
> What is the inccaps argument here?

The capabilities of an include file (include[file][file]['capability'] 
in aa.py IIRC). I should probably rename inccaps to include_rules to 
make the name fit for all rule types.

> >     def get_glob_ext(self, path_or_rule):
> >         '''returns the next possible glob with extension (for file
> >         rules only).
> When we come around to dbus and some of the other rule types, it's
> likely they will also have meaningful results. What's the return
> type/value for if there's no meaningful glob? Would 'capability,'
> (*shudder*) be a valid glob for capabilities?

At the moment, my plan is to return a string (basically a rawrule), but 
that's something we can change if you have a better idea.

And yes, "capability," would be a valid glob - and also something I 
wouldn't want to have in my profiles ;-)

BTW: get_glob_ext() is for "Glob with (E)xt" - for capabilities, only 
get_glob() is available.

> > utils/apparmor/rule/capability.py:
> > 
> > class capability_rule(base_rule):
> >     '''Class to handle and store a single capability rule'''
> >     
> >     def __init__(self):
> >         self.capability = []
> 
> Why is it a list, for a single capability? (And again, just declare
> the field with the initial value.)

Because someone had the idea to allow
    capability chown chgrp audit_write,
in the profiles ;-)

Until now, the utils just parsed this as 
    "capability"     "chown chgrp audit_write"
but that makes finding duplicates quite hard ;-)

...
> > 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'''
> I... don't quite parse that, but perhaps I need to look at code to see
> the purpose.

This little helper function allowed me to move several functions to the 
base_rules class (the only difference for usage with other rule types 
was the class name of the rule object, so it made sense to split it out 
to the new_rule() function)

> >     def get_glob(self, path_or_rule):
> >         '''Return the next possible glob. For capability rules,
> >         that's always "capability," (all capabilities)'''
> Seems like perhaps there should be just a get_globs() function,
> which returns a list of globs/glob extensions, etc.?

Depends on how you want to use the result ;-)

For usage with aa-logprof, returning only one ("the next") glob fits the 
user interface perfectly. If you have different use cases (or prefer a 
different implementation way), tell me ;-)


Regards,

Christian Boltz
-- 
Am besten im Targa-Format,  oder Geotiff,  oder HandshakeCT, oder so.
Bloß nichts komprimiertes! Wohlmöglich entpackt sich die Datei in der
Leitung, und dann ist das Kabel im Eimer.       [Ratti in suse-linux]




More information about the AppArmor mailing list