[apparmor] Proposal for rule class

Christian Boltz apparmor at cboltz.de
Sun Oct 26 12:25:18 UTC 2014


Hello,

I have a first proposal for the rule class interface :-)

The code below is basically a python skeleton with lots of comments, 
but also contains some pseudocode or implicit code (like variables that
are "just there" without being set before - however their name should
indicate what they mean ;-)

I'm quite sure this is not the final interface, but I think it's something
that should at least work in theory ;-)


Do you like my proposal or do you want something totally different?
Did I include something that will explode in practise?
What's your opinion on the questions I included in the comments?
Feedback welcome! ;-)


BTW: This is the first public version, but my personal version counter
is > 10. To simplify things, let's just use the date as version ;-)



# proposal for a rule class (v2014-10-26)
# the interface should be (nearly) the same for all rule types


# aa[profile][hat]['netdomain'] = networkrule
#               ^^^^
#          - allow/deny handled inside the class
#          - only one instance of the class per profile, with all rules of the same type in it  

class aarule

    global_profiles = None
    global_abstractions = None
        # need to be set when creating the instance of the class
        #
        # needed for handling of #include - global_profiles and global_abstractions should contain _references_
        # (not a copy! we always need the latest version!) to all profiles and all available abstractions.
        # (I first thought about using global variables, but this might cause problems if we want to have more 
        # than one set of profiles loaded at once, especially when it comes to includes (aa-mergeprof?).)
        #
        # In theory global_profiles[profile][hat] would be enough, but there might be cases where knowing the
        # list of all profiles is useful, for example when proposing *x rules it could be helpful if the target
        # profile exists
        # (if we do this, rename global_profiles to full_profile and drop the profile and hat variables)

    profile = ''
    hat = ''
        # need to be set when creating the instance of the class
        #
        # needed when checking global_profiles for includes etc.
        # (or we need to hand over the includes at various points, which would make the class more stand-alone, 
        # but annoying to use)
        #
        # should be private to the class (outside users know this by the position in the global profiles variable)

    rules = []
        # TODO: decide about internal structure
        # - class contains both allow and deny rules
        # - probably some nested dicts, but not hasher() ;-)
        # - different for each rule type, but should be consistent as much as possible
        #
        # each rule should
        # - have a 'changed' flag
        # - maybe even have a 'deleted' flag so that we can easily track deleted rules
        #   (for 3-way merge or generating diffs internally)
        # - store the raw rule as found in the profile file (to write a profile in original formatting)
        # - store the comment line(s) above
        # - store the comment at the end of the line
        #
        # I'm also thinking about the internal order - do we want to keep [allow/deny][path][permissions]
        # or is [path][allow/deny][permissions] easier to handle?
        #
        # should be read-only for "outside" users (needed for checking the rules in #include files!)

    canglob = 1
        # decides if the (G)lob and Glob w/ (E)xt options are displayed
        #
        # Possible values:
        # - 2 - glob + glob with extension (for file rules)
        # - 1 - just normal glob, for example for network rules
        # - 0 - for rule types that don't support globbing
        #
        # should be read-only for "outside" users

    last_proposal = None
        # cache last proposal by _propose_rules (used by add_proposed_rule)
        # should be private to the class

    def __init__(self, profile, hat, global_profiles, global_abstractions)
        self.profile = profile
        self.hat = hat
        self.global_profiles = global_profiles
        self.global_abstractions = global_abstractions

    def initrule()
        # called by add() to initialize the internal structure of a new rules
        # (internal use only)

    def add(rawrule)
        # parse rawrule (from profile file) and store it in a structured way

    def add_proposed_rule(proposal_number, chosen_action)
        # like add, but takes one of the last_proposed rules as input
        # - proposal_number is the number of the proposed rule, for example "[1] /foo r"
        # - chosen_action is the selected "button", for example "(A)llow"

    def get_raw()
        # return all raw rules (if possible/not modified) as previously added with add(),
        # and of couse also all added rules
        # (as array of lines, without leading whitespace - or add a 'whitespace' parameter)
        # replaces write_$ruletype() (in non-clean mode)

    def get_clean()
        # get "clean" rules
        # replaces write_$ruletype() (in clean mode)

    def covered(rawrule)
        # return true if covered by existing rules (which means aa-logprof doesn't need to ask), otherwise false
        # needs the includes from global_profiles

    def covered_from_log(log_entry)
        # like covered, but takes a log entry as input

    def get_glob(path_or_rule)
        # returns the next possible glob

    def get_glob_ext(path_or_rule)
        # returns the next possible glob with extension (for file rules only)

    def propose_rules(rawrule)
        # returns _propose_rules() result for usage in aa-mergeprof
        # PARAM rawrule: raw rule from other profile
        return _propose_rules(parsed_rawrule)

    def propose_rules_from_log(log_entry)
        # returns _propose_rules() result for usage in aa-logprof
        # PARAM log_entry: log entry

        # wildcards etc. in filenames need to be escaped before calling _propose_rules()!
        return _propose_rules(parsed_and_escaped_log_entry)

    def _propose_rules(parsed_rule)
        # returns an array of 
        # - array with proposed rules (including globs from logprof.conf etc. and abstractions)
        # - can_glob (see explanation in class header)
        #   - if all proposals are already "globbed to the end", should we return 0 for can_glob?
        #     (for example if we only offer bare "network," and an abstraction)
        # - or go a step further and return an array with available_buttons instead of only can_glob?
        #   that would mean just allow/deny, audit and maybe glob for many cases, but for file rules
        #   it could also include the various *x options
        #
        # PARAM parsed_rule: pre-parsed rule (rawrule or log_entry)
        # needs the list of all available abstractions from global_abstractions

        this.last_proposal = proposal  # needed by add_proposed_rule()
        return proposal, can_glob

    def delete_duplicates()
        # does exactly that ;-)
        # needs the profile's includes from global_profiles

    def get_diff()
        # just dreaming ;-)
        # (but it should be possible based on the information inside the class)





Regards,

Christian Boltz
-- 
[Passwörter] Ich suche nach einem Mittelweg zwischen maximaler
Sicherheit und Zumutbarkeit für den Benutzer (ein Pferd mit Hufen,
dem unsere Admin-Tastaturen viel zu klein sind :-)).
[Manfred Rebentisch in suse-linux]




More information about the AppArmor mailing list