2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 10:07:12 +00:00

aa-notify: Allow writing to local profiles

The new option --local allows user to write new rules to local profiles
instead of system profiles, enabling cleaner profile deployment.

This option support the values (yes, no and auto)

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
This commit is contained in:
Maxime Bélair 2025-08-08 15:47:19 +02:00
parent 4c30a0ac65
commit df1a4c8782
3 changed files with 63 additions and 18 deletions

View File

@ -612,7 +612,8 @@ def add_to_profile(rule, profile_name):
return return
update_profile_path = update_profile.__file__ update_profile_path = update_profile.__file__
command = ['pkexec', '--keep-cwd', update_profile_path, 'add_rule', rule, profile_name]
command = ['pkexec', '--keep-cwd', update_profile_path, 'add_rule', args.local, rule, profile_name]
try: try:
subprocess.run(command, check=True) subprocess.run(command, check=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@ -647,7 +648,7 @@ def allow_rules(clean_rules, allow_all=False):
with open(tmp.name, mode='w') as f: with open(tmp.name, mode='w') as f:
for profile_name, profile_rules in clean_rules.items(): for profile_name, profile_rules in clean_rules.items():
written += f.write(profile_rules.get_writable_rules(template_path)) written += f.write(profile_rules.get_writable_rules(template_path, args.local))
if written > 0: if written > 0:
create_from_file(tmp.name) create_from_file(tmp.name)
@ -849,6 +850,7 @@ def main():
parser.add_argument('-w', '--wait', type=int, metavar=('NUM'), help=_('wait NUM seconds before displaying notifications (with -p)')) parser.add_argument('-w', '--wait', type=int, metavar=('NUM'), help=_('wait NUM seconds before displaying notifications (with -p)'))
parser.add_argument('-m', '--merge-notifications', action='store_true', help=_('Merge notification for improved readability (with -p)')) parser.add_argument('-m', '--merge-notifications', action='store_true', help=_('Merge notification for improved readability (with -p)'))
parser.add_argument('-F', '--foreground', action='store_true', help=_('Do not fork to the background')) parser.add_argument('-F', '--foreground', action='store_true', help=_('Do not fork to the background'))
parser.add_argument('-L', '--local', nargs='?', const='yes', default='auto', choices=['yes', 'no', 'auto'], help=_('Add to local profile'))
parser.add_argument('--prompt-filter', type=str, metavar=('PF'), help=_('kind of operations which display a popup prompt')) parser.add_argument('--prompt-filter', type=str, metavar=('PF'), help=_('kind of operations which display a popup prompt'))
parser.add_argument('--debug', action='store_true', help=_('debug mode')) parser.add_argument('--debug', action='store_true', help=_('debug mode'))
parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS)
@ -938,6 +940,7 @@ def main():
- prompt_filter - prompt_filter
- maximum_number_notification_profiles - maximum_number_notification_profiles
- keys_to_aggregate - keys_to_aggregate
- use_local_profiles
- filter.profile, - filter.profile,
- filter.operation, - filter.operation,
- filter.name, - filter.name,
@ -960,6 +963,7 @@ def main():
'message_footer', 'message_footer',
'maximum_number_notification_profiles', 'maximum_number_notification_profiles',
'keys_to_aggregate', 'keys_to_aggregate',
'use_local_profiles',
'filter.profile', 'filter.profile',
'filter.operation', 'filter.operation',
'filter.name', 'filter.name',
@ -1068,6 +1072,15 @@ def main():
keys_to_aggregate = config['']['keys_to_aggregate'].strip().split(',') keys_to_aggregate = config['']['keys_to_aggregate'].strip().split(',')
else: else:
keys_to_aggregate = {'operation', 'class', 'name', 'denied', 'target'} keys_to_aggregate = {'operation', 'class', 'name', 'denied', 'target'}
if 'use_local_profiles' in config['']:
if config['']['use_local_profiles'] in {'auto', 'yes', 'no'}:
args.local = config['']['use_local_profiles']
elif config['']['use_local_profiles'] is None:
args.local = 'yes'
else:
sys.exit(_('ERROR: using an invalid value for use_local_profiles in config {}\nSupported values: {}').format(
config['']['use_local_profiles'], ', '.join({'yes', 'auto', 'no'})
))
if args.file: if args.file:
logfile = args.file logfile = args.file

View File

@ -100,12 +100,12 @@ class ProfileRules:
for raw_rule in raw_rules: for raw_rule in raw_rules:
self.rules.append(SelectableRule(raw_rule, self.selectable)) self.rules.append(SelectableRule(raw_rule, self.selectable))
def get_writable_rules(self, template_path, allow_all=False): def get_writable_rules(self, template_path, local='yes', allow_all=False):
out = '' out = ''
for rule in self.rules: for rule in self.rules:
if allow_all or rule.selected.get(): if allow_all or rule.selected.get():
if not self.is_userns_profile: if not self.is_userns_profile:
out += 'add_rule\t{}\t{}\n'.format(rule.rule, self.profile_name) out += 'add_rule\t{}\t{}\t{}\n'.format(local, rule.rule, self.profile_name)
else: else:
out += 'create_userns\t{}\t{}\t{}\t{}\t{}\n'.format(template_path, self.profile_name, self.bin_path, self.profile_path, 'allow') out += 'create_userns\t{}\t{}\t{}\t{}\t{}\n'.format(template_path, self.profile_name, self.bin_path, self.profile_path, 'allow')
return out return out

View File

@ -8,8 +8,19 @@ from apparmor import aa
from apparmor.logparser import ReadLog from apparmor.logparser import ReadLog
from apparmor.translations import init_translation from apparmor.translations import init_translation
_ = init_translation() _ = init_translation()
is_aa_inited = False
def init_if_needed():
global is_aa_inited
if not is_aa_inited:
aa.init_aa()
aa.read_profiles()
is_aa_inited = True
def create_userns(template_path, name, bin_path, profile_path, decision): def create_userns(template_path, name, bin_path, profile_path, decision):
with open(template_path, 'r') as f: with open(template_path, 'r') as f:
@ -27,27 +38,48 @@ def create_userns(template_path, name, bin_path, profile_path, decision):
exit(_('Cannot reload updated profile')) exit(_('Cannot reload updated profile'))
def add_to_profile(rule, profile_name): def add_to_profile(rule_obj, profile_name):
aa.init_aa() aa.active_profiles[profile_name][rule_obj.rule_name].add(rule_obj, cleanup=True)
aa.update_profiles()
rule_type, rule_class = ReadLog('', '', '').get_rule_type(rule)
rule_obj = rule_class.create_instance(rule)
if not aa.active_profiles.profile_exists(profile_name):
exit(_('Cannot find {} in profiles').format(profile_name))
aa.active_profiles[profile_name][rule_type].add(rule_obj, cleanup=True)
# Save changes # Save changes
aa.write_profile_ui_feedback(profile_name) aa.write_profile_ui_feedback(profile_name)
def add_to_local_profile(rule_obj, profile_name):
inc_file = aa.create_local_profile_if_needed(profile_name, cleanup=True)
aa.include[inc_file][inc_file].data[rule_obj.rule_name].add(rule_obj)
aa.write_include_ui_feedback(aa.include[inc_file][inc_file], inc_file)
def add_rule(mode, rule, profile_name):
init_if_needed()
if not aa.active_profiles.profile_exists(profile_name):
exit(_('Cannot find {} in profiles').format(profile_name))
rule_type, rule_class = ReadLog('', '', '').get_rule_type(rule)
rule_obj = rule_class.create_instance(rule)
if mode == 'yes':
add_to_local_profile(rule_obj, profile_name)
elif mode == 'no':
add_to_profile(rule_obj, profile_name)
elif mode == 'auto':
if aa.get_local_include(profile_name):
add_to_local_profile(rule_obj, profile_name)
else:
add_to_profile(rule_obj, profile_name)
else:
usage(False)
aa.reload_base(profile_name) aa.reload_base(profile_name)
def usage(is_help): def usage(is_help):
print('This tool is a low level tool - do not use it directly') print('This tool is a low level tool - do not use it directly')
print('{} create_userns <template_path> <name> <bin_path> <profile_path> <decision>'.format(sys.argv[0])) print('{} create_userns <template_path> <name> <bin_path> <profile_path> <decision>'.format(sys.argv[0]))
print('{} add_rule <rule> <profile_name>'.format(sys.argv[0])) print('{} add_rule <mode=yes|no|auto> <rule> <profile_name>'.format(sys.argv[0]))
print('{} from_file <file>'.format(sys.argv[0])) print('{} from_file <file>'.format(sys.argv[0]))
if is_help: if is_help:
exit(0) exit(0)
@ -76,9 +108,9 @@ def do_command(command, args):
usage(False) usage(False)
create_userns(args[1], args[2], args[3], args[4], args[5]) create_userns(args[1], args[2], args[3], args[4], args[5])
elif command == 'add_rule': elif command == 'add_rule':
if not len(args) == 3: if not len(args) == 4:
usage(False) usage(False)
add_to_profile(args[1], args[2]) add_rule(args[1], args[2], args[3])
elif command == 'help': elif command == 'help':
usage(True) usage(True)
else: else: