[apparmor] [RFC] Refactoring apparmor-profiles repository

Vincas Dargis vindrg at gmail.com
Sat Jun 23 12:11:12 UTC 2018


On 6/18/18 2:28 AM, John Johansen wrote:
> On 06/17/2018 05:56 AM, Vincas Dargis wrote:
>> With policy versioning we could have single profile for all future AppArmor releases? Basically, no more need to have these directories (nor "ubuntu", neither "apparmor/x.y")?
>>
> A possibility, that I would certainly like to get to, but I don't think we will be able to get to it with the 3.0 release

Oh, we need some more "conditioning" features I guess?
> Basically the big issue is installing policy with rules that supports new features on older systems that don't have support for the feature. You could update the userspace but most stable releases are unwilling to do this. To help deal with this in the past we have been front running the features in the parser before they land upstream, but we really need to get away from doing that. We could wrap rules in conditional statements but with the current implementation the parser will break as conditionals are not a preprocess and the rules in their blocks must be parsable.
> 
> The long term goal is to allow defining parsing patterns within the parser language itself. So we could add a pattern that would be used to skip/downgrade a rule if the parser doesn't support the feature. So something along the lines of
> 
>    if !parser_supports(Xapparmor) {
>       ignore pattern 'X' .... # some expression that will let the parser get past the unsupported rule
>    }
> 
> and eventually if the rules follow supported patterns and don't need anything specially allow declaring the rule patterns in policy entirely.

This "x_supports" is planned for > 3.0?

>> Will we have new policy version numbers on every minor AppArmor release (with new/updated abstractions)?
>>
> Using policy versioning as a name is really as misnomer, at least at the policy level, the idea certainly started out as such but it has evolved from there.
> 
> The idea is that we declare the supported feature abi that the policy was developed under right in the policy it self. This will give a similar effect as using feature pinning but on a per profile basis instead of applying to all policy on the system. If the feature abi is not declared in policy then, policy will fall back to feature pinning and then if there is no declaration the compile will fallback to the 4.14 feature abi as the default.
> 
> Policy wise the base of policy versioning is the new feature rule.
> 
>    features=<features/file>,
> 
> Also instead of sticking the features into a feature file they can be included inline
>     features={ blah, blah blah }
> 
> the features file will be the same as what is currently used for caching and feature pinning. And in support of this, there will be a couple of additions to the parser language to help identify what is currently supported.
> 
>    kernel_supports
>    parser_supports
>    policy_supports
>    supports - asking about what is supported for this compile which is an intersection of what the kernel and the policy supports
> 
> hopefully if we do it right we can hide most conditionals within the abstractions.
> 
> to abstract some of the details away we are going to wrap the features= rule in an include which will allow us to abstract away anything new we come up with
> 
> the include could contain multiple features= rules, defining some variables that can be used conditionally etc.
> 
> It is the include file naming and how packaging of policy is handled that would be the closest to what policy versioning implies, and I have been struggling with how best to do this

OK so it's "versioning" against features. That's useful, as two 
distributions might look like having the "same" AppArmor v3.0, though 
one distro might not have DBus mediation in Kernel yet, for example.

>> Could you give us an example of how versioned profile snippet will look like? Meaning, "if apparmor/policy version is >= X, then include <abstraction/foo>, else copy-pasted rule...".
>>
> 
> So I'd like to address the version number first. I would like, if possible, to avoid having to do "if version >= X", I am not saying there won't be cases where something of the sort is needed but I would rather the conditionals be along the lines of
> 
>    if (supports(X)) {
>       include <abstraction/foo>
>    }

So for every new abstraction there should be defined as "a feature" in a 
version/3.0 include, basically? This "supports" will look at included 
feature set and/or distro feature file, right?

In recent Thunderbird profile backport [0] I've copied 
abstractions/dri-enumerate content inline, as Debian Sid does not yet 
have 2.13. What if abstractions/dri-enumerate would have been available, 
but not up-to-date, with some rules missing?

We aren't going to add a feature definition for every abstraction 
change..? (you do mention abstraction versions later, but see below)

> and that we keep the conditionals in the abstractions as much as possible
> 
> The version abstraction at its most basic would look like
> $ cat version/3.0
> 
> # feature set supported by the 3.0 release
> features=<features/4.17>
> # allow policy to also support kernels with the older out of tree patches
> features=<features/out_of_tree_net_and_af_unix>
> @{version}=3.0    # or perhaps define the var as @{abi} instead doesn't matter as long as we are consistent
> 
> $
> 
> The idea behind the @{version} variable is then that could be used in policy to do things like
> 
> include <abstractions/@{version}/...>
> 
> for version specific bits if needed.
> 
> 
> a profile file would then do
> 
>    include <version/3.0>
> 
> to set what its policy version

No sure about this this <abstractions/@{version}/... , I imagine one 
would like to use latest abstraction by default, only adding 
(backporting) some missing rules. I guess it could look like this (still 
using v2.13 as for example with mentioned Thunderbird issue, with 
imaginary 2.14):

```
if (@{version} >= 2.14) {

   # AA >= 2.14 abstraction has all the features we need (so far)!
   #include <abstractions/dri-enumarate>

} else if (@{version} == 2.13) {

    #include <abstractions/dri-enumarate>

   # Backported from the dri-enumerate abstraction from AppArmor 2.14
    /some/additional/rule.. r,

} else {

   # We don't have dri-enumerate at all in AA 2.12!

   # Backported from the dri-enumerate abstraction from AppArmor 2.13
 
/sys/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} 
r,

   # Backported from the dri-enumerate abstraction from AppArmor 2.14
    /some/additional/rule.. r,
}

That's a lot of "ifdefing", but maybe it could reach that "single 
profile for all AA versions" style though.

Or is there a cleaner approach? You said you would like to avoid "if 
version >= X", I am not yet sure about avoiding in this example, when 
AppArmor version is fixed in distribution release, but some application 
(like Thunderbird in this example) gets updates and needs more recent 
abstractions.
```

> packaging and dealing with missing bits of policy is where I am failing atm, and 3.0 won't have debhelpers or rpm macros to help with packaging needs around policy versioning.
> 
> I am not sure if we want to split the version and feature files into their own packages that packaging can setup dependencies on or whether it should just be dealt with in policy.
> 
> policy authors could do things like
> 
>    include if exists <version/3.0>
> 
> if they want the ability to fallback to a different version, but use a regular include if they want the missing dependency to fail.

OK so let's imagine Debian Buster has AppArmor 3.0, and Buster+1 has 
AppArmor 3.2. How could we make _same single profile_ that could use 3.2 
features on Buster+1, and 3.0 on Buster?

Multiple includes that would override each over? Like this:

```
#include <version/3.0> # policy needs at least 3.0!
#include if exists <version/3.2> # add more features than 3.0 had

...

if supports(dbus_mediation) { # since ABI version 3.2
	#include <abstractions/dbus-session-strict>
	dbus ...,
}
```

> I suspect we are going to end up with a mix of packaging and policy magic depending on the policy author needs. Policy that the apparmor project ships could be easily setup to have packaging dependencies but LXD might choose to do something more flexible as they are dynamically generating policy
> 
> 
>> Maybe we could still use "apparmor/3.0" directory for new-style versioned profiles, leaving ubuntu/x.y for backpacking as it is now (We will have Ubuntu 18.04 for quite some time). If any day policy changes too much, "apparmor/4.0" could be added, or maybe we are sure enough that that's never going to happen, and we don't need that "3.0" at all?
>>
> 
> That is a possibility, and whether or not we are sure that policy will never change enough to require a new version number we are going to keep the option to provide one because if we don't, we will need it later.

So is this "a deal"? Could we have "apparmor/3.0" (or similar) in 
apparmor-profile repo to make this big distinction against "old-style" 
policies, to add new 3.x-based profiles and upgrade older ones? What 
others think?

[0] 
https://gitlab.com/apparmor/apparmor-profiles/commit/748fe86fd81a9e48b5a26c771c3551c444c1d89a



More information about the AppArmor mailing list