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

Marc Deslauriers marc.deslauriers at canonical.com
Thu May 26 17:59:35 UTC 2011


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.


=== 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
 

=== modified file 'utils/aa-status'
--- utils/aa-status	2010-12-20 20:29:10 +0000
+++ utils/aa-status	2011-05-26 17:55:23 +0000
@@ -1,7 +1,8 @@
-#!/usr/bin/perl -w
+#!/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
@@ -9,210 +10,194 @@
 #
 # ------------------------------------------------------------------
 
-
-use strict;
-use Getopt::Long;
-use Cwd 'abs_path';
-
-my $confdir = "/etc/apparmor";
-my $sd_mountpoint;
-my $check_enabled = 0;
-my $count_enforced = 0;
-my $count_profiled = 0;
-my $count_complain = 0;
-my $verbose = 0;
-my $help;
-
-GetOptions(
-  'complaining'		=> \$count_complain,
-  'enabled'		=> \$check_enabled,
-  'enforced'		=> \$count_enforced,
-  'profiled'		=> \$count_profiled,
-  'verbose|v'		=> \$verbose,
-  'help|h'		=> \$help,
-) or usage();
-
-sub usage {
-  print "Usage: $0 [OPTIONS]\n";
-  print "Displays various information about the currently loaded AppArmor policy.\n";
-  print "OPTIONS (one only):\n";
-  print "  --enabled       returns error code if subdomain not enabled\n";
-  print "  --profiled      prints the number of loaded policies\n";
-  print "  --enforced      prints the number of loaded enforcing policies\n";
-  print "  --complaining   prints the number of loaded non-enforcing policies\n";
-  print "  --verbose       (default) displays multiple data points about loaded policy set\n";
-  print "  --help          this message\n";
-  exit;
-}
-
-$verbose = 1 if ($count_complain + $check_enabled + $count_enforced + $count_profiled == 0);
-usage() if $help or ($count_complain + $check_enabled + $count_enforced + $count_profiled + $verbose > 1);
-
-sub is_subdomain_loaded() {
-  return 1 if (-d "/sys/module/apparmor");
-  if(open(MODULES, "/proc/modules")) {
-    while(<MODULES>) {
-      return 1 if m/^(subdomain|apparmor)\s+/;
-    }
-  }
-
-  return 0;
-}
-
-sub find_subdomainfs() {
-
-  my $sd_mountpoint;
-  if(open(MOUNTS, "/proc/mounts")) {
-    while(<MOUNTS>) {
-      $sd_mountpoint = "$1/apparmor" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/apparmor";
-      $sd_mountpoint = "$1/subdomain" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/subdomain";
-      $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/ && -e "$1";
-    }
-    close(MOUNTS);
-  }
-
-  return $sd_mountpoint;
-}
-
-sub get_profiles {
-  my $mountpoint = shift;
-  my %profiles = ();
-
-  if (open(PROFILES, "$mountpoint/profiles")) {
-    while(<PROFILES>) {
-      $profiles{$1} = $2 if m/^([^\(]+)\s+\((\w+)\)$/;
-    }
-    close(PROFILES);
-  }
-  return (%profiles);
-}
-
-sub get_processes {
-  my %profiles = @_;
-  my %processes = ();
-  if (opendir(PROC, "/proc")) {
-    my $file;
-    while (defined($file = readdir(PROC))) {
-      if ($file =~ m/^\d+/) {
-        if (open(CURRENT, "/proc/$file/attr/current")) {
-          while (<CURRENT>) {
-            if (m/^([^\(]+)\s+\((\w+)\)$/) {
-              $processes{$file}{'profile'} = $1;
-              $processes{$file}{'mode'} = $2;
-            } elsif (grep(abs_path("/proc/$file/exe") eq $_ , keys(%profiles))) {
-              # keep only unconfined processes that have a profile defined
-              $processes{$file}{'profile'} = abs_path("/proc/$file/exe");
-              $processes{$file}{'mode'} = 'unconfined';
-            }
-          }
-          close(CURRENT);
-        }
-      }
-    }
-    closedir(PROC);
-  }
-  return (%processes);
-}
-
-my $is_loaded = is_subdomain_loaded();
-
-if (!$is_loaded) {
-  print STDERR "apparmor module is not loaded.\n" if $verbose;
-  exit 1;
-}
-
-print "apparmor module is loaded.\n" if $verbose;
-
-$sd_mountpoint = find_subdomainfs();
-if (!$sd_mountpoint) {
-  print STDERR "apparmor filesystem is not mounted.\n" if $verbose;
-  exit 3;
-}
-
-if (! -r "$sd_mountpoint/profiles") {
-  print STDERR "You do not have enough privilege to read the profile set.\n" if $verbose;
-  exit 4;
-}
-
-#print "subdomainfs is at $sd_mountpoint.\n" if $verbose;
-
-# processes is a hash table :
-#   * keys : processes pid
-#   * values : hash containing information about the running process:
-#       * 'profile' : name of the profile applied to the running process
-#       * 'mode' : mode of the profile applied to the running process
-my %processes = ();
-my %enforced_processes = ();
-my %complain_processes = ();
-my %unconfined_processes = ();
-
-# profiles is a hash table :
-#  * keys : profile name
-#  * value : profile mode
-my %profiles;
-my @enforced_profiles = ();
-my @complain_profiles = ();
-
-%profiles = get_profiles($sd_mountpoint);
- at enforced_profiles = grep { $profiles{$_} eq 'enforce' } keys %profiles;
- at complain_profiles = grep { $profiles{$_} eq 'complain' } keys %profiles;
-
-# we consider the case where no profiles are loaded to be "disabled" as well
-my $rc = (keys(%profiles) == 0) ? 2 : 0;
-
-if ($check_enabled) {
-  exit $rc;
-}
-
-if ($count_profiled) {
-  print scalar(keys(%profiles)). "\n";
-  exit $rc;
-}
-
-if ($count_enforced) {
-  print $#enforced_profiles + 1 . "\n";
-  exit $rc;
-}
-
-if ($count_complain) {
-  print $#complain_profiles + 1 . "\n";
-  exit $rc;
-}
-
-
-if ($verbose) {
-  print keys(%profiles) . " profiles are loaded.\n";
-  print $#enforced_profiles + 1 . " profiles are in enforce mode.\n";
-  for (sort(@enforced_profiles)) {
-    print "   " . $_ . "\n";
-  }
-  print $#complain_profiles + 1 . " profiles are in complain mode.\n";
-  for (sort(@complain_profiles)) {
-    print "   " . $_ . "\n";
-  }
-}
-
-%processes = get_processes(%profiles);
-if ($verbose) {
-  for (keys(%processes)) {
-    $enforced_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'enforce';
-    $complain_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'complain';
-    # some early code uses unconfined instead of unconfined.
-    $unconfined_processes{$_} = $processes{$_} if $processes{$_}{'mode'} =~ /uncon(fi|strai)ned/;
-  }
-  print keys(%processes) . " processes have profiles defined.\n";
-  print keys(%enforced_processes) . " processes are in enforce mode :\n";
-  for (sort { $enforced_processes{$a}{'profile'} cmp $enforced_processes{$b}{'profile'} } keys(%enforced_processes)) {
-    print "   " . $enforced_processes{$_}{'profile'} . " ($_) \n";
-  }
-  print keys(%complain_processes) . " processes are in complain mode.\n";
-  for (sort { $complain_processes{$a}{'profile'} cmp $complain_processes{$b}{'profile'} } keys(%complain_processes)) {
-    print "   " . $complain_processes{$_}{'profile'} . " ($_) \n";
-  }
-  print keys(%unconfined_processes) . " processes are unconfined but have a profile defined.\n";
-  for (sort { $unconfined_processes{$a}{'profile'} cmp $unconfined_processes{$b}{'profile'} } keys(%unconfined_processes)) {
-    print "   " . $unconfined_processes{$_}{'profile'} . " ($_) \n";
-  }
-}
-
-exit $rc;
+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()
+    print "%d" % len(profiles)
+    if profiles == {}:
+        sys.exit(2)
+
+def cmd_enforced():
+    '''Prints the number of loaded enforcing profiles'''
+    profiles = get_profiles()
+    print "%d" % len(filter_profiles(profiles, 'enforce'))
+    if profiles == {}:
+        sys.exit(2)
+
+def cmd_complaining():
+    '''Prints the number of loaded non-enforcing profiles'''
+    profiles = get_profiles()
+    print "%d" % 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)
+
+    print "%d profiles are loaded." % len(profiles)
+    for status in ('enforce', 'complain'):
+        filtered_profiles = filter_profiles(profiles, status)
+        print "%d profiles are in %s mode." % (len(filtered_profiles), status)
+        for item in filtered_profiles:
+            print "   %s" % item
+
+    print "%d processes have profiles defined." % len(processes)
+    for status in ('enforce', 'complain'):
+        filtered_processes = filter_processes(processes, status)
+        print "%d processes are in %s mode." % (len(filtered_processes), status)
+        sorted_list = filtered_processes.keys()
+        sorted_list.sort()
+        for item in sorted_list:
+            print "   %s (%s) " % (item, filtered_processes[item])
+
+    filtered_processes = filter_processes(processes, 'unconfined')
+    print "%d processes are unconfined but have a profile defined." % len(filtered_processes)
+    sorted_list = filtered_processes.keys()
+    sorted_list.sort()
+    for item in sorted_list:
+        print "   %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 profiles.has_key(os.path.realpath("/proc/%s/exe" % filename)):
+                        # 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 profiles.iteritems():
+        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 processes.iteritems():
+        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:
+        print >>sys.stderr, message
+
+def stdmsg(message):
+    '''Prints to stdout if verbose mode is on'''
+    global verbose
+    if verbose:
+        print message
+
+def print_usage():
+    '''Print usage information'''
+    print >> sys.stdout, '''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:
+    print >>sys.stderr, "Error: Too many options."
+    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 commands.has_key(cmd):
+    commands[cmd]()
+    sys.exit(0)
+else:
+    print >>sys.stderr, "Error: Invalid command."
+    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/1bc8d80a/attachment.pgp>


More information about the AppArmor mailing list