[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