2019-02-03 23:01:30 +01:00
#! /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 time
2019-02-11 20:18:44 +02:00
import unittest
from common_test import AATest , setup_all_loops , setup_aa
import apparmor . aa as aa
2019-02-03 23:01:30 +01:00
# 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 ' ) ]
2019-02-11 20:18:44 +02:00
class AANotifyTest ( AATest ) :
2019-02-03 23:01:30 +01:00
2019-02-11 20:18:44 +02:00
def AASetup ( self ) :
2019-02-03 23:01:30 +01:00
''' 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 ( )
2019-02-11 20:18:44 +02:00
def AATeardown ( self ) :
2019-02-03 23:01:30 +01:00
''' Remove temporary log file after tests ended '''
if self . test_logfile and os . path . exists ( self . test_logfile ) :
os . remove ( self . test_logfile )
2019-01-09 23:59:40 +01:00
# The Perl aa-notify script was written so, that it will checked for kern.log
2019-02-09 13:24:53 +02:00
# before printing help when invoked without arguments (sic!).
@unittest.skipUnless ( os . path . isfile ( ' /var/log/kern.log ' ) , ' Requires kern.log on system ' )
2019-02-03 23:01:30 +01:00
def test_no_arguments ( self ) :
''' Test using no arguments at all '''
2019-01-09 23:59:40 +01:00
expected_return_code = 0
expected_output_has = ' usage: aa-notify '
2019-02-03 23:01:30 +01:00
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 = \
2019-01-09 23:59:40 +01:00
''' usage: aa-notify [-h] [-p] [--display DISPLAY] [-f FILE] [-l] [-s NUM] [-v]
[ - u USER ] [ - w NUM ] [ - - debug ]
2019-02-03 23:01:30 +01:00
Display AppArmor notifications or messages for DENIED entries .
2019-01-09 23:59:40 +01:00
optional arguments :
- h , - - help show this help message and exit
- p , - - poll poll AppArmor logs and display notifications
2019-04-19 23:12:32 +03:00
- - display DISPLAY set the DISPLAY environment variable ( might be needed if
sudo resets $ DISPLAY )
2019-01-09 23:59:40 +01:00
- f FILE , - - file FILE search FILE for AppArmor messages
- l , - - since - last display stats since last login
- s NUM , - - since - days NUM
2019-04-19 23:12:32 +03:00
show stats for last NUM days ( can be used alone or with
- p )
2019-01-09 23:59:40 +01:00
- v , - - verbose show messages with stats
- 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 )
- - debug debug mode
2019-02-03 23:01:30 +01:00
'''
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 ' ] )
2019-01-09 23:59:40 +01:00
if " ERROR: Could not find last login " in output :
2019-02-09 13:24:53 +02:00
self . skipTest ( ' Could not find last login ' )
2019-02-03 23:01:30 +01:00
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 )
2019-02-09 13:24:53 +02:00
@unittest.skipUnless ( os . path . isfile ( ' /var/log/wtmp ' ) , ' Requires wtmp on system ' )
2019-02-03 23:01:30 +01:00
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 ' ] )
2019-01-09 23:59:40 +01:00
if " ERROR: Could not find last login " in output :
2019-02-09 13:24:53 +02:00
self . skipTest ( ' Could not find last login ' )
2019-02-03 23:01:30 +01:00
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 )
2019-02-11 20:18:44 +02:00
setup_aa ( aa ) # Wrapper for aa.init_aa()
setup_all_loops ( __name__ )
2019-02-03 23:01:30 +01:00
if __name__ == ' __main__ ' :
if ' APPARMOR_NOTIFY ' in os . environ :
aanotify_bin = os . environ [ ' APPARMOR_NOTIFY ' ]
unittest . main ( verbosity = 1 )