mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 00:14:44 +01:00
Add aa-logprof test framework
... and a simple test for a single (fake) event for ping. Notes: - to let aa-logprof work in the CI environment, we need to skip checking for the AppArmor mountpoint. Introduce --no-check-mountpoint for this. - PYTHONPATH and LD_LIBRARY_PATH need to be explicitely forwarded when starting aa-logprof via subprocess.Popen() - if the test runs with coverage enabled, it will also start aa-logprof with coverage (parameters copied from Makefile). Speaking about coverage - this test adds 4% overall coverage, and 10% more coverage for apparmor/aa.py.
This commit is contained in:
parent
313366fbbc
commit
46debcc493
7 changed files with 172 additions and 2 deletions
|
@ -29,6 +29,7 @@ parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
|
|||
parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start processing after'))
|
||||
parser.add_argument('-j', '--json', action='store_true', help=_('Input and Output in JSON'))
|
||||
parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS)
|
||||
parser.add_argument('--no-check-mountpoint', action='store_true', help=argparse.SUPPRESS)
|
||||
args = parser.parse_args()
|
||||
|
||||
logmark = args.mark or ''
|
||||
|
@ -41,7 +42,7 @@ if args.json:
|
|||
apparmor.set_logfile(args.file)
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
if not aa_mountpoint:
|
||||
if not aa_mountpoint and not args.no_check_mountpoint:
|
||||
raise AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.'))
|
||||
|
||||
apparmor.loadincludes()
|
||||
|
|
|
@ -21,7 +21,7 @@ COMMONDIR=../../common/
|
|||
include $(COMMONDIR)/Make.rules
|
||||
|
||||
# files that don't have 100% test coverage
|
||||
INCOMPLETE_COVERAGE=libraries/libapparmor/swig/python/.*/LibAppArmor/LibAppArmor.py|utils/apparmor/aa.py|utils/apparmor/common.py|utils/apparmor/config.py|utils/apparmor/easyprof.py|utils/apparmor/fail.py|utils/apparmor/logparser.py|utils/apparmor/profile_storage.py|utils/apparmor/rules.py|utils/apparmor/ui.py|minitools_test.py
|
||||
INCOMPLETE_COVERAGE=libraries/libapparmor/swig/python/.*/LibAppArmor/LibAppArmor.py|utils/aa-logprof|utils/apparmor/aa.py|utils/apparmor/common.py|utils/apparmor/config.py|utils/apparmor/easyprof.py|utils/apparmor/fail.py|utils/apparmor/logparser.py|utils/apparmor/profile_storage.py|utils/apparmor/rules.py|utils/apparmor/ui.py|minitools_test.py
|
||||
|
||||
|
||||
ifdef USE_SYSTEM
|
||||
|
|
3
utils/test/logprof/ping.auditlog
Normal file
3
utils/test/logprof/ping.auditlog
Normal file
|
@ -0,0 +1,3 @@
|
|||
type=AVC msg=audit(1691930856.284:29963): apparmor="DENIED" operation="open" class="file" profile="ping" name="/proc/21622/cmdline" pid=9136 comm="cat" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000
|
||||
type=SYSCALL msg=audit(1691930856.284:29963): arch=c000003e syscall=257 success=no exit=-13 a0=ffffff9c a1=7ffc4539abf8 a2=0 a3=0 items=0 ppid=21622 pid=9136 auid=1000 uid=1000 gid=100 euid=1000 suid=1000 fsuid=1000 egid=100 sgid=100 fsgid=100 tty=pts4 ses=2 comm="cat" exe="/usr/bin/cat" subj=ping key=(null)
|
||||
type=AVC msg=audit(1691930881.661:29975): apparmor="STATUS" operation="profile_replace" profile="apparmor_parser" name="ping" pid=10005 comm="apparmor_parser"
|
33
utils/test/logprof/ping.bin.ping
Normal file
33
utils/test/logprof/ping.bin.ping
Normal file
|
@ -0,0 +1,33 @@
|
|||
abi <abi/4.0>,
|
||||
|
||||
include <tunables/global>
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2009 Novell/SUSE
|
||||
# Copyright (C) 2010 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
|
||||
# License published by the Free Software Foundation.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
profile ping /{usr/,}bin/{,iputils-}ping {
|
||||
include <abstractions/base>
|
||||
include <abstractions/consoles>
|
||||
include <abstractions/nameservice>
|
||||
include if exists <local/bin.ping>
|
||||
|
||||
capability net_raw,
|
||||
capability setuid,
|
||||
|
||||
network inet raw,
|
||||
network inet6 raw,
|
||||
|
||||
/etc/modules.conf r,
|
||||
/proc/21622/cmdline r,
|
||||
/{,usr/}bin/{,iputils-}ping mrix,
|
||||
|
||||
}
|
13
utils/test/logprof/ping.jsonlog
Normal file
13
utils/test/logprof/ping.jsonlog
Normal file
|
@ -0,0 +1,13 @@
|
|||
o {"dialog": "apparmor-json-version","data": "2.12"}
|
||||
o {"dialog": "info","data": "Updating AppArmor profiles in /etc/apparmor.d."}
|
||||
o {"dialog": "info","data": "Reading log entries from /var/log/audit/audit.log."}
|
||||
o {"dialog": "info","data": "Complain-mode changes:"}
|
||||
o {"dialog": "info","data": "Enforce-mode changes:"}
|
||||
o {"dialog": "promptuser","title": null,"headers": {"Profile": "ping","Path": "/proc/21622/cmdline","New Mode": "owner r","Severity": 6},"explanation": null,"options": ["owner /proc/*/cmdline r,","owner /proc/21622/cmdline r,"],"menu_items": ["(A)llow","[(D)eny]","(I)gnore","(G)lob","Glob with (E)xtension","(N)ew","Audi(t)","(O)wner permissions off","Abo(r)t","(F)inish"],"default_key": "d"}
|
||||
i {"dialog":"promptuser","selected":0,"response_key":"o"}
|
||||
o {"dialog": "promptuser","title": null,"headers": {"Profile": "ping","Path": "/proc/21622/cmdline","New Mode": "r","Severity": 6},"explanation": null,"options": ["/proc/*/cmdline r,","/proc/21622/cmdline r,"],"menu_items": ["(A)llow","[(D)eny]","(I)gnore","(G)lob","Glob with (E)xtension","(N)ew","Audi(t)","(O)wner permissions on","Abo(r)t","(F)inish"],"default_key": "d"}
|
||||
i {"dialog":"promptuser","selected":1,"response_key":"a"}
|
||||
o {"dialog": "info","data": "Adding /proc/21622/cmdline r, to profile."}
|
||||
o {"dialog": "promptuser","title": "Changed Local Profiles","headers": {},"explanation": "The following local profiles were changed. Would you like to save them?","options": ["ping"],"menu_items": ["(S)ave Changes","Save Selec(t)ed Profile","[(V)iew Changes]","View Changes b/w (C)lean profiles","Abo(r)t"],"default_key": "v"}
|
||||
i {"dialog":"promptuser","selected":0,"response_key":"t"}
|
||||
o {"dialog": "info","data": "Writing updated profile for ping."}
|
1
utils/test/severity.db
Symbolic link
1
utils/test/severity.db
Symbolic link
|
@ -0,0 +1 @@
|
|||
../severity.db
|
119
utils/test/test-logprof.py
Normal file
119
utils/test/test-logprof.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
#! /usr/bin/python3
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2023 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# 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 shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# import apparmor.aa as aa # see the setup_aa() call for details
|
||||
from common_test import AATest, read_file, setup_all_loops # , setup_aa
|
||||
|
||||
|
||||
class TestLogprof(AATest):
|
||||
# This test expects a set of files:
|
||||
# - logprof/TESTNAME.auditlog - audit.log
|
||||
# - logprof/TESTNAME.jsonlog - expected aa-logprof --json input and output (gathered with json_log=1 in logprof.conf)
|
||||
# - logprof/TESTNAME.PROFILE - one or more profiles in the expected state
|
||||
# where TESTNAME is the name given in the first column of 'tests'
|
||||
tests = (
|
||||
# test name # profiles to verify
|
||||
('ping', ['bin.ping']),
|
||||
)
|
||||
|
||||
def AASetup(self):
|
||||
self.createTmpdir()
|
||||
|
||||
# copy the local profiles to the test directory
|
||||
self.profile_dir = self.tmpdir + '/profiles'
|
||||
shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True)
|
||||
|
||||
def AATeardown(self):
|
||||
self._terminate()
|
||||
|
||||
def _startLogprof(self, auditlog):
|
||||
exe = [sys.executable]
|
||||
|
||||
if 'coverage' in sys.modules:
|
||||
exe = exe + ['-m', 'coverage', 'run', '--branch', '-p']
|
||||
|
||||
exe = exe + ['../aa-logprof', '--json', '--configdir', './', '-f', auditlog, '-d', self.profile_dir, '--no-check-mountpoint']
|
||||
|
||||
process = subprocess.Popen(
|
||||
exe,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
# stderr=subprocess.STDOUT,
|
||||
env={'LANG': 'C',
|
||||
'PYTHONPATH': os.environ.get('PYTHONPATH', ''),
|
||||
'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''),
|
||||
},
|
||||
)
|
||||
|
||||
return process
|
||||
|
||||
def _terminate(self):
|
||||
self.process.stdin.close()
|
||||
self.process.stdout.close()
|
||||
self.process.terminate()
|
||||
self.process.wait(timeout=0.2)
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
auditlog = './logprof/%s.auditlog' % params
|
||||
jsonlog = './logprof/%s.jsonlog' % params
|
||||
|
||||
jlog = read_file(jsonlog)
|
||||
jlog = jlog.replace('/etc/apparmor.d', self.profile_dir)
|
||||
jlog = jlog.replace('/var/log/audit/audit.log', auditlog)
|
||||
jlog = jlog.strip().split('\n')
|
||||
|
||||
self.process = self._startLogprof(auditlog)
|
||||
|
||||
for line in jlog:
|
||||
if line.startswith('o '): # read from stdout
|
||||
output = self.process.stdout.readline().decode("utf-8").strip()
|
||||
self.assertEqual(output, line[2:])
|
||||
|
||||
elif line.startswith('i '): # send to stdin
|
||||
# expect an empty prompt line
|
||||
output = self.process.stdout.readline().decode("utf-8").strip()
|
||||
self.assertEqual(output, '')
|
||||
|
||||
# "type" answer
|
||||
self.process.stdin.write(line[2:].encode("utf-8") + b"\n")
|
||||
self.process.stdin.flush()
|
||||
|
||||
else:
|
||||
raise Exception('Unknown line in json log %s: %s' % (jsonlog, line))
|
||||
|
||||
# give logprof some time to write the updated profile and terminate
|
||||
self.process.wait(timeout=0.2)
|
||||
self.assertEqual(self.process.returncode, 0)
|
||||
|
||||
for file in expected:
|
||||
exp = read_file('./logprof/%s.%s' % (params, file))
|
||||
actual = read_file(os.path.join(self.profile_dir, file))
|
||||
|
||||
# remove '# Last Modified:' line from updated profile
|
||||
actual = actual.split('\n')
|
||||
if actual[0].startswith('# Last Modified:'):
|
||||
actual = actual[1:]
|
||||
actual = '\n'.join(actual)
|
||||
|
||||
self.assertEqual(actual, exp)
|
||||
|
||||
|
||||
# if you import apparmor.aa and call init_aa() in your tests, uncomment this
|
||||
# setup_aa(aa)
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=1)
|
Loading…
Add table
Reference in a new issue