[apparmor] [PATCH v2 4/8] utils: Accept parser base and include options in aa-easyprof

Tyler Hicks tyhicks at canonical.com
Wed Mar 1 20:52:02 UTC 2017


https://launchpad.net/bugs/1521031

aa-easyprof accepts a list of abstractions to include and, by default,
execs apparmor_parser to verify the generated profile including any
abstractions. However, aa-easyprof didn't provide the same flexibility
as apparmor_parser when it came to where in the filesystem the
abstraction files could exist.

The parser supports --base (defaulting to /etc/apparmor.d) and --Include
(defaulting to unset) options to specify the search paths for
abstraction files. This patch adds the same options to aa-easyprof to
aide in two different situations:

 1) Some Ubuntu packages use aa-easyprof to generate AppArmor profiles
    at build time. Something that has been previously needed is a way
    for those packages to ship their own abstractions file(s) that are
    #included in the easyprof-generated profile. That's not been
    possible since the abstraction file(s) have not yet been installed
    during the package build.

 2) The test-aa-easyprof.py script contains some tests that specify
    abstractions that should be #included. Without the ability to
    specify a different --base or --Include directory, the abstractions
    were required to be present in /etc/apparmor.d/abstractions/ or the
    tests would fail. This prevents the Python utils from being able to
    strictly test against in-tree code/profiles/etc.

I don't like the names of the command line options --base and --Include.
They're not particularly descriptive and the capital 'I' is not user
friendly. However, I decided to preserve the name of the options from
apparmor_parser.

Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
Acked-by: Christian Boltz <apparmor at cboltz.de>
Acked-by: Seth Arnold <seth.arnold at canonical.com>
---
 utils/aa-easyprof.pod          | 10 +++++
 utils/apparmor/easyprof.py     | 43 +++++++++++++++++----
 utils/test/test-aa-easyprof.py | 88 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 133 insertions(+), 8 deletions(-)

diff --git a/utils/aa-easyprof.pod b/utils/aa-easyprof.pod
index e82041b..31687bf 100644
--- a/utils/aa-easyprof.pod
+++ b/utils/aa-easyprof.pod
@@ -64,6 +64,16 @@ usually recommended you use policy groups instead, but this is provided as a
 convenience. AppArmor abstractions are located in /etc/apparmor.d/abstractions.
 See apparmor.d(5) for details.
 
+=item -b PATH, --base=PATH
+
+Set the base PATH for resolving abstractions specified by --abstractions.
+See the same option in apparmor_parser(8) for details.
+
+=item -I PATH, --Include=PATH
+
+Add PATH to the search paths used for resolving abstractions specified by
+--abstractions. See the same option in apparmor_parser(8) for details.
+
 =item -r PATH, --read-path=PATH
 
 Specify a PATH to allow owner reads. May be specified multiple times. If the
diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py
index d7ca3e1..01c7fd6 100644
--- a/utils/apparmor/easyprof.py
+++ b/utils/apparmor/easyprof.py
@@ -259,7 +259,7 @@ def open_file_read(path):
     return orig
 
 
-def verify_policy(policy):
+def verify_policy(policy, base=None, include=None):
     '''Verify policy compiles'''
     exe = "/sbin/apparmor_parser"
     if not os.path.exists(exe):
@@ -279,7 +279,14 @@ def verify_policy(policy):
         os.write(f, policy)
         os.close(f)
 
-    rc, out = cmd([exe, '-QTK', fn])
+    command = [exe, '-QTK']
+    if base:
+        command.extend(['-b', base])
+    if include:
+        command.extend(['-I', include])
+    command.append(fn)
+
+    rc, out = cmd(command)
     os.unlink(fn)
     if rc == 0:
         return True
@@ -302,6 +309,14 @@ class AppArmorEasyProfile:
         if os.path.isfile(self.conffile):
             self._get_defaults()
 
+        self.parser_base = "/etc/apparmor.d"
+        if opt.parser_base:
+            self.parser_base = opt.parser_base
+
+        self.parser_include = None
+        if opt.parser_include:
+            self.parser_include = opt.parser_include
+
         if opt.templates_dir and os.path.isdir(opt.templates_dir):
             self.dirs['templates'] = os.path.abspath(opt.templates_dir)
         elif not opt.templates_dir and \
@@ -350,8 +365,6 @@ class AppArmorEasyProfile:
         if not 'policygroups' in self.dirs:
             raise AppArmorException("Could not find policygroups directory")
 
-        self.aa_topdir = "/etc/apparmor.d"
-
         self.binary = binary
         if binary:
             if not valid_binary_path(binary):
@@ -506,9 +519,15 @@ class AppArmorEasyProfile:
 
     def gen_abstraction_rule(self, abstraction):
         '''Generate an abstraction rule'''
-        p = os.path.join(self.aa_topdir, "abstractions", abstraction)
-        if not os.path.exists(p):
-            raise AppArmorException("%s does not exist" % p)
+        base = os.path.join(self.parser_base, "abstractions", abstraction)
+        if not os.path.exists(base):
+            if not self.parser_include:
+                raise AppArmorException("%s does not exist" % base)
+
+            include = os.path.join(self.parser_include, "abstractions", abstraction)
+            if not os.path.exists(include):
+                raise AppArmorException("Neither %s nor %s exist" % (base, include))
+
         return "#include <abstractions/%s>" % abstraction
 
     def gen_variable_declaration(self, dec):
@@ -661,7 +680,7 @@ class AppArmorEasyProfile:
 
         if no_verify:
             debug("Skipping policy verification")
-        elif not verify_policy(policy):
+        elif not verify_policy(policy, self.parser_base, self.parser_include):
             msg("\n" + policy)
             raise AppArmorException("Invalid policy")
 
@@ -811,6 +830,14 @@ def add_parser_policy_args(parser):
                       dest="abstractions",
                       help="Comma-separated list of abstractions",
                       metavar="ABSTRACTIONS")
+    parser.add_option("-b", "--base",
+                      dest="parser_base",
+                      help="Set the base directory for resolving abstractions",
+                      metavar="DIR")
+    parser.add_option("-I", "--Include",
+                      dest="parser_include",
+                      help="Add a directory to the search path when resolving abstractions",
+                      metavar="DIR")
     parser.add_option("--read-path",
                       action="callback",
                       callback=check_for_manifest_arg_append,
diff --git a/utils/test/test-aa-easyprof.py b/utils/test/test-aa-easyprof.py
index 5cc1f79..3ebfec6 100755
--- a/utils/test/test-aa-easyprof.py
+++ b/utils/test/test-aa-easyprof.py
@@ -913,6 +913,94 @@ POLICYGROUPS_DIR="%s/templates"
                 raise
             raise Exception ("abstraction '%s' should be invalid" % s)
 
+    def _create_tmp_base_dir(self, prefix='', abstractions=[], tunables=[]):
+        '''Create a temporary base dir layout'''
+        base_name = 'apparmor.d'
+        if prefix:
+            base_name = '%s-%s' % (prefix, base_name)
+        base_dir = os.path.join(self.tmpdir, base_name)
+        abstractions_dir = os.path.join(base_dir, 'abstractions')
+        tunables_dir = os.path.join(base_dir, 'tunables')
+
+        os.mkdir(base_dir)
+        os.mkdir(abstractions_dir)
+        os.mkdir(tunables_dir)
+
+        for f in abstractions:
+            contents = '''
+  # Abstraction file for testing
+  /%s r,
+''' % (f)
+            open(os.path.join(abstractions_dir, f), 'w').write(contents)
+
+        for f in tunables:
+            contents = '''
+# Tunable file for testing
+@{AA_TEST_%s}=foo
+''' % (f)
+            open(os.path.join(tunables_dir, f), 'w').write(contents)
+
+        return base_dir
+
+    def test_genpolicy_abstractions_custom_base(self):
+        '''Test genpolicy (custom base dir)'''
+        abstraction = "custom-base-dir-test-abstraction"
+        # The default template #includes the base abstraction and global
+        # tunable so we need to create placeholders
+        base = self._create_tmp_base_dir(abstractions=['base', abstraction], tunables=['global'])
+        args = ['--abstractions=%s' % abstraction, '--base=%s' % base]
+
+        p = self._gen_policy(extra_args=args)
+        search = "#include <abstractions/%s>" % abstraction
+        self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
+        inv_s = '###ABSTRACTIONS###'
+        self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
+
+    def test_genpolicy_abstractions_custom_base_bad(self):
+        '''Test genpolicy (custom base dir - bad base dirs)'''
+        abstraction = "custom-base-dir-test-abstraction"
+        bad = [ None, '/etc/apparmor.d', '/' ]
+        for base in bad:
+            try:
+                args = ['--abstractions=%s' % abstraction]
+                if base:
+                    args.append('--base=%s' % base)
+                self._gen_policy(extra_args=args)
+            except easyprof.AppArmorException:
+                continue
+            except Exception:
+                raise
+            raise Exception ("abstraction '%s' should be invalid" % abstraction)
+
+    def test_genpolicy_abstractions_custom_include(self):
+        '''Test genpolicy (custom include dir)'''
+        abstraction = "custom-include-dir-test-abstraction"
+        # No need to create placeholders for the base abstraction or global
+        # tunable since we're not adjusting the base directory
+        include = self._create_tmp_base_dir(abstractions=[abstraction])
+        args = ['--abstractions=%s' % abstraction, '--Include=%s' % include]
+        p = self._gen_policy(extra_args=args)
+        search = "#include <abstractions/%s>" % abstraction
+        self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
+        inv_s = '###ABSTRACTIONS###'
+        self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
+
+    def test_genpolicy_abstractions_custom_include_bad(self):
+        '''Test genpolicy (custom include dir - bad include dirs)'''
+        abstraction = "custom-include-dir-test-abstraction"
+        bad = [ None, '/etc/apparmor.d', '/' ]
+        for include in bad:
+            try:
+                args = ['--abstractions=%s' % abstraction]
+                if include:
+                    args.append('--Include=%s' % include)
+                self._gen_policy(extra_args=args)
+            except easyprof.AppArmorException:
+                continue
+            except Exception:
+                raise
+            raise Exception ("abstraction '%s' should be invalid" % abstraction)
+
     def test_genpolicy_profile_name_bad(self):
         '''Test genpolicy (profile name - bad values)'''
         bad = [
-- 
2.7.4




More information about the AppArmor mailing list