diff --git a/utils/test/Makefile b/utils/test/Makefile index aca76428d..fde4332ff 100644 --- a/utils/test/Makefile +++ b/utils/test/Makefile @@ -30,6 +30,7 @@ else # PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))") LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/ + PERL5LIB=../../libraries/libapparmor/swig/perl/blib/lib/:../../libraries/libapparmor/swig/perl/:../../libraries/libapparmor/swig/perl/blib/arch/auto/:../../libraries/libapparmor/swig/perl/blib/arch/auto/LibAppArmor/ PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH) CONFDIR=$(CURDIR) BASEDIR=../../profiles/apparmor.d @@ -78,7 +79,7 @@ clean: rm -rf __pycache__/ .coverage htmlcov check: __libapparmor __parser - export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test))) + export PYTHONPATH=$(PYTHONPATH) PERL5LIB=$(PERL5LIB) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test))) .coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor __parser export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); ) @@ -91,4 +92,3 @@ coverage-report: .coverage coverage-html: .coverage $(PYTHON) -m coverage html --omit="$(COVERAGE_OMIT)" $(HTML_COVR_ARGS) - diff --git a/utils/test/test-aa-notify.py b/utils/test/test-aa-notify.py new file mode 100644 index 000000000..da70282b3 --- /dev/null +++ b/utils/test/test-aa-notify.py @@ -0,0 +1,271 @@ +#! /usr/bin/python3 +# ------------------------------------------------------------------ +# +# Copyright (C) 2011-2012 Canonical Ltd. +# Copyright (C) 2019 Otto Kekäläinen +# +# 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 os +import signal +import subprocess +import tempfile +import unittest +import time + +# The location of the aa-notify utility can be overridden by setting +# the APPARMOR_NOTIFY environment variable; this is useful for running +# these tests in an installed environment +aanotify_bin = "../aa-notify" + +# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html +# This is needed so that the subprocesses that produce endless output +# actually quit when the reader goes away. +def subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what + # non-Python subprocesses expect. + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + +def cmd(command): + '''Try to execute given command (array) and return its stdout, or return + a textual error if it failed.''' + + try: + sp = subprocess.Popen( + command, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + preexec_fn=subprocess_setup + ) + except OSError as e: + return [127, str(e)] + + stdout, stderr = sp.communicate(input) + + # If there was some error output, show that instead of stdout to ensure + # test fails and does not mask potentially major warnings and errors. + if stderr: + out = stderr + else: + out = stdout + + return [sp.returncode, out.decode('utf-8')] + + +class AANotifyTest(unittest.TestCase): + + def setUp(self): + '''Create temporary log file with 30 enties of different age''' + + test_logfile_contents_999_days_old = \ +'''Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834382] audit: type=1400 audit({epoch}:113): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/bin/uname" pid=4097 comm="sh" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/bin/uname" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834888] audit: type=1400 audit({epoch}:114): apparmor="ALLOWED" operation="file_inherit" profile="libreoffice-soffice//null-/bin/uname" name="/dev/null" pid=4097 comm="uname" requested_mask="w" denied_mask="w" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834890] audit: type=1400 audit({epoch}:115): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/bin/uname" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835136] audit: type=1400 audit({epoch}:116): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/ld-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835377] audit: type=1400 audit({epoch}:117): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/etc/ld.so.cache" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835405] audit: type=1400 audit({epoch}:118): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835421] audit: type=1400 audit({epoch}:119): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835696] audit: type=1400 audit({epoch}:120): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/usr/lib/locale/locale-archive" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.875891] audit: type=1400 audit({epoch}:121): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/usr/bin/file" pid=4111 comm="soffice.bin" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/usr/bin/file" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoch}:122): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/usr/bin/file" name="/usr/bin/file" pid=4111 comm="file" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +'''.format(epoch=round(time.time(), 3) - 60*60*24*999) + + test_logfile_contents_30_days_old = \ +'''Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834382] audit: type=1400 audit({epoch}:113): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/bin/uname" pid=4097 comm="sh" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/bin/uname" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834888] audit: type=1400 audit({epoch}:114): apparmor="ALLOWED" operation="file_inherit" profile="libreoffice-soffice//null-/bin/uname" name="/dev/null" pid=4097 comm="uname" requested_mask="w" denied_mask="w" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834890] audit: type=1400 audit({epoch}:115): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/bin/uname" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835136] audit: type=1400 audit({epoch}:116): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/ld-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835377] audit: type=1400 audit({epoch}:117): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/etc/ld.so.cache" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835405] audit: type=1400 audit({epoch}:118): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835421] audit: type=1400 audit({epoch}:119): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835696] audit: type=1400 audit({epoch}:120): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/usr/lib/locale/locale-archive" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.875891] audit: type=1400 audit({epoch}:121): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/usr/bin/file" pid=4111 comm="soffice.bin" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/usr/bin/file" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoch}:122): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/usr/bin/file" name="/usr/bin/file" pid=4111 comm="file" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +'''.format(epoch=round(time.time(), 3) - 60*60*24*30) + + test_logfile_contents_unrelevant_entries = \ +'''Feb 1 19:35:44 XPS-13-9370 kernel: [99848.048761] audit: type=1400 audit(1549042544.968:72): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/snap/core/6350/usr/lib/snapd/snap-confine" pid=12871 comm="apparmor_parser" +Feb 2 00:40:09 XPS-13-9370 kernel: [103014.549071] audit: type=1400 audit(1549060809.600:89): apparmor="STATUS" operation="profile_load" profile="unconfined" name="docker-default" pid=17195 comm="apparmor_parser" +Feb 4 20:05:42 XPS-13-9370 kernel: [132557.202931] audit: type=1400 audit(1549303542.661:136): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap.atom.apm" pid=11306 comm="apparmor_parser" +''' + + test_logfile_contents_0_seconds_old = \ +'''Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834382] audit: type=1400 audit({epoch}:113): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/bin/uname" pid=4097 comm="sh" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/bin/uname" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834888] audit: type=1400 audit({epoch}:114): apparmor="ALLOWED" operation="file_inherit" profile="libreoffice-soffice//null-/bin/uname" name="/dev/null" pid=4097 comm="uname" requested_mask="w" denied_mask="w" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834890] audit: type=1400 audit({epoch}:115): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/bin/uname" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835136] audit: type=1400 audit({epoch}:116): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/ld-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835377] audit: type=1400 audit({epoch}:117): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/etc/ld.so.cache" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835405] audit: type=1400 audit({epoch}:118): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835421] audit: type=1400 audit({epoch}:119): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/bin/uname" name="/lib/x86_64-linux-gnu/libc-2.27.so" pid=4097 comm="uname" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.835696] audit: type=1400 audit({epoch}:120): apparmor="ALLOWED" operation="open" profile="libreoffice-soffice//null-/bin/uname" name="/usr/lib/locale/locale-archive" pid=4097 comm="uname" requested_mask="r" denied_mask="r" fsuid=1001 ouid=0 +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.875891] audit: type=1400 audit({epoch}:121): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/usr/bin/file" pid=4111 comm="soffice.bin" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/usr/bin/file" +Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoch}:122): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/usr/bin/file" name="/usr/bin/file" pid=4111 comm="file" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 +'''.format(epoch=round(time.time(), 3)) + + handle, self.test_logfile = tempfile.mkstemp(prefix='test-aa-notify-') + os.close(handle) + handle = open(self.test_logfile, "w+") + handle.write( + test_logfile_contents_999_days_old + + test_logfile_contents_30_days_old + + test_logfile_contents_unrelevant_entries + + test_logfile_contents_0_seconds_old + ) + handle.close() + + def tearDown(self): + '''Remove temporary log file after tests ended''' + + if self.test_logfile and os.path.exists(self.test_logfile): + os.remove(self.test_logfile) + + def test_no_arguments(self): + '''Test using no arguments at all''' + + expected_return_code = 1 + expected_output_has = 'USAGE: aa-notify' + + return_code, output = cmd([aanotify_bin]) + result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) + self.assertEqual(expected_return_code, return_code, result + output) + result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) + self.assertIn(expected_output_has, output, result + output) + + def test_help_contents(self): + '''Test output of help text''' + + expected_return_code = 0 + expected_output_is = \ +'''USAGE: aa-notify [OPTIONS] + +Display AppArmor notifications or messages for DENIED entries. + +OPTIONS: + -p, --poll poll AppArmor logs and display notifications + --display $DISPLAY set the DISPLAY environment variable to $DISPLAY + (might be needed if sudo resets $DISPLAY) + -f FILE, --file=FILE search FILE for AppArmor messages + -l, --since-last display stats since last login + -s NUM, --since-days=NUM show stats for last NUM days (can be used alone + or with -p) + -v, --verbose show messages with stats + -h, --help display this help + -u USER, --user=USER user to drop privileges to when not using sudo + -w NUM, --wait=NUM wait NUM seconds before displaying + notifications (with -p) +''' + + return_code, output = cmd([aanotify_bin, '--help']) + result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) + self.assertEqual(expected_return_code, return_code, result + output) + result = 'Got output "%s", expected "%s"\n' % (output, expected_output_is) + self.assertEqual(expected_output_is, output, result + output) + + def test_entries_since_100_days(self): + '''Test showing log entries since 100 days''' + + expected_return_code = 0 + expected_output_has = 'AppArmor denials: 20 (since' + + return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-s', '100']) + result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) + self.assertEqual(expected_return_code, return_code, result + output) + result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) + self.assertIn(expected_output_has, output, result + output) + + def test_entries_since_login(self): + '''Test showing log entries since last login''' + + expected_return_code = 0 + expected_output_has = 'AppArmor denials: 10 (since' + + return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l']) + result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) + self.assertEqual(expected_return_code, return_code, result + output) + result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) + self.assertIn(expected_output_has, output, result + output) + + def test_entries_since_login_verbose(self): + '''Test showing log entries since last login in verbose mode''' + + expected_return_code = 0 + expected_output_has = \ +'''Profile: libreoffice-soffice +Operation: exec +Name: /bin/uname +Denied: x +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: file_inherit +Name: /dev/null +Denied: w +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: file_mmap +Name: /bin/uname +Denied: rm +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: file_mmap +Name: /lib/x86_64-linux-gnu/ld-2.27.so +Denied: rm +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: open +Name: /etc/ld.so.cache +Denied: r +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: open +Name: /lib/x86_64-linux-gnu/libc-2.27.so +Denied: r +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: file_mmap +Name: /lib/x86_64-linux-gnu/libc-2.27.so +Denied: rm +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/bin/uname +Operation: open +Name: /usr/lib/locale/locale-archive +Denied: r +Logfile: {logfile} + +Profile: libreoffice-soffice +Operation: exec +Name: /usr/bin/file +Denied: x +Logfile: {logfile} + +Profile: libreoffice-soffice//null-/usr/bin/file +Operation: file_mmap +Name: /usr/bin/file +Denied: rm +Logfile: {logfile} + +AppArmor denials: 10 (since'''.format(logfile=self.test_logfile) + + return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l', '-v']) + result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) + self.assertEqual(expected_return_code, return_code, result + output) + result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) + self.assertIn(expected_output_has, output, result + output) + +if __name__ == '__main__': + if 'APPARMOR_NOTIFY' in os.environ: + aanotify_bin = os.environ['APPARMOR_NOTIFY'] + unittest.main(verbosity=1)