[apparmor] [PATCH v2] Convert aa-status to Python

Marc Deslauriers marc.deslauriers at canonical.com
Thu May 26 19:59:21 UTC 2011


[v2: cleaned up for Python v3 support, moves Perl aa-status tool to the
deprecated directory for distros that still need it.]

Here is a patch that converts the aa-status tool to Python, so it can be
packaged separately from the rest of the perl tools and not require a
bunch of perl modules to be installed on a minimal system.

Marc.

=== added directory 'deprecated/utils'
=== renamed file 'utils/aa-status' => 'deprecated/utils/aa-status'
=== modified file 'utils/Makefile'
--- utils/Makefile	2011-02-08 18:39:44 +0000
+++ utils/Makefile	2011-05-26 17:53:59 +0000
@@ -28,8 +28,8 @@
 
 MODDIR = Immunix
 PERLTOOLS = aa-genprof aa-logprof aa-autodep aa-audit aa-complain aa-enforce \
-	aa-unconfined aa-status aa-notify aa-disable
-TOOLS = ${PERLTOOLS} aa-decode
+	aa-unconfined aa-notify aa-disable
+TOOLS = ${PERLTOOLS} aa-decode aa-status
 MODULES = ${MODDIR}/AppArmor.pm ${MODDIR}/Repository.pm \
 	${MODDIR}/Config.pm ${MODDIR}/Severity.pm
 

=== added file 'utils/aa-status'
--- utils/aa-status	1970-01-01 00:00:00 +0000
+++ utils/aa-status	2011-05-26 19:53:25 +0000
@@ -0,0 +1,203 @@
+#!/usr/bin/python
+# ------------------------------------------------------------------
+#
+#    Copyright (C) 2005-2006 Novell/SUSE
+#    Copyright (C) 2011 Canonical Ltd.
+#
+#    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 published by the Free Software Foundation.
+#
+# ------------------------------------------------------------------
+
+import re, os, sys
+
+def cmd_enabled():
+    '''Returns error code if AppArmor is not enabled'''
+    if get_profiles() == {}:
+        sys.exit(2)
+
+def cmd_profiled():
+    '''Prints the number of loaded profiles'''
+    profiles = get_profiles()
+    sys.stdout.write("%d\n" % len(profiles))
+    if profiles == {}:
+        sys.exit(2)
+
+def cmd_enforced():
+    '''Prints the number of loaded enforcing profiles'''
+    profiles = get_profiles()
+    sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'enforce')))
+    if profiles == {}:
+        sys.exit(2)
+
+def cmd_complaining():
+    '''Prints the number of loaded non-enforcing profiles'''
+    profiles = get_profiles()
+    sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'complain')))
+    if profiles == {}:
+        sys.exit(2)
+
+def cmd_verbose():
+    '''Displays multiple data points about loaded profile set'''
+    global verbose
+    verbose = True
+    profiles = get_profiles()
+    processes = get_processes(profiles)
+
+    stdmsg("%d profiles are loaded." % len(profiles))
+    for status in ('enforce', 'complain'):
+        filtered_profiles = filter_profiles(profiles, status)
+        stdmsg("%d profiles are in %s mode." % (len(filtered_profiles), status))
+        for item in filtered_profiles:
+                stdmsg("   %s" % item)
+
+    stdmsg("%d processes have profiles defined." % len(processes))
+    for status in ('enforce', 'complain'):
+        filtered_processes = filter_processes(processes, status)
+        stdmsg("%d processes are in %s mode." % (len(filtered_processes), status))
+        sorted_list = list(filtered_processes.keys())
+        sorted_list.sort()
+        for item in sorted_list:
+            stdmsg("   %s (%s) " % (item, filtered_processes[item]))
+
+    filtered_processes = filter_processes(processes, 'unconfined')
+    stdmsg("%d processes are unconfined but have a profile defined." % len(filtered_processes))
+    sorted_list = list(filtered_processes.keys())
+    sorted_list.sort()
+    for item in sorted_list:
+        stdmsg("   %s (%s) " % (item, filtered_processes[item]))
+
+    if profiles == {}:
+        sys.exit(2)
+
+def get_profiles():
+    '''Fetch loaded profiles'''
+
+    profiles = {}
+
+    if os.path.exists("/sys/module/apparmor"):
+        stdmsg("apparmor module is loaded.")
+    else:
+        errormsg("apparmor module is not loaded.")
+        sys.exit(1)
+
+    apparmorfs = find_apparmorfs()
+    if not apparmorfs:
+        errormsg("apparmor filesystem is not mounted.")
+        sys.exit(3)
+
+    apparmor_profiles = os.path.join(apparmorfs, "profiles")
+    if not os.access(apparmor_profiles, os.R_OK):
+        errormsg("You do not have enough privilege to read the profile set.")
+        sys.exit(4)
+
+    for p in open(apparmor_profiles).readlines():
+        match = re.search("^([^\(]+)\s+\((\w+)\)$", p)
+        profiles[match.group(1)] = match.group(2)
+
+    return profiles
+
+def get_processes(profiles):
+    '''Fetch process list'''
+    processes = {}
+    contents = os.listdir("/proc")
+    for filename in contents:
+        if filename.isdigit():
+            try:
+                for p in open("/proc/%s/attr/current" % filename).readlines():
+                    match = re.search("^([^\(]+)\s+\((\w+)\)$", p)
+                    if match:
+                        processes[filename] = { 'profile' : match.group(1), \
+                                                'mode' : match.group(2) }
+                    elif os.path.realpath("/proc/%s/exe" % filename) in profiles:
+                        # keep only unconfined processes that have a profile defined
+                        processes[filename] = { 'profile' : os.path.realpath("/proc/%s/exe" % filename), \
+                                                'mode' : 'unconfined' }
+            except:
+                pass
+    return processes
+
+def filter_profiles(profiles, status):
+    '''Return a list of profiles that have a particular status'''
+    filtered = []
+    for key, value in list(profiles.items()):
+        if value == status:
+            filtered.append(key)
+    filtered.sort()
+    return filtered
+
+def filter_processes(processes, status):
+    '''Return a dict of processes that have a particular status'''
+    filtered = {}
+    for key, value in list(processes.items()):
+        if value['mode'] == status:
+            filtered[value['profile']] = key
+    return filtered
+
+def find_apparmorfs():
+    '''Finds AppArmor mount point'''
+    for p in open("/proc/mounts").readlines():
+        if p.split()[2] == "securityfs" and \
+           os.path.exists(os.path.join(p.split()[1], "apparmor")):
+            return os.path.join(p.split()[1], "apparmor")
+    return False
+
+def errormsg(message):
+    '''Prints to stderr if verbose mode is on'''
+    global verbose
+    if verbose:
+        sys.stderr.write(message + "\n")
+
+def stdmsg(message):
+    '''Prints to stdout if verbose mode is on'''
+    global verbose
+    if verbose:
+        sys.stdout.write(message + "\n")
+
+def print_usage():
+    '''Print usage information'''
+    sys.stdout.write('''Usage: %s [OPTIONS]
+Displays various information about the currently loaded AppArmor policy.
+OPTIONS (one only):
+  --enabled       returns error code if AppArmor not enabled
+  --profiled      prints the number of loaded policies
+  --enforced      prints the number of loaded enforcing policies
+  --complaining   prints the number of loaded non-enforcing policies
+  --verbose       (default) displays multiple data points about loaded policy set
+  --help          this message
+''' % sys.argv[0])
+
+# Main
+global verbose
+verbose = False
+
+if len(sys.argv) > 2:
+    sys.stderr.write("Error: Too many options.\n")
+    print_usage()
+    sys.exit(1)
+elif len(sys.argv) == 2:
+    cmd = sys.argv.pop(1)
+else:
+    cmd = '--verbose'
+
+# Command dispatch:
+commands = {
+    '--enabled'      : cmd_enabled,
+    '--profiled'     : cmd_profiled,
+    '--enforced'     : cmd_enforced,
+    '--complaining'  : cmd_complaining,
+    '--verbose'      : cmd_verbose,
+    '-v'             : cmd_verbose,
+    '--help'         : print_usage,
+    '-h'             : print_usage
+}
+
+if cmd in commands:
+    commands[cmd]()
+    sys.exit(0)
+else:
+    sys.stderr.write("Error: Invalid command.\n")
+    print_usage()
+    sys.exit(1)
+

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: This is a digitally signed message part
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20110526/31000130/attachment-0001.pgp>


More information about the AppArmor mailing list