mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-29 13:28:19 +00:00
Add tests for aa-show-usage
Add new tests for aa-show-usage and regex.py, that is internally used by aa-show-usage Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
This commit is contained in:
parent
b850f19622
commit
229811de9a
116
utils/test/test-aa-show-usage.py
Normal file
116
utils/test/test-aa-show-usage.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#! /usr/bin/python3
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Copyright (C) 2025 Maxime Bélair <maxime.belair@canonical.com>
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
import unittest
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import apparmor.aa as aa
|
||||||
|
from apparmor.common import cmd
|
||||||
|
from common_test import AATest, setup_aa, setup_all_loops
|
||||||
|
|
||||||
|
|
||||||
|
class AAShowUsageTest(AATest):
|
||||||
|
|
||||||
|
def test_help_contents(self):
|
||||||
|
"""Test output of help text"""
|
||||||
|
|
||||||
|
expected_return_code = 0
|
||||||
|
|
||||||
|
expected_output_1 = \
|
||||||
|
'''usage: aa-show-usage [-h] [-s {all,used,unused}] [-j] [-d DIR]
|
||||||
|
[--filter.flags FLAGS] [--filter.profile_name PROFILE_NAME]
|
||||||
|
[--filter.profile_attach PROFILE_ATTACH]
|
||||||
|
[--filter.profile_path PROFILE_PATH]
|
||||||
|
|
||||||
|
Check which profiles are used
|
||||||
|
''' # noqa: E128
|
||||||
|
|
||||||
|
expected_output_2 = \
|
||||||
|
'''
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-s, --show-type {all,used,unused}
|
||||||
|
Type of profiles to show
|
||||||
|
-j, --json Output in JSON
|
||||||
|
-d, --dir DIR Path to profiles
|
||||||
|
|
||||||
|
Filtering options:
|
||||||
|
Filters are used to reduce the output of information to only those entries
|
||||||
|
that will match the filter. Filters use Python's regular expression syntax.
|
||||||
|
|
||||||
|
--filter.flags FLAGS Filter by flags
|
||||||
|
--filter.profile_name PROFILE_NAME
|
||||||
|
Filter by profile name
|
||||||
|
--filter.profile_attach PROFILE_ATTACH
|
||||||
|
Filter by profile attachment
|
||||||
|
--filter.profile_path PROFILE_PATH
|
||||||
|
Filter by profile path
|
||||||
|
''' # noqa: E128
|
||||||
|
|
||||||
|
if sys.version_info[:2] < (3, 13):
|
||||||
|
# Python 3.13 tweaked argparse output [1]. When running on older
|
||||||
|
# Python versions, we adapt the expected output to match.
|
||||||
|
#
|
||||||
|
# https://github.com/python/cpython/pull/103372
|
||||||
|
patches = [(
|
||||||
|
'-s, --show-type {all,used,unused}',
|
||||||
|
'-s {all,used,unused}, --show-type {all,used,unused}',
|
||||||
|
), (
|
||||||
|
'-d, --dir DIR Path to profiles',
|
||||||
|
'-d DIR, --dir DIR Path to profiles'
|
||||||
|
)]
|
||||||
|
for patch in patches:
|
||||||
|
expected_output_2 = expected_output_2.replace(patch[0], patch[1])
|
||||||
|
|
||||||
|
return_code, output = cmd([aashowusage_bin, '--help'])
|
||||||
|
result = 'Got return code {}, expected {}\n'.format(return_code, expected_return_code)
|
||||||
|
self.assertEqual(expected_return_code, return_code, result + output)
|
||||||
|
|
||||||
|
self.assertIn(expected_output_1, output)
|
||||||
|
self.assertIn(expected_output_2, output)
|
||||||
|
|
||||||
|
def test_show_unconfined_profiles(self):
|
||||||
|
expected_return_code = 0
|
||||||
|
return_code, output = cmd([aashowusage_bin, '--filter.flags=unconfined', '-d', aa.profile_dir])
|
||||||
|
result = 'Got return code {}, expected {}\n'.format(return_code, expected_return_code)
|
||||||
|
self.assertEqual(expected_return_code, return_code, result + output)
|
||||||
|
|
||||||
|
nb_profile = 0
|
||||||
|
|
||||||
|
for line in output.splitlines():
|
||||||
|
if line.startswith(' Profile '):
|
||||||
|
nb_profile += 1
|
||||||
|
|
||||||
|
command = ['grep', '-Er', r'flags=.*unconfined.*\{', '--', aa.profile_dir]
|
||||||
|
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, check=False)
|
||||||
|
self.assertEqual(
|
||||||
|
len(result.stdout.splitlines()), nb_profile,
|
||||||
|
"Error found {} profiles, expected {}\n\n Output was: \n {}. Grepped profiles are: {}".format(
|
||||||
|
nb_profile, len(result.stdout.splitlines()), output, result.stdout)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
setup_aa(aa) # Wrapper for aa.init_aa()
|
||||||
|
setup_all_loops(__name__)
|
||||||
|
|
||||||
|
# The location of the aa-show-usage utility can be overridden by setting
|
||||||
|
# the APPARMOR_SHOW_USAGE or USE_SYSTEM environment variable;
|
||||||
|
# this is useful for running these tests in an installed environment
|
||||||
|
aashowusage_bin = "../aa-show-usage"
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if 'APPARMOR_SHOW_USAGE' in os.environ:
|
||||||
|
aashowusage_bin = os.environ['APPARMOR_SHOW_USAGE']
|
||||||
|
elif 'USE_SYSTEM' in os.environ:
|
||||||
|
aashowusage_bin = 'aa-show-usage'
|
||||||
|
|
||||||
|
unittest.main(verbosity=1)
|
@ -17,7 +17,8 @@ from apparmor.regex import (
|
|||||||
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
|
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
|
||||||
RE_PROFILE_START, parse_profile_start_line, re_match_include, RE_PROFILE_UNIX,
|
RE_PROFILE_START, parse_profile_start_line, re_match_include, RE_PROFILE_UNIX,
|
||||||
RE_PROFILE_PIVOT_ROOT,
|
RE_PROFILE_PIVOT_ROOT,
|
||||||
re_match_include_parse, strip_parenthesis, strip_quotes)
|
re_match_include_parse, strip_parenthesis, strip_quotes, resolve_variables, expand_braces,
|
||||||
|
expand_var, expand_string)
|
||||||
from common_test import AATest, setup_aa, setup_all_loops
|
from common_test import AATest, setup_aa, setup_all_loops
|
||||||
|
|
||||||
|
|
||||||
@ -724,6 +725,138 @@ class TestStripQuotes(AATest):
|
|||||||
self.assertEqual(strip_quotes(params), expected)
|
self.assertEqual(strip_quotes(params), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpandBraces(AATest):
|
||||||
|
tests = (
|
||||||
|
('foo', ['foo']),
|
||||||
|
('/{,foo}', ['/', '/foo']),
|
||||||
|
('/{,foo,bar}', ['/', '/foo', '/bar']),
|
||||||
|
('/{bin,sbin}/runc', ['/bin/runc', '/sbin/runc']),
|
||||||
|
('/{,usr/}{,s}bin/runc', ['/bin/runc', '/sbin/runc', '/usr/bin/runc', '/usr/sbin/runc']),
|
||||||
|
('/{,usr/{,s}}bin/runc', ['/bin/runc', '/usr/bin/runc', '/usr/sbin/runc']),
|
||||||
|
('{{a,b},{c,{d,e}},f}', ['a', 'b', 'c', 'd', 'e', 'f']),
|
||||||
|
('{,b}{d,e}', ['d', 'e', 'bd', 'be']),
|
||||||
|
('{aa,b}{d,e}', ['aad', 'aae', 'bd', 'be']),
|
||||||
|
('{{foo,bar},{baz,qux}}', ['foo', 'bar', 'baz', 'qux']),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
self.assertEqual(expand_braces(params), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInvalidExpandBraces(AATest):
|
||||||
|
tests = (
|
||||||
|
# Malformed expressions
|
||||||
|
('/{', AppArmorException),
|
||||||
|
('/{}}{', AppArmorException),
|
||||||
|
('/}', AppArmorException),
|
||||||
|
('/{foo,bar}}', AppArmorException),
|
||||||
|
('/{a,b},{c,d}}', AppArmorException),
|
||||||
|
# Braces should always provide at least 2 alternatives
|
||||||
|
('{foo}', AppArmorException),
|
||||||
|
('/{foo}/', AppArmorException),
|
||||||
|
('/{,foo}{bar}', AppArmorException),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
expand_braces(params)
|
||||||
|
|
||||||
|
|
||||||
|
var_dict = {
|
||||||
|
# Normal variables
|
||||||
|
'@{a}': ['AAA'],
|
||||||
|
'@{b}': ['BBB'],
|
||||||
|
# Multiple values variables
|
||||||
|
'@{c}': ['CC', 'CCC'],
|
||||||
|
'@{d}': ['DD', 'DDD', 'DDDD'],
|
||||||
|
# Variable relying on other variables
|
||||||
|
'@{e}': ['@{a}'],
|
||||||
|
'@{f}': ['@{e}@{b}', '@{c}'],
|
||||||
|
'@{g}': ['/bin/@{f}/{foo,bar}'],
|
||||||
|
# Invalid variables
|
||||||
|
'@{h}': ['@{h}'], # Self reference
|
||||||
|
'@{i}': ['@{j}'], # Circular reference with i and j
|
||||||
|
'@{j}': ['@{i}'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolveVariables(AATest):
|
||||||
|
tests = (
|
||||||
|
('@{a}', ['AAA']),
|
||||||
|
('/@{a}/@{b}/', ['/AAA/BBB/']),
|
||||||
|
('/@{a}/@{c}/', ['/AAA/CC/', '/AAA/CCC/']),
|
||||||
|
('/@{a}/@{d}/', ['/AAA/DD/', '/AAA/DDD/', '/AAA/DDDD/']),
|
||||||
|
('/@{c}/@{d}/', ['/CC/DD/', '/CC/DDD/', '/CC/DDDD/', '/CCC/DD/', '/CCC/DDD/', '/CCC/DDDD/']),
|
||||||
|
('/@{e}/', ['/AAA/']),
|
||||||
|
('/@{f}/', ['/AAABBB/', '/CC/', '/CCC/']),
|
||||||
|
('@{g}', ['/bin/AAABBB/{foo,bar}', '/bin/CC/{foo,bar}', '/bin/CCC/{foo,bar}']),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
self.assertEqual(resolve_variables(params, var_dict), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInvalidResolveVariables(AATest):
|
||||||
|
tests = (
|
||||||
|
('@{h}', AppArmorException),
|
||||||
|
('@{i}', AppArmorException),
|
||||||
|
('@{z}', AppArmorException), # @{z} doesn't exist
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
resolve_variables(params, var_dict)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpandVar(AATest):
|
||||||
|
tests = (
|
||||||
|
(('@{a}', set()), ['AAA']),
|
||||||
|
(('@{d}', set()), ['DD', 'DDD', 'DDDD']),
|
||||||
|
(('@{f}', set()), ['AAABBB', 'CC', 'CCC']),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
var, seen_vars = params
|
||||||
|
self.assertEqual(expand_var(var, var_dict, seen_vars), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInvalidExpandVar(AATest):
|
||||||
|
tests = (
|
||||||
|
(('@{h}', set()), AppArmorException), # Circular dependency
|
||||||
|
(('@{a}', {'@{a}'}), AppArmorException), # Circular dependency
|
||||||
|
(('@{z}', set()), AppArmorException), # Invalid variable (not in var_dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
var, seen_vars = params
|
||||||
|
expand_var(var, var_dict, seen_vars)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpandString(AATest):
|
||||||
|
tests = (
|
||||||
|
('@{g}/@{a}', ['/bin/AAABBB/{foo,bar}/AAA', '/bin/CC/{foo,bar}/AAA', '/bin/CCC/{foo,bar}/AAA']),
|
||||||
|
('@{g}/@{c}', ['/bin/AAABBB/{foo,bar}/CC', '/bin/AAABBB/{foo,bar}/CCC', '/bin/CC/{foo,bar}/CC', '/bin/CC/{foo,bar}/CCC', '/bin/CCC/{foo,bar}/CC', '/bin/CCC/{foo,bar}/CCC']),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
self.assertEqual(expand_string(params, var_dict, set()), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInvalidExpandString(AATest):
|
||||||
|
tests = (
|
||||||
|
(('@{h}', set()), AppArmorException), # Circular dependency
|
||||||
|
(('@{a}', {'@{a}'}), AppArmorException), # Circular dependency
|
||||||
|
(('@{z}', set()), AppArmorException), # Invalid variable (not in var_dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
var, seen_vars = params
|
||||||
|
expand_string(var, var_dict, seen_vars)
|
||||||
|
|
||||||
|
|
||||||
setup_aa(aa)
|
setup_aa(aa)
|
||||||
setup_all_loops(__name__)
|
setup_all_loops(__name__)
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user