[apparmor] [PATCH] aa-keywords: Expose parser keywords

Goldwyn Rodrigues rgoldwyn at suse.de
Tue Feb 28 02:39:40 UTC 2017


From: Goldwyn Rodrigues <rgoldwyn at suse.com>

A simple utility to return the keywords used in apparmor.d profile
files.

This would enable utilities such as yast to create apparmor
profiles without the need to cross-checking and verifying
the syntax.

While there is nothing fancy about the tool, if you think this needs
more command-line arguments, I will be happy to put them in.

Signed-off-by: Goldwyn Rodrigues <rgoldwyn at suse.com>
---
 utils/aa-keywords              | 31 ++++++++++++++++++++
 utils/apparmor/rule/file.py    | 65 +++++++++++++++++++++++++++++++++---------
 utils/apparmor/rule/network.py |  8 ++++++
 utils/apparmor/rule/ptrace.py  |  4 +++
 utils/apparmor/rule/rlimit.py  | 12 ++++++++
 utils/apparmor/rule/signal.py  |  6 ++++
 6 files changed, 113 insertions(+), 13 deletions(-)
 create mode 100644 utils/aa-keywords

diff --git a/utils/aa-keywords b/utils/aa-keywords
new file mode 100644
index 0000000..3e22910
--- /dev/null
+++ b/utils/aa-keywords
@@ -0,0 +1,31 @@
+#! /usr/bin/python3
+# ----------------------------------------------------------------------
+#    Copyright (C) 2017 SUSE Linux Products
+#
+#    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.
+#
+# ----------------------------------------------------------------------
+
+import sys,json
+
+from apparmor.rule.file import FileRule
+from apparmor.rule.rlimit import RlimitRule
+from apparmor.rule.signal import SignalRule
+from apparmor.rule.ptrace import PtraceRule
+from apparmor.rule.network import NetworkRule
+
+jsonout = { 'file': FileRule.keywords(), 
+            'network': NetworkRule.keywords(), 
+            'rlimit': RlimitRule.keywords(),
+            'signal': SignalRule.keywords(),
+            'ptrace': PtraceRule.keywords()
+            }
+sys.stdout.write(json.dumps(jsonout, sort_keys=False, separators=(',', ': ')) + '\n')
+
diff --git a/utils/apparmor/rule/file.py b/utils/apparmor/rule/file.py
index 86270fd..8b8aaca 100644
--- a/utils/apparmor/rule/file.py
+++ b/utils/apparmor/rule/file.py
@@ -21,13 +21,41 @@ from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_v
 from apparmor.translations import init_translation
 _ = init_translation()
 
-
-allow_exec_transitions          = ('ix', 'ux', 'Ux', 'px', 'Px', 'cx', 'Cx')  # 2 chars - len relevant for split_perms()
-allow_exec_fallback_transitions = ('pix', 'Pix', 'cix', 'Cix', 'pux', 'PUx', 'cux', 'CUx')  # 3 chars - len relevant for split_perms()
-deny_exec_transitions           = ('x')
-file_permissions                = ('m', 'r', 'w', 'a', 'l', 'k')  # also defines the write order
-
-
+allow_exec_transitions          = {
+	'ix': 'inhert execute',
+	'ux': 'unconfined execute',
+	'Ux': 'unconfined execute - scrub environment',
+	'px': 'discrete profile execute',
+	'Px': 'discrete profile execute - scrub environment',
+	'cx': 'transition to subprofile execute',
+	'Cx': 'transition to subprofile execute - scrub environment'
+}  # 2 chars - len relevant for split_perms()
+
+allow_exec_fallback_transitions = {
+	'pix': 'discrete profile execute with inherit fallback',
+	'Pix': 'discrete profile execute with inherit fallback - scrub the environment',
+	'cix': 'transition to subprofile on execute with inherit fallback',
+	'Cix': 'transition to subprofile on execute with inherit fallback - scrub the environment',
+	'pux': 'discrete profile execute with fallback to unconfined',
+	'PUx': 'discrete profile execute with fallback to unconfined - scrub the environment',
+	'cux': 'transition to subprofile on execute with fallback to unconfirmed',
+	'CUx': 'transition to subprofile on execute with fallback to unconfirmed - scrub the environment'
+}  # 3 chars - len relevant for split_perms()
+
+deny_exec_transitions           = {
+	'x': 'Execute'
+}
+
+file_permissions                = {
+	'm': 'allow PROTO_EXEC with mmap calls', 
+	'r': 'read mode', 
+	'w': 'write mode', 
+	'a': 'append mode', 
+	'l': 'link', 
+	'k': 'lock'
+}  # also defines the write order
+
+#file_permissions                = ('m', 'r', 'w', 'a', 'l', 'k')
 
 class FileRule(BaseRule):
     '''Class to handle and store a single file rule'''
@@ -76,7 +104,7 @@ class FileRule(BaseRule):
         elif perms == None:
             perms = set()
 
-        self.perms, self.all_perms, unknown_items = check_and_split_list(perms, file_permissions, FileRule.ALL, 'FileRule', 'permissions', allow_empty_list=True)
+        self.perms, self.all_perms, unknown_items = check_and_split_list(perms, tuple(file_permissions.keys()), FileRule.ALL, 'FileRule', 'permissions', allow_empty_list=True)
         if unknown_items:
             raise AppArmorBug('Passed unknown perms to FileRule: %s' % str(unknown_items))
         if self.perms and 'a' in self.perms and 'w' in self.perms:
@@ -95,7 +123,7 @@ class FileRule(BaseRule):
             else:
                 if exec_perms == 'x':
                     raise AppArmorException(_("Execute flag ('x') in file rule must specify the exec mode (ix, Px, Cx etc.)"))
-                elif exec_perms not in allow_exec_transitions and exec_perms not in allow_exec_fallback_transitions:
+                elif exec_perms not in tuple(allow_exec_transitions.keys()) and exec_perms not in tuple(allow_exec_fallback_transitions.keys()):
                     raise AppArmorBug('Unknown execute mode specified in file rule: %s' % exec_perms)
             self.exec_perms = exec_perms
         else:
@@ -169,6 +197,17 @@ class FileRule(BaseRule):
         return FileRule(path, perms, exec_perms, target, owner, file_keyword, leading_perms,
                            audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
 
+    @staticmethod
+    def keywords():
+        ''' return a list of valid keywords '''
+        kw = {'Extra': 'deny',
+              'Exec Transitions': allow_exec_transitions,
+              'Exec Fallback Transitions': allow_exec_fallback_transitions,
+              'Deny Exec Transitions': deny_exec_transitions,
+              'File Permissions': file_permissions
+             }
+        return kw
+
     def get_clean(self, depth=0):
         '''return rule (in clean/default formatting)'''
 
@@ -224,7 +263,7 @@ class FileRule(BaseRule):
     def _join_given_perms(self, perms, exec_perms):
         '''return the permissions as string (using the perms and exec_perms given as parameter)'''
         perm_string = ''
-        for perm in file_permissions:
+        for perm in tuple(file_permissions.keys()):
             if perm in perms:
                 perm_string = perm_string + perm
 
@@ -511,7 +550,7 @@ def split_perms(perm_string, deny):
     exec_mode = None
 
     while perm_string:
-        if perm_string[0] in file_permissions:
+        if perm_string[0] in tuple(file_permissions.keys()):
             perms.add(perm_string[0])
             perm_string = perm_string[1:]
         elif perm_string[0] == 'x':
@@ -519,12 +558,12 @@ def split_perms(perm_string, deny):
                 raise AppArmorException(_("'x' must be preceded by an exec qualifier (i, P, C or U)"))
             exec_mode = 'x'
             perm_string = perm_string[1:]
-        elif perm_string.startswith(allow_exec_transitions):
+        elif perm_string.startswith(tuple(allow_exec_transitions.keys())):
             if exec_mode and exec_mode != perm_string[0:2]:
                 raise AppArmorException(_('conflicting execute permissions found: %s and %s' % (exec_mode, perm_string[0:2])))
             exec_mode = perm_string[0:2]
             perm_string = perm_string[2:]
-        elif perm_string.startswith(allow_exec_fallback_transitions):
+        elif perm_string.startswith(tuple(allow_exec_fallback_transitions.keys())):
             if exec_mode and exec_mode != perm_string[0:3]:
                 raise AppArmorException(_('conflicting execute permissions found: %s and %s' % (exec_mode, perm_string[0:3])))
             exec_mode = perm_string[0:3]
diff --git a/utils/apparmor/rule/network.py b/utils/apparmor/rule/network.py
index d1cc69b..90424c1 100644
--- a/utils/apparmor/rule/network.py
+++ b/utils/apparmor/rule/network.py
@@ -129,6 +129,14 @@ class NetworkRule(BaseRule):
         return NetworkRule(domain, type_or_protocol,
                            audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
 
+    @staticmethod
+    def keywords():
+        kw = {'Domain': network_domain_keywords,
+              'Type': network_type_keywords,
+              'Protocol': network_protocol_keywords
+             }
+        return kw
+
     def get_clean(self, depth=0):
         '''return rule (in clean/default formatting)'''
 
diff --git a/utils/apparmor/rule/ptrace.py b/utils/apparmor/rule/ptrace.py
index a82d06a..be93379 100644
--- a/utils/apparmor/rule/ptrace.py
+++ b/utils/apparmor/rule/ptrace.py
@@ -109,6 +109,10 @@ class PtraceRule(BaseRule):
         return PtraceRule(access, peer,
                            audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
 
+    @staticmethod
+    def keywords():
+        return {'words': access_keywords}
+
     def get_clean(self, depth=0):
         '''return rule (in clean/default formatting)'''
 
diff --git a/utils/apparmor/rule/rlimit.py b/utils/apparmor/rule/rlimit.py
index 2a2f5cd..81dc5c0 100644
--- a/utils/apparmor/rule/rlimit.py
+++ b/utils/apparmor/rule/rlimit.py
@@ -136,6 +136,18 @@ class RlimitRule(BaseRule):
         return RlimitRule(rlimit, value,
                            comment=comment)
 
+    @staticmethod
+    def keywords():
+        ''' return the valid keywords '''
+        sz = {'size': rlimit_size, 'suffix': 'K, M, G'}
+        nice = {'nice': rlimit_size, 'range_min': -20, 'range_max': 19}
+        numbers = {'numbers': rlimit_number}
+        time = {'time': rlimit_time, 'suffix': 'ms, us' }
+        return {'size': sz,
+                'number': numbers,
+                'nice': nice,
+                'time': time}
+
     def get_clean(self, depth=0):
         '''return rule (in clean/default formatting)'''
 
diff --git a/utils/apparmor/rule/signal.py b/utils/apparmor/rule/signal.py
index e37fec8..4781a34 100644
--- a/utils/apparmor/rule/signal.py
+++ b/utils/apparmor/rule/signal.py
@@ -147,6 +147,12 @@ class SignalRule(BaseRule):
         return SignalRule(access, signal, peer,
                            audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
 
+    @staticmethod
+    def keywords():
+        return {'access': access_keywords,
+                'signals': signal_keywords
+               }
+
     def get_clean(self, depth=0):
         '''return rule (in clean/default formatting)'''
 
-- 
2.10.2




More information about the AppArmor mailing list