From 061cc81842668cd1e832a1d25ca6735dc7662dd2 Mon Sep 17 00:00:00 2001 From: Martyn Welch <martyn.welch@collabora.co.uk> Date: Thu, 5 Jul 2018 12:04:48 +0100 Subject: [PATCH] Replace aa_log_extract_tokens.pl The script aa_log_extract_tokens.pl expects the format of the logs from audit. As we don't have audit in the majority of our images and would like to extract this information from the journal, remove this script and replace with a shell function. Signed-off-by: Martyn Welch <martyn.welch@collabora.co.uk> --- aa_log_extract_tokens.pl | 228 --------------------------------------- common-apparmor.sh | 43 ++++++++ run-aa-test | 41 ++----- 3 files changed, 54 insertions(+), 258 deletions(-) delete mode 100755 aa_log_extract_tokens.pl create mode 100644 common-apparmor.sh diff --git a/aa_log_extract_tokens.pl b/aa_log_extract_tokens.pl deleted file mode 100755 index bca2451..0000000 --- a/aa_log_extract_tokens.pl +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env perl -# vim: set sts=4 sw=4 et tw=80 : -# -# Copyright (c) 2006 Novell, Inc. All Rights Reserved. -# Copyright (c) 2010 Canonical, Ltd. -# Copyright (c) 2012-2015 Collabora 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 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. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. - -# Example script to extract tokens from apparmor events in audit.log -# We use auditd because otherwise messages are lost if they come too quickly -# http://wiki.apparmor.net/index.php/AppArmorMonitoring#Realtime_information -# -# -# Example complaint event: -# type=AVC msg=audit(1344659645.579:153): apparmor="ALLOWED" operation="open" -# parent=15215 profile="/home/chaiwala/bin/*" name="/etc/group" pid=15216 -# comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 -# -# Example audit event: -# type=AVC msg=audit(1344735566.384:827): apparmor="AUDIT" operation="getattr" -# parent=16769 profile="/home/chaiwala/bin/*" name="/etc/localtime" pid=16770 -# comm="ls" requested_mask="r" fsuid=1000 ouid=0 -# - -use strict; -use warnings; - -use LibAppArmor qw(); - -use constant DEBUG => $ENV{AA_LOG_DEBUG}; - -my $event; -my $record; -my $value; -my $profile_wanted; -my @event_modes; -my @skip_names; -my %records_order; - -sub usage { - print("Usage: $0 <event mode> [event mode]...\n"); - print("And pipe logs to it\n"); - print("Valid values for [event mode] are: PERMITTING, REJECTING, AUDITING\n"); -} - -# Ensure there are some arguments -if ($#ARGV < 0) { - usage(); - exit(1); -} - -# Ensure that the arguments are all valid -foreach (@ARGV) { - SWITCH: { - if (/PERMITTING/) { last SWITCH; } - if (/REJECTING/) { last SWITCH; } - if (/AUDITING/) { last SWITCH; } - usage(); - exit(1); - } -} - -# Valid values we'll use: PERMITTING, REJECTING, AUDITING -# Valid values that we're not interested in: STATUS, ERROR, UNKNOWN, HINT -# PERMITTING is emitted when: -# * AppArmor is in "normal" audit mode[1] -# * A profile loaded in complain mode matches something -# REJECTING is emitted when: -# * AppArmor is in "normal" audit mode[1] -# * A profile loaded in enforce mode matches something -# AUDITING is emitted when: -# * AppArmor is in "all" audit mode[1] -# -# 1. http://wiki.apparmor.net/index.php/AppArmor_Failures#Module_audit_settings -@event_modes = @ARGV; - -# We're being lazy and getting arguments from the environment instead of doing -# getopt parsing -$profile_wanted = $ENV{'AA_PROFILE_WANTED'}; - -# Regex of file paths to skip accesses to -@skip_names=( - "/etc/ld\.so\.cache", - "/lib/i386-linux-gnu/libc.*\.so", - "/dev/pts/.*", - "/dev/(null|zero|random|urandom)", -); - -# Arbitrary order, but we want to keep it constant. -# chaiwala-apparmor-tests rely on the following order. -%records_order = ( - 'profile' => 1, - 'sdmode' => 2, - 'denied_mask' => 3, - 'operation' => 4, - 'name' => 5, - 'request_mask' => 6, - ); - -sub records_sort { - if (exists $records_order{$a} && exists $records_order{$b}) - { - return ($records_order{$a}) <=> ($records_order{$b}); - } - - if (! exists $records_order{$a} && !exists $records_order{$b}) - { - return $a cmp $b; - } - - return exists $records_order{$a} ? -1 : 1; -} - -# Simplified from Immunix::AppArmor. -sub stringify_sdmode { - my $sdmode = shift; - - return 'ERROR' if $sdmode == 1; - return 'AUDITING' if $sdmode == 2; - return 'PERMITTING' if $sdmode == 3; - return 'REJECTING' if $sdmode == 4; - return 'HINT' if $sdmode == 5; - return 'STATUS' if $sdmode == 6; - - # no idea - output *something* - return $sdmode; -} - -# Simplified from Immunix::AppArmor. We're not using its parse_event -# because that only works properly for events whose mask is something -# like r or w, not for events whose mask is more like receive or send; -# this version is more "raw", and hopefully a little more robust against -# AA changes. -sub parse_event { - my %ev = (); - my $msg = shift; - chomp($msg); - my $event = LibAppArmor::parse_record($msg); - - $ev{resource} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{active_hat} = LibAppArmor::aa_log_record::swig_active_hat_get($event); - $ev{sdmode} = LibAppArmor::aa_log_record::swig_event_get($event); - $ev{time} = LibAppArmor::aa_log_record::swig_epoch_get($event); - $ev{operation} = LibAppArmor::aa_log_record::swig_operation_get($event); - $ev{profile} = LibAppArmor::aa_log_record::swig_profile_get($event); - $ev{name} = LibAppArmor::aa_log_record::swig_name_get($event); - $ev{name2} = LibAppArmor::aa_log_record::swig_name2_get($event); - $ev{attr} = LibAppArmor::aa_log_record::swig_attribute_get($event); - $ev{parent} = LibAppArmor::aa_log_record::swig_parent_get($event); - $ev{pid} = LibAppArmor::aa_log_record::swig_pid_get($event); - $ev{task} = LibAppArmor::aa_log_record::swig_task_get($event); - $ev{info} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{denied_mask} = LibAppArmor::aa_log_record::swig_denied_mask_get($event); - $ev{request_mask} = LibAppArmor::aa_log_record::swig_requested_mask_get($event); - $ev{magic_token} = LibAppArmor::aa_log_record::swig_magic_token_get($event); - $ev{family} = LibAppArmor::aa_log_record::swig_net_family_get($event); - $ev{protocol} = LibAppArmor::aa_log_record::swig_net_protocol_get($event); - $ev{sock_type} = LibAppArmor::aa_log_record::swig_net_sock_type_get($event); - - LibAppArmor::free_record($event); - - if (! $ev{sdmode}) { - return undef; - } - - $ev{sdmode} = stringify_sdmode($ev{sdmode}); - - if (DEBUG) { - print STDERR "/----\n"; - foreach my $k (sort keys %ev) { - if (defined $ev{$k}) { - print STDERR "\t", $k, "\t", $ev{$k}, "\n"; - } - } - print STDERR "\\----\n"; - } - - return \%ev; -} - -EVENT: while (<STDIN>) { - # This doesn't give us all the tokens, unfortunately. - # Only gives us: profile, sdmode, time, denied_mask, pid, operation, - # parent (parent pid), name (filename), request_mask - # (FIXME: it could now give more if we want them) - $event = parse_event($_); - - if (!$event || - !(grep { $_ eq $$event{'sdmode'} } @event_modes) || - ($profile_wanted && ($$event{'profile'} ne $profile_wanted))) - { - next EVENT; - } - - foreach (@skip_names) { - if (defined $$event{'name'} && $$event{'name'} =~ /$_/) { - next EVENT; - } - } - - print "====\n"; - # This is pretty much it. Go forth and use this hash in whatever way. - foreach $record (sort records_sort keys %$event) { - $value = $$event{$record}; - next unless $value; - # Skip records that keep changing with every invocation - SWITCH: { - if ($record eq "time") { last SWITCH; } - if ($record eq "pid") { last SWITCH; } - if ($record eq "parent") { last SWITCH; } - print "$record:$value\n"; - } - } -} diff --git a/common-apparmor.sh b/common-apparmor.sh new file mode 100644 index 0000000..f6a9497 --- /dev/null +++ b/common-apparmor.sh @@ -0,0 +1,43 @@ +# Need to source this file. + +# Pass in audit string, returns formatted block as `aa_log_extract_tokens.pl` +# would have provided. Need to return: +# +# profile:/usr/bin/busctl +# sdmode:REJECTING +# denied_mask:r +# operation:open +# name:/proc/879/stat +# request_mask:r +# +# Note: apparmor uses the term "DENIED" rather than "REJECTING", but our +# expected values want "REJECTING", so tweak for now. +apparmor_parse_journal() { + awk -v event=$2 ' + { + for (i = 1; i <= NF; i++) { + n = index($i, "="); + if(n) { + vars[substr($i, 1, n - 1)] = substr($i, n + 1) + # Strip quotes + gsub("\"","",vars[substr($i, 1, n - 1)]) + } + } + } + { + if (vars["apparmor"] == event) { + print "====" + print "profile:"vars["profile"] + # Match old nomenclature + if (vars["apparmor"] == "DENIED") { + vars["apparmor"] = "REJECTING" + } + print "sdmode:"vars["apparmor"] + print "denied_mask:"vars["denied_mask"] + print "operation:"vars["operation"] + print "name:"vars["name"] + print "request_mask:"vars["requested_mask"] + } + } + ' $1 +} diff --git a/run-aa-test b/run-aa-test index 5939940..ad6ec39 100755 --- a/run-aa-test +++ b/run-aa-test @@ -8,6 +8,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +. common/common-apparmor.sh + ALTERNATIVE_SEPARATOR="## alternative ##" END=2 @@ -52,7 +54,6 @@ if [ ! -x $1 ]; then exit 1 fi -SOMETHING_FAILED="False" # typically "normal.expected" or "malicious.expected" TEST_TITLE=$( basename ${EXPECT_FILE} ) @@ -112,7 +113,8 @@ else echo "# ${CMDLINE} exited ${RET}" # typically "normal.expected_underlying_tests: fail" echo "${TEST_TITLE}_underlying_tests: fail" - SOMETHING_FAILED="True" + + exit 1 fi # Give journal time to log the entries. @@ -124,11 +126,11 @@ journalctl -S "${START_TIME}" -t audit -o cat > ${AUDIT_FILE} echo "#=== ${TEST_TITLE} ===" -echo "#---8<--- raw output in audit log" +echo "#---8<--- raw apparmor output from journal" cat ${AUDIT_FILE} | sed 's/^/# /' echo "#--->8---" -echo "#---8<--- expected output from aa_log_extract_tokens.pl" +echo "#---8<--- expected parsed apparmor output from journal" cat ${EXPECT_FILE} | sed 's/^/# /' echo "#--->8---" @@ -141,29 +143,12 @@ done PARSE_FILE="${TMP_DIR}/PARSE" -RET=$( cat ${AUDIT_FILE} | common/aa_log_extract_tokens.pl REJECTING > ${PARSE_FILE} ) - -if [ "${RET}" != "0" ]; then - echo "# aa_log_extract_tokens.pl failed, trying line-by-line..." - - LINES=$(wc -l ${AUDIT_FILE} | cut -d ' ' -f1 ) - - cat ${AUDIT_FILE} | while read LINE; do - echo ${LINE} | common/aa_log_extract_tokens.pl REJECTING 2>${TMP_DIR}/STDERR > ${TMP_DIR}/STDOUT - RET=$? - cat ${TMP_DIR}/STDOUT >> ${TMP_DIR}/ERRPARSE - - cat ${TMP_DIR}/STDERR | sed 's/^/E: /' >> ${TMP_DIR}/ERRPARSE +# TODO: There is potential for other processes to cause messages to appear in +# the journal that may lead to false failures. If this is found to be an +# issue in practice, then additional filtering of results may be required +apparmor_parse_journal ${AUDIT_FILE} DENIED > ${PARSE_FILE} - if [ "$RET" != "0" ]; then - echo -n "^ original line: ${LINE}" >> ${TMP_DIR}/ERRPARSE - fi - done - - mv ${TMP_DIR}/ERRPARSE ${PARSE_FILE} -fi - -echo "#---8<--- actual output from aa_log_extract_tokens.pl" +echo "#---8<--- actual parsed apparmor output from journal" cat ${PARSE_FILE} | sed 's/^/# /' echo "#--->8---" @@ -189,10 +174,6 @@ else diff -urN ${TMP_DIR}/EXPECT${NUM} ${PARSE_FILE} echo "#--->8---" echo "${TEST_TITLE}: fail" - SOMETHING_FAILED="True" -fi - -if [ "${SOMETHING_FAILED}" = "True" ]; then exit 1 fi -- GitLab