mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 00:14:44 +01:00
Merge in Kshitij Gupta <kgupta8592@gmail.com>'s rewrite of the
logprof/genprof and related utilities in python. Because the branch that was worked on was not based on the apparmor tree, not all of the history can be maintained for files that are not newly created or entirely rewritten in the branch. (This merge also includes a subsequent commit to the branch I was merging from which includes my missed bzr add of utils/apparmor/translations.py)
This commit is contained in:
commit
f989dd0132
46 changed files with 9574 additions and 37 deletions
3
utils/README.md
Normal file
3
utils/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Known Bugs:
|
||||
Will allow multiple letters in the () due to translation/unicode issues with regexing the key.
|
||||
User input will probably bug out in a different locale.
|
40
utils/aa-audit
Normal file
40
utils/aa-audit
Normal file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('--trace', action='store_true', help=_('Show full trace'))
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
audit = apparmor.tools.aa_tools('audit', args)
|
||||
|
||||
audit.act()
|
||||
except Exception as e:
|
||||
if not args.trace:
|
||||
print(e.value + "\n")
|
||||
|
||||
else:
|
||||
traceback.print_exc()
|
|
@ -2,17 +2,30 @@
|
|||
|
||||
=head1 NAME
|
||||
|
||||
aa-audit - set a AppArmor security profile to I<audit> mode.
|
||||
aa-audit - set an AppArmor security profile to I<audit> mode.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-audit I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...]>
|
||||
B<aa-audit I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-r>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-r --remove>
|
||||
|
||||
Removes the audit mode for the profile.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-audit> is used to set the audit mode for one or more profiles to audit.
|
||||
B<aa-audit> is used to set one or more profiles to audit mode.
|
||||
In this mode security policy is enforced and all access (successes and failures) are logged to the system log.
|
||||
|
||||
The I<--remove> option can be used to remove the audit mode for the profile.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
If you find any bugs, please report them at
|
||||
|
|
31
utils/aa-autodep
Normal file
31
utils/aa-autodep
Normal file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements'))
|
||||
parser.add_argument('--force', type=str, help=_('overwrite existing profile'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
args = parser.parse_args()
|
||||
|
||||
autodep = apparmor.tools.aa_tools('autodep', args)
|
||||
|
||||
autodep.act()
|
|
@ -26,7 +26,18 @@ aa-autodep - guess basic AppArmor profile requirements
|
|||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-autodep I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...]>
|
||||
B<aa-autodep I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-f>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-f --force>
|
||||
|
||||
Overwrites any existing AppArmor profile for the executable with the generated minimal AppArmor profile.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
|
@ -37,6 +48,9 @@ a base profile containing a base include directive which includes basic
|
|||
profile entries needed by most programs. The profile is generated by
|
||||
recursively calling ldd(1) on the executables listed on the command line.
|
||||
|
||||
The I<--force> option will overwrite any existing profile for the executable with
|
||||
the newly generated minimal AppArmor profile.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
This program does not perform full static analysis of executables, so
|
||||
|
|
31
utils/aa-cleanprof
Normal file
31
utils/aa-cleanprof
Normal file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('-s', '--silent', action='store_true', help=_('Silently overwrite with a clean profile'))
|
||||
args = parser.parse_args()
|
||||
|
||||
clean = apparmor.tools.aa_tools('cleanprof', args)
|
||||
|
||||
clean.act()
|
39
utils/aa-cleanprof.pod
Normal file
39
utils/aa-cleanprof.pod
Normal file
|
@ -0,0 +1,39 @@
|
|||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
aa-cleanprof - clean an existing AppArmor security profile.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-cleanprof I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-s>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-s --silent>
|
||||
|
||||
Silently overwrites the profile without user prompt.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-cleanprof> is used to perform a cleanup on one or more profiles.
|
||||
The tool removes any existing superfluous rules (rules that are covered
|
||||
under an include or another rule), reorders the rules to group similar rules
|
||||
together and removes all comments from the file.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
If you find any bugs, please report them at
|
||||
L<https://bugs.launchpad.net/apparmor/+filebug>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1),
|
||||
aa_change_hat(2), and L<http://wiki.apparmor.net>.
|
||||
|
||||
=cut
|
31
utils/aa-complain
Normal file
31
utils/aa-complain
Normal file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-r', '--remove', action='store_true', help=_('remove complain mode'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
args = parser.parse_args()
|
||||
|
||||
complain = apparmor.tools.aa_tools('complain', args)
|
||||
#print(args)
|
||||
complain.act()
|
|
@ -22,17 +22,31 @@
|
|||
|
||||
=head1 NAME
|
||||
|
||||
aa-complain - set a AppArmor security profile to I<complain> mode.
|
||||
aa-complain - set an AppArmor security profile to I<complain> mode.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-complain I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...]>
|
||||
B<aa-complain I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-r>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-r --remove>
|
||||
|
||||
Removes the complain mode for the profile.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-complain> is used to set the enforcement mode for one or more profiles to
|
||||
complain. In this mode security policy is not enforced but rather access
|
||||
violations are logged to the system log.
|
||||
B<aa-complain> is used to set the enforcement mode for one or more profiles to I<complain> mode.
|
||||
In this mode security policy is not enforced but rather access violations
|
||||
are logged to the system log.
|
||||
|
||||
The I<--remove> option can be used to remove the complain mode for the profile,
|
||||
setting it to enforce mode by default.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
|
|
32
utils/aa-disable
Normal file
32
utils/aa-disable
Normal file
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-r', '--revert', action='store_true', help=_('enable the profile for the given programs'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
args = parser.parse_args()
|
||||
|
||||
disable = apparmor.tools.aa_tools('disable', args)
|
||||
|
||||
disable.act()
|
||||
|
|
@ -26,15 +26,28 @@ aa-disable - disable an AppArmor security profile
|
|||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-disable I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...]>
|
||||
B<aa-disable I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-r>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-r --revert>
|
||||
|
||||
Enables the profile and loads it.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-disable> is used to disable the enforcement mode for one or more
|
||||
profiles. This command will unload the profile from the kernel and
|
||||
prevent the profile from being loaded on AppArmor startup. The
|
||||
I<aa-enforce> and I<aa-complain> utilities may be used to to change this
|
||||
behavior.
|
||||
B<aa-disable> is used to I<disable> one or more profiles.
|
||||
This command will unload the profile from the kernel and prevent the
|
||||
profile from being loaded on AppArmor startup.
|
||||
The I<aa-enforce> and I<aa-complain> utilities may be used to to change
|
||||
this behavior.
|
||||
|
||||
The I<--revert> option can be used to enable the profile.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
|
|
33
utils/aa-enforce
Normal file
33
utils/aa-enforce
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
|
||||
import apparmor.tools
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-r', '--remove', action='store_true', help=_('switch to complain mode'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
args = parser.parse_args()
|
||||
# Flipping the remove flag since complain = !enforce
|
||||
args.remove = not args.remove
|
||||
|
||||
enforce = apparmor.tools.aa_tools('complain', args)
|
||||
|
||||
enforce.act()
|
|
@ -27,16 +27,30 @@ being disabled or I<complain> mode.
|
|||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-enforce I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...]>
|
||||
B<aa-enforce I<E<lt>executableE<gt>> [I<E<lt>executableE<gt>> ...] [I<-d /path/to/profiles>] [I<-r>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir / path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-r --remove>
|
||||
|
||||
Removes the enforce mode for the profile.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-enforce> is used to set the enforcement mode for one or more profiles
|
||||
to I<enforce>. This command is only relevant in conjunction with the
|
||||
I<aa-complain> utility which sets a profile to complain mode and the
|
||||
I<aa-disable> utility which unloads and disables a profile. The default
|
||||
mode for a security policy is enforce and the I<aa-complain> utility must
|
||||
be run to change this behavior.
|
||||
B<aa-enforce> is used to set one or more profiles to I<enforce> mode.
|
||||
This command is only relevant in conjunction with the I<aa-complain> utility
|
||||
which sets a profile to complain mode and the I<aa-disable> utility which
|
||||
unloads and disables a profile.
|
||||
The default mode for a security policy is enforce and the I<aa-complain>
|
||||
utility must be run to change this behavior.
|
||||
|
||||
The I<--remove> option can be used to remove the enforce mode for the profile,
|
||||
setting it to complain mode.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
|
|
164
utils/aa-genprof
Normal file
164
utils/aa-genprof
Normal file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
import atexit
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
def sysctl_read(path):
|
||||
value = None
|
||||
with open(path, 'r') as f_in:
|
||||
value = int(f_in.readline())
|
||||
return value
|
||||
|
||||
def sysctl_write(path, value):
|
||||
if not value:
|
||||
return
|
||||
with open(path, 'w') as f_out:
|
||||
f_out.write(str(value))
|
||||
|
||||
def last_audit_entry_time():
|
||||
out = subprocess.check_output(['tail', '-1', '/var/log/audit/audit.log'], shell=True)
|
||||
logmark = None
|
||||
if re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out):
|
||||
logmark = re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out).groups()[0]
|
||||
else:
|
||||
logmark = ''
|
||||
return logmark
|
||||
|
||||
def restore_ratelimit():
|
||||
sysctl_write(ratelimit_sysctl, ratelimit_saved)
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Generate profile for the given program'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
|
||||
parser.add_argument('program', type=str, help=_('name of program to profile'))
|
||||
args = parser.parse_args()
|
||||
|
||||
profiling = args.program
|
||||
profiledir = args.dir
|
||||
filename = args.file
|
||||
|
||||
|
||||
if filename:
|
||||
if not os.path.isfile(filename):
|
||||
raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename)
|
||||
else:
|
||||
apparmor.filename = filename
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
if not aa_mountpoint:
|
||||
raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.'))
|
||||
|
||||
if profiledir:
|
||||
apparmor.profile_dir = apparmor.get_full_path(profiledir)
|
||||
if not os.path.isdir(apparmor.profile_dir):
|
||||
raise apparmor.AppArmorException(_("%s is not a directory.") %profiledir)
|
||||
|
||||
program = None
|
||||
#if os.path.exists(apparmor.which(profiling.strip())):
|
||||
if os.path.exists(profiling):
|
||||
program = apparmor.get_full_path(profiling)
|
||||
else:
|
||||
if '/' not in profiling:
|
||||
which = apparmor.which(profiling)
|
||||
if which:
|
||||
program = apparmor.get_full_path(which)
|
||||
|
||||
if not program or not os.path.exists(program):
|
||||
if '/' not in profiling:
|
||||
raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") %(profiling, profiling))
|
||||
else:
|
||||
raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling)
|
||||
|
||||
# Check if the program has been marked as not allowed to have a profile
|
||||
apparmor.check_qualifiers(program)
|
||||
|
||||
apparmor.loadincludes()
|
||||
|
||||
profile_filename = apparmor.get_profile_filename(program)
|
||||
if os.path.exists(profile_filename):
|
||||
apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename, program)
|
||||
else:
|
||||
apparmor.autodep(program)
|
||||
apparmor.helpers[program] = 'enforce'
|
||||
|
||||
if apparmor.helpers[program] == 'enforce':
|
||||
apparmor.complain(program)
|
||||
apparmor.reload(program)
|
||||
|
||||
# When reading from syslog, it is possible to hit the default kernel
|
||||
# printk ratelimit. This will result in audit entries getting skipped,
|
||||
# making profile generation inaccurate. When using genprof, disable
|
||||
# the printk ratelimit, and restore it on exit.
|
||||
ratelimit_sysctl = '/proc/sys/kernel/printk_ratelimit'
|
||||
ratelimit_saved = sysctl_read(ratelimit_sysctl)
|
||||
sysctl_write(ratelimit_sysctl, 0)
|
||||
|
||||
atexit.register(restore_ratelimit)
|
||||
|
||||
apparmor.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles')
|
||||
|
||||
apparmor.UI_Important(_('Please start the application to be profiled in\nanother window and exercise its functionality now.\n\nOnce completed, select the "Scan" option below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.'))
|
||||
|
||||
syslog = True
|
||||
logmark = ''
|
||||
done_profiling = False
|
||||
|
||||
if os.path.exists('/var/log/audit/audit.log'):
|
||||
syslog = False
|
||||
|
||||
passno = 0
|
||||
while not done_profiling:
|
||||
if syslog:
|
||||
logmark = subprocess.check_output(['date | md5sum'], shell=True)
|
||||
logmark = logmark.decode('ascii').strip()
|
||||
logmark = re.search('^([0-9a-f]+)', logmark).groups()[0]
|
||||
t=subprocess.call("%s -p kern.warn 'GenProf: %s'"%(apparmor.logger, logmark), shell=True)
|
||||
|
||||
else:
|
||||
logmark = last_audit_entry_time()
|
||||
|
||||
q=apparmor.hasher()
|
||||
q['headers'] = [_('Profiling'), program]
|
||||
q['functions'] = ['CMD_SCAN', 'CMD_FINISHED']
|
||||
q['default'] = 'CMD_SCAN'
|
||||
ans, arg = apparmor.UI_PromptUser(q, 'noexit')
|
||||
|
||||
if ans == 'CMD_SCAN':
|
||||
lp_ret = apparmor.do_logprof_pass(logmark, passno)
|
||||
passno += 1
|
||||
if lp_ret == 'FINISHED':
|
||||
done_profiling = True
|
||||
else:
|
||||
done_profiling = True
|
||||
|
||||
for p in sorted(apparmor.helpers.keys()):
|
||||
if apparmor.helpers[p] == 'enforce':
|
||||
enforce(p)
|
||||
reload(p)
|
||||
|
||||
apparmor.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.'))
|
||||
apparmor.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles\n')
|
||||
apparmor.UI_Info(_('Finished generating profile for %s.')%program)
|
||||
sys.exit(0)
|
|
@ -26,7 +26,7 @@ aa-genprof - profile generation utility for AppArmor
|
|||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-genprof I<E<lt>executableE<gt>> [I<-d /path/to/profiles>]>
|
||||
B<aa-genprof I<E<lt>executableE<gt>> [I<-d /path/to/profiles>] [I<-f /path/to/logfile>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
|
@ -35,6 +35,14 @@ B<-d --dir /path/to/profiles>
|
|||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-f --file /path/to/logfile>
|
||||
|
||||
Specifies the location of logfile.
|
||||
Default locations are read from F</etc/apparmor/logprof.conf>.
|
||||
Typical defaults are:
|
||||
/var/log/audit/audit.log
|
||||
/var/log/syslog
|
||||
/var/log/messages
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
|
@ -64,7 +72,7 @@ using aa-logprof(1).
|
|||
After the user finishes selecting profile entries based on violations
|
||||
that were detected during the program execution, aa-genprof will reload
|
||||
the updated profiles in complain mode and again prompt the user for (S)can and
|
||||
(D)one. This cycle can then be repeated as necessary until all application
|
||||
(F)inish. This cycle can then be repeated as necessary until all application
|
||||
functionality has been exercised without generating access violations.
|
||||
|
||||
When the user eventually hits (F)inish, aa-genprof will set the main profile,
|
||||
|
|
53
utils/aa-logprof
Normal file
53
utils/aa-logprof
Normal file
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
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'))
|
||||
args = parser.parse_args()
|
||||
|
||||
profiledir = args.dir
|
||||
filename = args.file
|
||||
logmark = args.mark or ''
|
||||
|
||||
|
||||
if filename:
|
||||
if not os.path.isfile(filename):
|
||||
raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename)
|
||||
else:
|
||||
apparmor.filename = filename
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
if not aa_mountpoint:
|
||||
raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.'))
|
||||
|
||||
if profiledir:
|
||||
apparmor.profile_dir = apparmor.get_full_path(profiledir)
|
||||
if not os.path.isdir(apparmor.profile_dir):
|
||||
raise apparmor.AppArmorException("%s is not a directory."%profiledir)
|
||||
|
||||
apparmor.loadincludes()
|
||||
|
||||
apparmor.do_logprof_pass(logmark)
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
=head1 NAME
|
||||
|
||||
aa-logprof - utility program for managing AppArmor security profiles
|
||||
aa-logprof - utility for updating AppArmor security profiles
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
|
@ -32,12 +32,17 @@ B<aa-logprof [I<-d /path/to/profiles>] [I<-f /path/to/logfile>] [I<-m E<lt>mark
|
|||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
The path to where the AppArmor security profiles are stored
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
B<-f --file /path/to/logfile>
|
||||
|
||||
The path to the location of the logfile that contains AppArmor
|
||||
security events.
|
||||
Specifies the location of logfile that contains AppArmor security events.
|
||||
Default locations are read from F</etc/apparmor/logprof.conf>.
|
||||
Typical defaults are:
|
||||
/var/log/audit/audit.log
|
||||
/var/log/syslog
|
||||
/var/log/messages
|
||||
|
||||
B< -m --logmark "mark">
|
||||
|
||||
|
@ -47,9 +52,8 @@ B< -m --logmark "mark">
|
|||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-logprof> is an interactive tool used to review AppArmor's
|
||||
complain mode output and generate new entries for AppArmor security
|
||||
profiles.
|
||||
B<aa-logprof> is an interactive tool used to review AppArmor generated
|
||||
messages and update AppArmor security profiles.
|
||||
|
||||
Running aa-logprof will scan the log file and if there are new AppArmor
|
||||
events that are not covered by the existing profile set, the user will
|
||||
|
@ -71,11 +75,17 @@ The user is then presented with info about the access including profile,
|
|||
path, old mode if there was a previous entry in the profile for this path,
|
||||
new mode, the suggestion list, and given these options:
|
||||
|
||||
(A)llow, (D)eny, (N)ew, (G)lob last piece, (Q)uit
|
||||
(A)llow, (D)eny, (I)gnore, (N)ew, (G)lob last piece, (Q)uit
|
||||
|
||||
If the AppArmor profile was in complain mode when the event was generated,
|
||||
the default for this option is (A)llow, otherwise, it's (D)eny.
|
||||
|
||||
The (D)eny option adds a "deny" rule to the AppArmor profile, which
|
||||
silences logging.
|
||||
|
||||
The (I)gnore option allows user to ignore the event, without making any
|
||||
changes to the AppArmor profile.
|
||||
|
||||
The suggestion list is presented as a numbered list with includes
|
||||
at the top, the literal path in the middle, and the suggested globs
|
||||
at the bottom. If any globs are being suggested, the shortest glob
|
||||
|
@ -109,9 +119,9 @@ Adding r access to /usr/share/themes/** would delete an entry for r
|
|||
access to /usr/share/themes/foo/*.gif if it exists in the profile.
|
||||
|
||||
If (Q)uit is selected at this point, aa-logprof will ignore all new pending
|
||||
capability and path accesses.
|
||||
accesses.
|
||||
|
||||
After all of the path accesses have been handled, logrof will write all
|
||||
After all of the accesses have been handled, logrof will write all
|
||||
updated profiles to the disk and reload them if AppArmor is running.
|
||||
|
||||
=head2 New Process (Execution) Events
|
||||
|
|
682
utils/aa-mergeprof
Normal file
682
utils/aa-mergeprof
Normal file
|
@ -0,0 +1,682 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import apparmor.aa
|
||||
import apparmor.aamode
|
||||
import apparmor.severity
|
||||
import apparmor.cleanprofile as cleanprofile
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles'))
|
||||
parser.add_argument('mine', type=str, help=_('your profile'))
|
||||
parser.add_argument('base', type=str, help=_('base profile'))
|
||||
parser.add_argument('other', type=str, help=_('other profile'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts'))
|
||||
args = parser.parse_args()
|
||||
|
||||
profiles = [args.mine, args.base, args.other]
|
||||
|
||||
def main():
|
||||
mergeprofiles = Merge(profiles)
|
||||
#Get rid of common/superfluous stuff
|
||||
mergeprofiles.clear_common()
|
||||
|
||||
if not args.auto:
|
||||
mergeprofiles.ask_the_questions('other')
|
||||
|
||||
mergeprofiles.clear_common()
|
||||
|
||||
mergeprofiles.ask_the_questions('base')
|
||||
|
||||
q = apparmor.aa.hasher()
|
||||
q['title'] = 'Changed Local Profiles'
|
||||
q['headers'] = []
|
||||
q['explanation'] = _('The following local profiles were changed. Would you like to save them?')
|
||||
q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT']
|
||||
q['default'] = 'CMD_VIEW_CHANGES'
|
||||
q['options'] = []
|
||||
q['selected'] = 0
|
||||
p =None
|
||||
ans = ''
|
||||
arg = None
|
||||
programs = list(mergeprofiles.user.aa.keys())
|
||||
program = programs[0]
|
||||
while ans != 'CMD_SAVE_CHANGES':
|
||||
ans, arg = apparmor.aa.UI_PromptUser(q)
|
||||
if ans == 'CMD_SAVE_CHANGES':
|
||||
apparmor.aa.write_profile_ui_feedback(program)
|
||||
apparmor.aa.reload_base(program)
|
||||
elif ans == 'CMD_VIEW_CHANGES':
|
||||
for program in programs:
|
||||
apparmor.aa.original_aa[program] = apparmor.aa.deepcopy(apparmor.aa.aa[program])
|
||||
#oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '')
|
||||
newprofile = apparmor.aa.serialize_profile(mergeprofiles.user.aa[program], program, '')
|
||||
apparmor.aa.display_changes_with_comments(mergeprofiles.user.filename, newprofile)
|
||||
|
||||
|
||||
class Merge(object):
|
||||
def __init__(self, profiles):
|
||||
user, base, other = profiles
|
||||
|
||||
#Read and parse base profile and save profile data, include data from it and reset them
|
||||
apparmor.aa.read_profile(base, True)
|
||||
self.base = cleanprofile.Prof(base)
|
||||
|
||||
self.reset()
|
||||
|
||||
#Read and parse other profile and save profile data, include data from it and reset them
|
||||
apparmor.aa.read_profile(other, True)
|
||||
self.other = cleanprofile.Prof(other)
|
||||
|
||||
self.reset()
|
||||
|
||||
#Read and parse user profile
|
||||
apparmor.aa.read_profile(profiles[0], True)
|
||||
self.user = cleanprofile.Prof(user)
|
||||
|
||||
def reset(self):
|
||||
apparmor.aa.aa = apparmor.aa.hasher()
|
||||
apparmor.aa.filelist = apparmor.aa.hasher()
|
||||
apparmor.aa.include = dict()
|
||||
apparmor.aa.existing_profiles = apparmor.aa.hasher()
|
||||
apparmor.aa.original_aa = apparmor.aa.hasher()
|
||||
|
||||
def clear_common(self):
|
||||
deleted = 0
|
||||
#Remove off the parts in other profile which are common/superfluous from user profile
|
||||
user_other = cleanprofile.CleanProf(False, self.user, self.other)
|
||||
deleted += user_other.compare_profiles()
|
||||
|
||||
#Remove off the parts in base profile which are common/superfluous from user profile
|
||||
user_base = cleanprofile.CleanProf(False, self.user, self.base)
|
||||
deleted += user_base.compare_profiles()
|
||||
|
||||
#Remove off the parts in other profile which are common/superfluous from base profile
|
||||
base_other = cleanprofile.CleanProf(False, self.base, self.other)
|
||||
deleted += user_base.compare_profiles()
|
||||
|
||||
def conflict_mode(self, profile, hat, allow, path, mode, new_mode, old_mode):
|
||||
m = new_mode
|
||||
o = old_mode
|
||||
new_mode = apparmor.aa.flatten_mode(new_mode)
|
||||
old_mode = apparmor.aa.flatten_mode(old_mode)
|
||||
conflict_modes = set('uUpPcCiIxX')
|
||||
conflict_x= (old_mode | new_mode) & conflict_modes
|
||||
if conflict_x:
|
||||
#We may have conflicting x modes
|
||||
if conflict_x & set('x'):
|
||||
conflict_x.remove('x')
|
||||
if conflict_x & set('X'):
|
||||
conflict_x.remove('X')
|
||||
if len(conflict_x) > 1:
|
||||
q = apparmor.aa.hasher()
|
||||
q['headers'] = [_('Path'), path]
|
||||
q['headers'] += [_('Select the appropriate mode'), '']
|
||||
options = []
|
||||
options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(new_mode)))# - (old_mode & conflict_x))))
|
||||
options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(old_mode)))#(old_mode | new_mode) - (new_mode & conflict_x))))
|
||||
q['options'] = options
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_ABORT']
|
||||
done = False
|
||||
while not done:
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
if ans == 'CMD_ALLOW':
|
||||
if selected == 0:
|
||||
self.user.aa[profile][hat][allow]['path'][path][mode] = m#apparmor.aa.owner_flatten_mode(new_mode)#(old_mode | new_mode) - (old_mode & conflict_x)
|
||||
return m
|
||||
elif selected == 1:
|
||||
return o
|
||||
pass#self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x)
|
||||
else:
|
||||
raise apparmor.aa.AppArmorException(_('Unknown selection'))
|
||||
done = True
|
||||
|
||||
def ask_the_questions(self, other):
|
||||
if other == 'other':
|
||||
other = self.other
|
||||
else:
|
||||
other = self.base
|
||||
#print(other.aa)
|
||||
|
||||
#Add the file-wide includes from the other profile to the user profile
|
||||
done = False
|
||||
options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.filelist[other.filename]['include'].keys())))
|
||||
q = apparmor.aa.hasher()
|
||||
q['options'] = options
|
||||
default_option = 1
|
||||
q['selected'] = default_option - 1
|
||||
q['headers'] = [_('File includes'), _('Select the ones you wish to add')]
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
q['default'] = 'CMD_ALLOW'
|
||||
while not done and options:
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
elif ans == 'CMD_ALLOW':
|
||||
selection = options[selected]
|
||||
inc = apparmor.aa.re_match_include(selection)
|
||||
self.user.filelist[self.user.filename]['include'][inc] = True
|
||||
options.pop(selected)
|
||||
apparmor.aa.UI_Info(_('Adding %s to the file.') % selection)
|
||||
|
||||
|
||||
sev_db = apparmor.aa.sev_db
|
||||
if not sev_db:
|
||||
sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
|
||||
for profile in sorted(other.aa.keys()):
|
||||
for hat in sorted(other.aa[profile].keys()):
|
||||
#Add the includes from the other profile to the user profile
|
||||
done = False
|
||||
options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.aa[profile][hat]['include'].keys())))
|
||||
q = apparmor.aa.hasher()
|
||||
q['options'] = options
|
||||
default_option = 1
|
||||
q['selected'] = default_option - 1
|
||||
q['headers'] = [_('File includes'), _('Select the ones you wish to add')]
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
q['default'] = 'CMD_ALLOW'
|
||||
while not done and options:
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
elif ans == 'CMD_ALLOW':
|
||||
selection = options[selected]
|
||||
inc = apparmor.aa.re_match_include(selection)
|
||||
deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc)
|
||||
self.user.aa[profile][hat]['include'][inc] = True
|
||||
options.pop(selected)
|
||||
apparmor.aa.UI_Info(_('Adding %s to the file.') % selection)
|
||||
if deleted:
|
||||
apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
#Add the capabilities
|
||||
for allow in ['allow', 'deny']:
|
||||
if other.aa[profile][hat].get(allow, False):
|
||||
continue
|
||||
for capability in sorted(other.aa[profile][hat][allow]['capability'].keys()):
|
||||
severity = sev_db.rank('CAP_%s' % capability)
|
||||
default_option = 1
|
||||
options = []
|
||||
newincludes = apparmor.aa.match_cap_includes(self.user.aa[profile][hat], capability)
|
||||
q = apparmor.aa.hasher()
|
||||
if newincludes:
|
||||
options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes))))
|
||||
|
||||
if options:
|
||||
options.append('capability %s' % capability)
|
||||
q['options'] = [options]
|
||||
q['selected'] = default_option - 1
|
||||
|
||||
q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)]
|
||||
q['headers'] += [_('Capability'), capability]
|
||||
q['headers'] += [_('Severity'), severity]
|
||||
|
||||
audit_toggle = 0
|
||||
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
|
||||
q['default'] = 'CMD_ALLOW'
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
# Ignore the log entry
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
break
|
||||
|
||||
if ans == 'CMD_ALLOW':
|
||||
selection = ''
|
||||
if options:
|
||||
selection = options[selected]
|
||||
match = apparmor.aa.re_match_include(selection)
|
||||
if match:
|
||||
deleted = False
|
||||
inc = match
|
||||
deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc)
|
||||
self.user.aa[profile][hat]['include'][inc] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Adding %s to profile.') % selection)
|
||||
if deleted:
|
||||
apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
self.user.aa[profile][hat]['allow']['capability'][capability]['set'] = True
|
||||
self.user.aa[profile][hat]['allow']['capability'][capability]['audit'] = other.aa[profile][hat]['allow']['capability'][capability]['audit']
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Adding capability %s to profile.'), capability)
|
||||
done = True
|
||||
|
||||
elif ans == 'CMD_DENY':
|
||||
self.user.aa[profile][hat]['deny']['capability'][capability]['set'] = True
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Denying capability %s to profile.') % capability)
|
||||
done = True
|
||||
else:
|
||||
done = False
|
||||
|
||||
# Process all the path entries.
|
||||
for allow in ['allow', 'deny']:
|
||||
for path in sorted(other.aa[profile][hat][allow]['path'].keys()):
|
||||
#print(path, other.aa[profile][hat][allow]['path'][path])
|
||||
mode = other.aa[profile][hat][allow]['path'][path]['mode']
|
||||
|
||||
if self.user.aa[profile][hat][allow]['path'].get(path, False):
|
||||
mode = self.conflict_mode(profile, hat, allow, path, 'mode', other.aa[profile][hat][allow]['path'][path]['mode'], self.user.aa[profile][hat][allow]['path'][path]['mode'])
|
||||
self.conflict_mode(profile, hat, allow, path, 'audit', other.aa[profile][hat][allow]['path'][path]['audit'], self.user.aa[profile][hat][allow]['path'][path]['audit'])
|
||||
apparmor.aa.changed[profile] = True
|
||||
continue
|
||||
# Lookup modes from profile
|
||||
allow_mode = set()
|
||||
allow_audit = set()
|
||||
deny_mode = set()
|
||||
deny_audit = set()
|
||||
|
||||
fmode, famode, fm = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'allow', path)
|
||||
if fmode:
|
||||
allow_mode |= fmode
|
||||
if famode:
|
||||
allow_audit |= famode
|
||||
|
||||
cm, cam, m = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'deny', path)
|
||||
if cm:
|
||||
deny_mode |= cm
|
||||
if cam:
|
||||
deny_audit |= cam
|
||||
|
||||
imode, iamode, im = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'allow', path)
|
||||
if imode:
|
||||
allow_mode |= imode
|
||||
if iamode:
|
||||
allow_audit |= iamode
|
||||
|
||||
cm, cam, m = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'deny', path)
|
||||
if cm:
|
||||
deny_mode |= cm
|
||||
if cam:
|
||||
deny_audit |= cam
|
||||
|
||||
if deny_mode & apparmor.aa.AA_MAY_EXEC:
|
||||
deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE
|
||||
|
||||
# Mask off the denied modes
|
||||
mode = mode - deny_mode
|
||||
|
||||
# If we get an exec request from some kindof event that generates 'PERMITTING X'
|
||||
# check if its already in allow_mode
|
||||
# if not add ix permission
|
||||
if mode & apparmor.aa.AA_MAY_EXEC:
|
||||
# Remove all type access permission
|
||||
mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE
|
||||
if not allow_mode & apparmor.aa.AA_MAY_EXEC:
|
||||
mode |= apparmor.aa.str_to_mode('ix')
|
||||
|
||||
# m is not implied by ix
|
||||
|
||||
### If we get an mmap request, check if we already have it in allow_mode
|
||||
##if mode & AA_EXEC_MMAP:
|
||||
## # ix implies m, so we don't need to add m if ix is present
|
||||
## if contains(allow_mode, 'ix'):
|
||||
## mode = mode - AA_EXEC_MMAP
|
||||
|
||||
if not mode:
|
||||
continue
|
||||
|
||||
matches = []
|
||||
|
||||
if fmode:
|
||||
matches += fm
|
||||
|
||||
if imode:
|
||||
matches += im
|
||||
|
||||
if not apparmor.aa.mode_contains(allow_mode, mode):
|
||||
default_option = 1
|
||||
options = []
|
||||
newincludes = []
|
||||
include_valid = False
|
||||
|
||||
for incname in apparmor.aa.include.keys():
|
||||
include_valid = False
|
||||
# If already present skip
|
||||
if self.user.aa[profile][hat][incname]:
|
||||
continue
|
||||
if incname.startswith(apparmor.aa.profile_dir):
|
||||
incname = incname.replace(apparmor.aa.profile_dir+'/', '', 1)
|
||||
|
||||
include_valid = apparmor.aa.valid_include('', incname)
|
||||
|
||||
if not include_valid:
|
||||
continue
|
||||
|
||||
cm, am, m = apparmor.aa.match_include_to_path(incname, 'allow', path)
|
||||
|
||||
if cm and apparmor.aa.mode_contains(cm, mode):
|
||||
dm = apparmor.aa.match_include_to_path(incname, 'deny', path)[0]
|
||||
# If the mode is denied
|
||||
if not mode & dm:
|
||||
if not list(filter(lambda s: '/**' == s, m)):
|
||||
newincludes.append(incname)
|
||||
# Add new includes to the options
|
||||
if newincludes:
|
||||
options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes))))
|
||||
# We should have literal the path in options list too
|
||||
options.append(path)
|
||||
# Add any the globs matching path from logprof
|
||||
globs = apparmor.aa.glob_common(path)
|
||||
if globs:
|
||||
matches += globs
|
||||
# Add any user entered matching globs
|
||||
for user_glob in apparmor.aa.user_globs:
|
||||
if apparmor.aa.matchliteral(user_glob, path):
|
||||
matches.append(user_glob)
|
||||
|
||||
matches = list(set(matches))
|
||||
if path in matches:
|
||||
matches.remove(path)
|
||||
|
||||
options += apparmor.aa.order_globs(matches, path)
|
||||
default_option = len(options)
|
||||
|
||||
sev_db.unload_variables()
|
||||
sev_db.load_variables(apparmor.aa.get_profile_filename(profile))
|
||||
severity = sev_db.rank(path, apparmor.aa.mode_to_str(mode))
|
||||
sev_db.unload_variables()
|
||||
|
||||
audit_toggle = 0
|
||||
owner_toggle = 0
|
||||
if apparmor.aa.cfg['settings']['default_owner_prompt']:
|
||||
owner_toggle = apparmor.aa.cfg['settings']['default_owner_prompt']
|
||||
done = False
|
||||
while not done:
|
||||
q = apparmor.aa.hasher()
|
||||
q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat),
|
||||
_('Path'), path]
|
||||
|
||||
if allow_mode:
|
||||
mode |= allow_mode
|
||||
tail = ''
|
||||
s = ''
|
||||
prompt_mode = None
|
||||
if owner_toggle == 0:
|
||||
prompt_mode = apparmor.aa.flatten_mode(mode)
|
||||
tail = ' ' + _('(owner permissions off)')
|
||||
elif owner_toggle == 1:
|
||||
prompt_mode = mode
|
||||
elif owner_toggle == 2:
|
||||
prompt_mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode)
|
||||
tail = ' ' + _('(force new perms to owner)')
|
||||
else:
|
||||
prompt_mode = apparmor.aa.owner_flatten_mode(mode)
|
||||
tail = ' ' + _('(force all rule perms to owner)')
|
||||
|
||||
if audit_toggle == 1:
|
||||
s = apparmor.aa.mode_to_str_user(allow_mode)
|
||||
if allow_mode:
|
||||
s += ', '
|
||||
s += 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode - allow_mode) + tail
|
||||
elif audit_toggle == 2:
|
||||
s = 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode) + tail
|
||||
else:
|
||||
s = apparmor.aa.mode_to_str_user(prompt_mode) + tail
|
||||
|
||||
q['headers'] += [_('Old Mode'), apparmor.aa.mode_to_str_user(allow_mode),
|
||||
_('New Mode'), s]
|
||||
|
||||
else:
|
||||
s = ''
|
||||
tail = ''
|
||||
prompt_mode = None
|
||||
if audit_toggle:
|
||||
s = 'audit'
|
||||
if owner_toggle == 0:
|
||||
prompt_mode = apparmor.aa.flatten_mode(mode)
|
||||
tail = ' ' + _('(owner permissions off)')
|
||||
elif owner_toggle == 1:
|
||||
prompt_mode = mode
|
||||
else:
|
||||
prompt_mode = apparmor.aa.owner_flatten_mode(mode)
|
||||
tail = ' ' + _('(force perms to owner)')
|
||||
|
||||
s = apparmor.aa.mode_to_str_user(prompt_mode)
|
||||
q['headers'] += [_('Mode'), s]
|
||||
|
||||
q['headers'] += [_('Severity'), severity]
|
||||
q['options'] = options
|
||||
q['selected'] = default_option - 1
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB',
|
||||
'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT',
|
||||
'CMD_FINISHED', 'CMD_OTHER']
|
||||
|
||||
q['default'] = 'CMD_ALLOW'
|
||||
|
||||
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
break
|
||||
|
||||
if ans == 'CMD_OTHER':
|
||||
audit_toggle, owner_toggle = apparmor.aa.UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode)
|
||||
elif ans == 'CMD_USER_TOGGLE':
|
||||
owner_toggle += 1
|
||||
if not allow_mode and owner_toggle == 2:
|
||||
owner_toggle += 1
|
||||
if owner_toggle > 3:
|
||||
owner_toggle = 0
|
||||
elif ans == 'CMD_ALLOW':
|
||||
path = options[selected]
|
||||
done = True
|
||||
match = apparmor.aa.re_match_include(path)
|
||||
if match:
|
||||
inc = match
|
||||
deleted = 0
|
||||
deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc)
|
||||
self.user.aa[profile][hat]['include'][inc] = True
|
||||
apparmor.aa.changed[profile] = True
|
||||
apparmor.aa.UI_Info(_('Adding %s to profile.') % path)
|
||||
if deleted:
|
||||
apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
else:
|
||||
if self.user.aa[profile][hat]['allow']['path'][path].get('mode', False):
|
||||
mode |= self.user.aa[profile][hat]['allow']['path'][path]['mode']
|
||||
deleted = []
|
||||
for entry in self.user.aa[profile][hat]['allow']['path'].keys():
|
||||
if path == entry:
|
||||
continue
|
||||
|
||||
if apparmor.aa.matchregexp(path, entry):
|
||||
if apparmor.aa.mode_contains(mode, self.user.aa[profile][hat]['allow']['path'][entry]['mode']):
|
||||
deleted.append(entry)
|
||||
for entry in deleted:
|
||||
self.user.aa[profile][hat]['allow']['path'].pop(entry)
|
||||
deleted = len(deleted)
|
||||
|
||||
if owner_toggle == 0:
|
||||
mode = apparmor.aa.flatten_mode(mode)
|
||||
#elif owner_toggle == 1:
|
||||
# mode = mode
|
||||
elif owner_toggle == 2:
|
||||
mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode)
|
||||
elif owner_toggle == 3:
|
||||
mode = apparmor.aa.owner_flatten_mode(mode)
|
||||
|
||||
if not self.user.aa[profile][hat]['allow'].get(path, False):
|
||||
self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode
|
||||
|
||||
|
||||
tmpmode = set()
|
||||
if audit_toggle == 1:
|
||||
tmpmode = mode- allow_mode
|
||||
elif audit_toggle == 2:
|
||||
tmpmode = mode
|
||||
|
||||
self.user.aa[profile][hat]['allow']['path'][path]['audit'] = self.user.aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Adding %s %s to profile') % (path, apparmor.aa.mode_to_str_user(mode)))
|
||||
if deleted:
|
||||
apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
elif ans == 'CMD_DENY':
|
||||
path = options[selected].strip()
|
||||
# Add new entry?
|
||||
self.user.aa[profile][hat]['deny']['path'][path]['mode'] = self.user.aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode)
|
||||
|
||||
self.user.aa[profile][hat]['deny']['path'][path]['audit'] = self.user.aa[profile][hat]['deny']['path'][path].get('audit', set())
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
done = True
|
||||
|
||||
elif ans == 'CMD_NEW':
|
||||
arg = options[selected]
|
||||
if not apparmor.aa.re_match_include(arg):
|
||||
ans = apparmor.aa.UI_GetString(_('Enter new path: '), arg)
|
||||
# if ans:
|
||||
# if not matchliteral(ans, path):
|
||||
# ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans)
|
||||
# key = apparmor.aa.UI_YesNo(ynprompt, 'n')
|
||||
# if key == 'n':
|
||||
# continue
|
||||
apparmor.aa.user_globs.append(ans)
|
||||
options.append(ans)
|
||||
default_option = len(options)
|
||||
|
||||
elif ans == 'CMD_GLOB':
|
||||
newpath = options[selected].strip()
|
||||
if not apparmor.aa.re_match_include(newpath):
|
||||
newpath = apparmor.aa.glob_path(newpath)
|
||||
|
||||
if newpath not in options:
|
||||
options.append(newpath)
|
||||
default_option = len(options)
|
||||
else:
|
||||
default_option = options.index(newpath) + 1
|
||||
|
||||
elif ans == 'CMD_GLOBEXT':
|
||||
newpath = options[selected].strip()
|
||||
if not apparmor.aa.re_match_include(newpath):
|
||||
newpath = apparmor.aa.glob_path_withext(newpath)
|
||||
|
||||
if newpath not in options:
|
||||
options.append(newpath)
|
||||
default_option = len(options)
|
||||
else:
|
||||
default_option = options.index(newpath) + 1
|
||||
|
||||
elif re.search('\d', ans):
|
||||
default_option = ans
|
||||
|
||||
#
|
||||
for allow in ['allow', 'deny']:
|
||||
for family in sorted(other.aa[profile][hat][allow]['netdomain']['rule'].keys()):
|
||||
# severity handling for net toggles goes here
|
||||
|
||||
for sock_type in sorted(other.aa[profile][hat][allow]['netdomain']['rule'][family].keys()):
|
||||
if apparmor.aa.profile_known_network(self.user.aa[profile][hat], family, sock_type):
|
||||
continue
|
||||
default_option = 1
|
||||
options = []
|
||||
newincludes = apparmor.aa.match_net_includes(self.user.aa[profile][hat], family, sock_type)
|
||||
q = apparmor.aa.hasher()
|
||||
if newincludes:
|
||||
options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes))))
|
||||
if True:#options:
|
||||
options.append('network %s %s' % (family, sock_type))
|
||||
q['options'] = options
|
||||
q['selected'] = default_option - 1
|
||||
|
||||
q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)]
|
||||
q['headers'] += [_('Network Family'), family]
|
||||
q['headers'] += [_('Socket Type'), sock_type]
|
||||
|
||||
audit_toggle = 0
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
|
||||
'CMD_ABORT', 'CMD_FINISHED']
|
||||
|
||||
q['default'] = 'CMD_ALLOW'
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
ans, selected = apparmor.aa.UI_PromptUser(q)
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
break
|
||||
|
||||
if ans.startswith('CMD_AUDIT'):
|
||||
audit_toggle = not audit_toggle
|
||||
audit = ''
|
||||
if audit_toggle:
|
||||
audit = 'audit'
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF',
|
||||
'CMD_ABORT', 'CMD_FINISHED']
|
||||
else:
|
||||
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW',
|
||||
'CMD_ABORT', 'CMD_FINISHED']
|
||||
q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)]
|
||||
q['headers'] += [_('Network Family'), audit + family]
|
||||
q['headers'] += [_('Socket Type'), sock_type]
|
||||
|
||||
elif ans == 'CMD_ALLOW':
|
||||
#print(options, selected)
|
||||
selection = options[selected]
|
||||
done = True
|
||||
if apparmor.aa.re_match_include(selection): #re.search('#include\s+<.+>$', selection):
|
||||
inc = apparmor.aa.re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0]
|
||||
deleted = 0
|
||||
deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc)
|
||||
|
||||
self.user.aa[profile][hat]['include'][inc] = True
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Adding %s to profile') % selection)
|
||||
if deleted:
|
||||
apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
else:
|
||||
self.user.aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle
|
||||
self.user.aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
apparmor.aa.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type))
|
||||
|
||||
elif ans == 'CMD_DENY':
|
||||
done = True
|
||||
self.user.aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True
|
||||
apparmor.aa.changed[profile] = True
|
||||
apparmor.aa.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type))
|
||||
|
||||
else:
|
||||
done = False
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
33
utils/aa-mergeprof.pod
Normal file
33
utils/aa-mergeprof.pod
Normal file
|
@ -0,0 +1,33 @@
|
|||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
aa-mergeprof - merge AppArmor security profiles.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-mergeprof I<E<lt>mineE<gt>> I<E<lt>userE<gt>> I<E<lt>otherE<gt>> [I<-d /path/to/profiles>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<-d --dir /path/to/profiles>
|
||||
|
||||
Specifies where to look for the AppArmor security profile set.
|
||||
Defaults to /etc/apparmor.d.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-mergeprof>
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
If you find any bugs, please report them at
|
||||
L<https://bugs.launchpad.net/apparmor/+filebug>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
apparmor(7), apparmor.d(5), aa_change_hat(2), aa-genprof(1),
|
||||
aa-logprof(1), aa-enforce(1), aa-audit(1), aa-complain(1),
|
||||
aa-disable(1), and L<http://wiki.apparmor.net>.
|
||||
|
||||
=cut
|
92
utils/aa-unconfined
Normal file
92
utils/aa-unconfined
Normal file
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/python
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports"))
|
||||
parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc"))
|
||||
args = parser.parse_args()
|
||||
|
||||
paranoid = args.paranoid
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
if not aa_mountpoint:
|
||||
raise apparmor.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again."))
|
||||
|
||||
pids = []
|
||||
if paranoid:
|
||||
pids = list(filter(lambda x: re.search(r"^\d+$", x), apparmor.get_subdirectories("/proc")))
|
||||
else:
|
||||
regex_tcp_udp = re.compile(r"^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)")
|
||||
import subprocess
|
||||
if sys.version_info < (3, 0):
|
||||
output = subprocess.check_output("LANG=C netstat -nlp", shell=True).split("\n")
|
||||
else:
|
||||
#Python3 needs to translate a stream of bytes to string with specified encoding
|
||||
output = str(subprocess.check_output("LANG=C netstat -nlp", shell=True), encoding='utf8').split("\n")
|
||||
|
||||
for line in output:
|
||||
match = regex_tcp_udp.search(line)
|
||||
if match:
|
||||
pids.append(match.groups()[4])
|
||||
# We can safely remove duplicate pid's?
|
||||
pids = list(map(int, set(pids)))
|
||||
|
||||
for pid in sorted(pids):
|
||||
try:
|
||||
prog = os.readlink("/proc/%s/exe"%pid)
|
||||
except OSError:
|
||||
continue
|
||||
attr = None
|
||||
if os.path.exists("/proc/%s/attr/current"%pid):
|
||||
with apparmor.open_file_read("/proc/%s/attr/current"%pid) as current:
|
||||
for line in current:
|
||||
if line.startswith("/") or line.startswith("null"):
|
||||
attr = line.strip()
|
||||
|
||||
cmdline = apparmor.cmd(["cat", "/proc/%s/cmdline"%pid])[1]
|
||||
pname = cmdline.split("\0")[0]
|
||||
if '/' in pname and pname != prog:
|
||||
pname = "(%s)"% pname
|
||||
else:
|
||||
pname = ""
|
||||
regex_interpreter = re.compile(r"^(/usr)?/bin/(python|perl|bash|dash|sh)$")
|
||||
if not attr:
|
||||
if regex_interpreter.search(prog):
|
||||
cmdline = re.sub(r"\x00", " ", cmdline)
|
||||
cmdline = re.sub(r"\s+$", "", cmdline).strip()
|
||||
|
||||
apparmor.UI_Info(_("%s %s (%s) not confined\n")%(pid, prog, cmdline))
|
||||
else:
|
||||
if pname and pname[-1] == ')':
|
||||
pname += ' '
|
||||
apparmor.UI_Info(_("%s %s %snot confined\n")%(pid, prog, pname))
|
||||
else:
|
||||
if regex_interpreter.search(prog):
|
||||
cmdline = re.sub(r"\0", " ", cmdline)
|
||||
cmdline = re.sub(r"\s+$", "", cmdline).strip()
|
||||
apparmor.UI_Info(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr))
|
||||
else:
|
||||
if pname and pname[-1] == ')':
|
||||
pname += ' '
|
||||
apparmor.UI_Info(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr))
|
|
@ -27,7 +27,14 @@ not have AppArmor profiles loaded
|
|||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-unconfined>
|
||||
B<aa-unconfined [I<--paranoid>]>
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
B<--paranoid>
|
||||
|
||||
Displays all processes from F</proc> filesystem with tcp or udp ports
|
||||
that do not have AppArmor profiles loaded.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
|
@ -39,7 +46,7 @@ network sockets and do not have AppArmor profiles loaded into the kernel.
|
|||
B<aa-unconfined> must be run as root to retrieve the process executable
|
||||
link from the F</proc> filesystem. This program is susceptible to race
|
||||
conditions of several flavours: an unlinked executable will be mishandled;
|
||||
an executable started before a AppArmor profile is loaded will not
|
||||
an executable started before an AppArmor profile is loaded will not
|
||||
appear in the output, despite running without confinement; a process that dies
|
||||
between the netstat(8) and further checks will be mishandled. This
|
||||
program only lists processes using TCP and UDP. In short, this
|
||||
|
|
4281
utils/apparmor/aa.py
Normal file
4281
utils/apparmor/aa.py
Normal file
File diff suppressed because it is too large
Load diff
280
utils/apparmor/aamode.py
Normal file
280
utils/apparmor/aamode.py
Normal file
|
@ -0,0 +1,280 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import re
|
||||
|
||||
def AA_OTHER(mode):
|
||||
other = set()
|
||||
for i in mode:
|
||||
other.add('::%s' % i)
|
||||
return other
|
||||
|
||||
def AA_OTHER_REMOVE(mode):
|
||||
other = set()
|
||||
for i in mode:
|
||||
if '::' in i:
|
||||
other.add(i[2:])
|
||||
return other
|
||||
|
||||
AA_MAY_EXEC = set('x')
|
||||
AA_MAY_WRITE = set('w')
|
||||
AA_MAY_READ = set('r')
|
||||
AA_MAY_APPEND = set('a')
|
||||
AA_MAY_LINK = set('l')
|
||||
AA_MAY_LOCK = set('k')
|
||||
AA_EXEC_MMAP = set('m')
|
||||
AA_EXEC_UNSAFE = set(['execunsafe'])
|
||||
AA_EXEC_INHERIT = set('i')
|
||||
AA_EXEC_UNCONFINED = set('U')
|
||||
AA_EXEC_PROFILE = set('P')
|
||||
AA_EXEC_CHILD = set('C')
|
||||
AA_EXEC_NT = set('N')
|
||||
AA_LINK_SUBSET = set(['linksubset'])
|
||||
#AA_OTHER_SHIFT = 14
|
||||
#AA_USER_MASK = 16384 - 1
|
||||
|
||||
AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT |
|
||||
AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT)
|
||||
|
||||
ALL_AA_EXEC_TYPE = AA_EXEC_TYPE
|
||||
|
||||
MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC,
|
||||
'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE,
|
||||
'r': AA_MAY_READ, 'R': AA_MAY_READ,
|
||||
'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND,
|
||||
'l': AA_MAY_LINK, 'L': AA_MAY_LINK,
|
||||
'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK,
|
||||
'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP,
|
||||
'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT,
|
||||
'u': AA_EXEC_UNCONFINED | AA_EXEC_UNSAFE, # Unconfined + Unsafe
|
||||
'U': AA_EXEC_UNCONFINED,
|
||||
'p': AA_EXEC_PROFILE | AA_EXEC_UNSAFE, # Profile + unsafe
|
||||
'P': AA_EXEC_PROFILE,
|
||||
'c': AA_EXEC_CHILD | AA_EXEC_UNSAFE, # Child + Unsafe
|
||||
'C': AA_EXEC_CHILD,
|
||||
'n': AA_EXEC_NT | AA_EXEC_UNSAFE,
|
||||
'N': AA_EXEC_NT
|
||||
}
|
||||
|
||||
LOG_MODE_RE = re.compile('(r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix)')
|
||||
MODE_MAP_RE = re.compile('(r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N)')
|
||||
|
||||
def str_to_mode(string):
|
||||
if not string:
|
||||
return set()
|
||||
user, other = split_log_mode(string)
|
||||
if not user:
|
||||
user = other
|
||||
|
||||
mode = sub_str_to_mode(user)
|
||||
#print(string, mode)
|
||||
#print(string, 'other', sub_str_to_mode(other))
|
||||
mode |= (AA_OTHER(sub_str_to_mode(other)))
|
||||
#print (string, mode)
|
||||
#print('str_to_mode:', mode)
|
||||
return mode
|
||||
|
||||
def sub_str_to_mode(string):
|
||||
mode = set()
|
||||
if not string:
|
||||
return mode
|
||||
while string:
|
||||
tmp = MODE_MAP_RE.search(string)
|
||||
if tmp:
|
||||
tmp = tmp.groups()[0]
|
||||
string = MODE_MAP_RE.sub('', string, 1)
|
||||
if tmp and MODE_HASH.get(tmp, False):
|
||||
mode |= MODE_HASH[tmp]
|
||||
else:
|
||||
pass
|
||||
|
||||
return mode
|
||||
|
||||
def split_log_mode(mode):
|
||||
user = ''
|
||||
other = ''
|
||||
match = re.search('(.*?)::(.*)', mode)
|
||||
if match:
|
||||
user, other = match.groups()
|
||||
else:
|
||||
user = mode
|
||||
other = mode
|
||||
#print ('split_logmode:', user, mode)
|
||||
return user, other
|
||||
|
||||
def mode_contains(mode, subset):
|
||||
# w implies a
|
||||
if mode & AA_MAY_WRITE:
|
||||
mode |= AA_MAY_APPEND
|
||||
if mode & (AA_OTHER(AA_MAY_WRITE)):
|
||||
mode |= (AA_OTHER(AA_MAY_APPEND))
|
||||
|
||||
return (mode & subset) == subset
|
||||
|
||||
def contains(mode, string):
|
||||
return mode_contains(mode, str_to_mode(string))
|
||||
|
||||
def validate_log_mode(mode):
|
||||
if LOG_MODE_RE.search(mode):
|
||||
#if LOG_MODE_RE.search(mode):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def hide_log_mode(mode):
|
||||
mode = mode.replace('::', '')
|
||||
return mode
|
||||
|
||||
def map_log_mode(mode):
|
||||
return mode
|
||||
|
||||
def print_mode(mode):
|
||||
user, other = split_mode(mode)
|
||||
string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other)
|
||||
|
||||
return string
|
||||
|
||||
def sub_mode_to_str(mode):
|
||||
string = ''
|
||||
# w(write) implies a(append)
|
||||
if mode & AA_MAY_WRITE:
|
||||
mode = mode - AA_MAY_APPEND
|
||||
#string = ''.join(mode)
|
||||
|
||||
if mode & AA_EXEC_MMAP:
|
||||
string += 'm'
|
||||
if mode & AA_MAY_READ:
|
||||
string += 'r'
|
||||
if mode & AA_MAY_WRITE:
|
||||
string += 'w'
|
||||
if mode & AA_MAY_APPEND:
|
||||
string += 'a'
|
||||
if mode & AA_MAY_LINK:
|
||||
string += 'l'
|
||||
if mode & AA_MAY_LOCK:
|
||||
string += 'k'
|
||||
|
||||
# modes P and C must appear before I and U else invalid syntax
|
||||
if mode & (AA_EXEC_PROFILE | AA_EXEC_NT):
|
||||
if mode & AA_EXEC_UNSAFE:
|
||||
string += 'p'
|
||||
else:
|
||||
string += 'P'
|
||||
|
||||
if mode & AA_EXEC_CHILD:
|
||||
if mode & AA_EXEC_UNSAFE:
|
||||
string += 'c'
|
||||
else:
|
||||
string += 'C'
|
||||
|
||||
if mode & AA_EXEC_UNCONFINED:
|
||||
if mode & AA_EXEC_UNSAFE:
|
||||
string += 'u'
|
||||
else:
|
||||
string += 'U'
|
||||
|
||||
if mode & AA_EXEC_INHERIT:
|
||||
string += 'i'
|
||||
|
||||
if mode & AA_MAY_EXEC:
|
||||
string += 'x'
|
||||
|
||||
return string
|
||||
|
||||
def is_user_mode(mode):
|
||||
user, other = split_mode(mode)
|
||||
|
||||
if user and not other:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def profilemode(mode):
|
||||
pass
|
||||
|
||||
def split_mode(mode):
|
||||
user = set()
|
||||
for i in mode:
|
||||
if not '::' in i:
|
||||
user.add(i)
|
||||
other = mode - user
|
||||
other = AA_OTHER_REMOVE(other)
|
||||
return user, other
|
||||
|
||||
def mode_to_str(mode):
|
||||
mode = flatten_mode(mode)
|
||||
return sub_mode_to_str(mode)
|
||||
|
||||
def flatten_mode(mode):
|
||||
if not mode:
|
||||
return set()
|
||||
|
||||
user, other = split_mode(mode)
|
||||
mode = user | other
|
||||
mode |= (AA_OTHER(mode))
|
||||
|
||||
return mode
|
||||
|
||||
def owner_flatten_mode(mode):
|
||||
mode = flatten_mode(mode)
|
||||
return mode
|
||||
|
||||
def mode_to_str_user(mode):
|
||||
user, other = split_mode(mode)
|
||||
string = ''
|
||||
|
||||
if not user:
|
||||
user = set()
|
||||
if not other:
|
||||
other = set()
|
||||
|
||||
if user - other:
|
||||
if other:
|
||||
string = sub_mode_to_str(other) + '+'
|
||||
string += 'owner ' + sub_mode_to_str(user - other)
|
||||
|
||||
elif is_user_mode(mode):
|
||||
string = 'owner ' + sub_mode_to_str(user)
|
||||
else:
|
||||
string = sub_mode_to_str(flatten_mode(mode))
|
||||
|
||||
return string
|
||||
|
||||
def log_str_to_mode(profile, string, nt_name):
|
||||
mode = str_to_mode(string)
|
||||
# If contains nx and nix
|
||||
#print (profile, string, nt_name)
|
||||
if contains(mode, 'Nx'):
|
||||
# Transform to px, cx
|
||||
match = re.search('(.+?)//(.+?)', nt_name)
|
||||
if match:
|
||||
lprofile, lhat = match.groups()
|
||||
tmode = 0
|
||||
|
||||
if lprofile == profile:
|
||||
if mode & AA_MAY_EXEC:
|
||||
tmode = str_to_mode('Cx::')
|
||||
if mode & AA_OTHER(AA_MAY_EXEC):
|
||||
tmode |= str_to_mode('Cx')
|
||||
nt_name = lhat
|
||||
else:
|
||||
if mode & AA_MAY_EXEC:
|
||||
tmode = str_to_mode('Px::')
|
||||
if mode & AA_OTHER(AA_MAY_EXEC):
|
||||
tmode |= str_to_mode('Px')
|
||||
nt_name = lhat
|
||||
|
||||
mode = mode - str_to_mode('Nx')
|
||||
mode |= tmode
|
||||
|
||||
return mode, nt_name
|
153
utils/apparmor/cleanprofile.py
Normal file
153
utils/apparmor/cleanprofile.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import re
|
||||
import copy
|
||||
|
||||
import apparmor
|
||||
|
||||
class Prof(object):
|
||||
def __init__(self, filename):
|
||||
self.aa = apparmor.aa.aa
|
||||
self.filelist = apparmor.aa.filelist
|
||||
self.include = apparmor.aa.include
|
||||
self.filename = filename
|
||||
|
||||
class CleanProf(object):
|
||||
def __init__(self, same_file, profile, other):
|
||||
#If same_file we're basically comparing the file against itself to check superfluous rules
|
||||
self.same_file = same_file
|
||||
self.profile = profile
|
||||
self.other = other
|
||||
|
||||
def compare_profiles(self):
|
||||
deleted = 0
|
||||
other_file_includes = list(self.other.filelist[self.other.filename]['include'].keys())
|
||||
|
||||
#Remove the duplicate file-level includes from other
|
||||
for rule in self.profile.filelist[self.profile.filename]['include'].keys():
|
||||
if rule in other_file_includes:
|
||||
self.other.filelist[self.other.filename]['include'].pop(rule)
|
||||
|
||||
for profile in self.profile.aa.keys():
|
||||
deleted += self.remove_duplicate_rules(profile)
|
||||
|
||||
return deleted
|
||||
|
||||
def remove_duplicate_rules(self, program):
|
||||
#Process the profile of the program
|
||||
#Process every hat in the profile individually
|
||||
file_includes = list(self.profile.filelist[self.profile.filename]['include'].keys())
|
||||
deleted = 0
|
||||
for hat in self.profile.aa[program].keys():
|
||||
#The combined list of includes from profile and the file
|
||||
includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes
|
||||
|
||||
#If different files remove duplicate includes in the other profile
|
||||
if not self.same_file:
|
||||
for inc in includes:
|
||||
if self.other.aa[program][hat]['include'].get(inc, False):
|
||||
self.other.aa[program][hat]['include'].pop(inc)
|
||||
deleted += 1
|
||||
#Clean up superfluous rules from includes in the other profile
|
||||
for inc in includes:
|
||||
if not self.profile.include.get(inc, {}).get(inc, False):
|
||||
apparmor.aa.load_include(inc)
|
||||
deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc)
|
||||
|
||||
#Clean the duplicates of caps in other profile
|
||||
deleted += delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file)
|
||||
deleted += delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file)
|
||||
|
||||
#Clean the duplicates of path in other profile
|
||||
deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file)
|
||||
deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file)
|
||||
|
||||
#Clean the duplicates of net rules in other profile
|
||||
deleted += delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file)
|
||||
deleted += delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file)
|
||||
|
||||
return deleted
|
||||
|
||||
def delete_path_duplicates(profile, profile_other, allow, same_profile=True):
|
||||
deleted = []
|
||||
# Check if any individual rule makes any rule superfluous
|
||||
for rule in profile[allow]['path'].keys():
|
||||
for entry in profile_other[allow]['path'].keys():
|
||||
if rule == entry:
|
||||
# Check the modes
|
||||
cm = profile[allow]['path'][rule]['mode']
|
||||
am = profile[allow]['path'][rule]['audit']
|
||||
# If modes of rule are a superset of rules implied by entry we can safely remove it
|
||||
if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']):
|
||||
if not same_profile:
|
||||
deleted.append(entry)
|
||||
continue
|
||||
if re.search('#?\s*include', rule) or re.search('#?\s*include', entry):
|
||||
continue
|
||||
# Check if the rule implies entry
|
||||
if apparmor.aa.matchliteral(rule, entry):
|
||||
# Check the modes
|
||||
cm = profile[allow]['path'][rule]['mode']
|
||||
am = profile[allow]['path'][rule]['audit']
|
||||
# If modes of rule are a superset of rules implied by entry we can safely remove it
|
||||
if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']):
|
||||
deleted.append(entry)
|
||||
|
||||
for entry in deleted:
|
||||
profile_other[allow]['path'].pop(entry)
|
||||
|
||||
return len(deleted)
|
||||
|
||||
def delete_cap_duplicates(profilecaps, profilecaps_other, same_profile=True):
|
||||
deleted = []
|
||||
if profilecaps and profilecaps_other and not same_profile:
|
||||
for capname in profilecaps.keys():
|
||||
if profilecaps_other[capname].get('set', False):
|
||||
deleted.append(capname)
|
||||
for capname in deleted:
|
||||
profilecaps_other.pop(capname)
|
||||
|
||||
return len(deleted)
|
||||
|
||||
def delete_net_duplicates(netrules, netrules_other, same_profile=True):
|
||||
deleted = 0
|
||||
hasher_obj = apparmor.aa.hasher()
|
||||
copy_netrules_other = copy.deepcopy(netrules_other)
|
||||
if netrules_other and netrules:
|
||||
netglob = False
|
||||
# Delete matching rules
|
||||
if netrules.get('all', False):
|
||||
netglob = True
|
||||
# Iterate over a copy of the rules in the other profile
|
||||
for fam in copy_netrules_other['rule'].keys():
|
||||
if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]):
|
||||
if not same_profile:
|
||||
if type(netrules_other['rule'][fam]) == type(hasher_obj):
|
||||
deleted += len(netrules_other['rule'][fam].keys())
|
||||
else:
|
||||
deleted += 1
|
||||
netrules_other['rule'].pop(fam)
|
||||
elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]:
|
||||
if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]:
|
||||
if not same_profile:
|
||||
netrules_other['rule'].pop(fam)
|
||||
deleted += 1
|
||||
else:
|
||||
for sock_type in netrules_other['rule'][fam].keys():
|
||||
if netrules['rule'].get(fam, False):
|
||||
if netrules['rule'][fam].get(sock_type, False):
|
||||
if not same_profile:
|
||||
netrules_other['rule'][fam].pop(sock_type)
|
||||
deleted += 1
|
||||
return deleted
|
|
@ -1,6 +1,7 @@
|
|||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2012 Canonical Ltd.
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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
|
||||
|
@ -9,11 +10,20 @@
|
|||
# ------------------------------------------------------------------
|
||||
|
||||
from __future__ import print_function
|
||||
import codecs
|
||||
import collections
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
|
||||
DEBUGGING = False
|
||||
|
||||
|
||||
#
|
||||
# Utility classes
|
||||
#
|
||||
|
@ -93,3 +103,154 @@ def cmd_pipe(command1, command2):
|
|||
|
||||
return [sp2.returncode, out]
|
||||
|
||||
def valid_path(path):
|
||||
'''Valid path'''
|
||||
# No relative paths
|
||||
m = "Invalid path: %s" % (path)
|
||||
if not path.startswith('/'):
|
||||
debug("%s (relative)" % (m))
|
||||
return False
|
||||
|
||||
if '"' in path: # We double quote elsewhere
|
||||
return False
|
||||
|
||||
try:
|
||||
os.path.normpath(path)
|
||||
except Exception:
|
||||
debug("%s (could not normalize)" % (m))
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_directory_contents(path):
|
||||
'''Find contents of the given directory'''
|
||||
if not valid_path(path):
|
||||
return None
|
||||
|
||||
files = []
|
||||
for f in glob.glob(path + "/*"):
|
||||
files.append(f)
|
||||
|
||||
files.sort()
|
||||
return files
|
||||
|
||||
def open_file_read(path, encoding='UTF-8'):
|
||||
'''Open specified file read-only'''
|
||||
try:
|
||||
orig = codecs.open(path, 'r', encoding)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
return orig
|
||||
|
||||
def open_file_write(path):
|
||||
'''Open specified file in write/overwrite mode'''
|
||||
try:
|
||||
orig = codecs.open(path, 'w', 'UTF-8')
|
||||
except Exception:
|
||||
raise
|
||||
return orig
|
||||
|
||||
def readkey():
|
||||
'''Returns the pressed key'''
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
|
||||
return ch
|
||||
|
||||
def hasher():
|
||||
'''A neat alternative to perl's hash reference'''
|
||||
# Creates a dictionary for any depth and returns empty dictionary otherwise
|
||||
return collections.defaultdict(hasher)
|
||||
|
||||
def convert_regexp(regexp):
|
||||
regex_paren = re.compile('^(.*){([^}]*)}(.*)$')
|
||||
regexp = regexp.strip()
|
||||
new_reg = re.sub(r'(?<!\\)(\.|\+|\$)', r'\\\1', regexp)
|
||||
|
||||
while regex_paren.search(new_reg):
|
||||
match = regex_paren.search(new_reg).groups()
|
||||
prev = match[0]
|
||||
after = match[2]
|
||||
p1 = match[1].replace(',', '|')
|
||||
new_reg = prev + '(' + p1 + ')' + after
|
||||
|
||||
new_reg = new_reg.replace('?', '[^/\000]')
|
||||
|
||||
multi_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__'
|
||||
new_reg = new_reg.replace('**', multi_glob)
|
||||
#print(new_reg)
|
||||
|
||||
# Match atleast one character if * or ** after /
|
||||
# ?< is the negative lookback operator
|
||||
new_reg = new_reg.replace('*', '(((?<=/)[^/\000]+)|((?<!/)[^/\000]*))')
|
||||
new_reg = new_reg.replace(multi_glob, '(((?<=/)[^\000]+)|((?<!/)[^\000]*))')
|
||||
if regexp[0] != '^':
|
||||
new_reg = '^' + new_reg
|
||||
if regexp[-1] != '$':
|
||||
new_reg = new_reg + '$'
|
||||
return new_reg
|
||||
|
||||
def user_perm(prof_dir):
|
||||
if not os.access(prof_dir, os.W_OK):
|
||||
sys.stdout.write("Cannot write to profile directory.\n" +
|
||||
"Please run as a user with appropriate permissions.\n")
|
||||
return False
|
||||
return True
|
||||
|
||||
class DebugLogger(object):
|
||||
def __init__(self, module_name=__name__):
|
||||
self.debugging = False
|
||||
self.logfile = '/var/log/apparmor/logprof.log'
|
||||
self.debug_level = logging.DEBUG
|
||||
if os.getenv('LOGPROF_DEBUG', False):
|
||||
self.debugging = os.getenv('LOGPROF_DEBUG')
|
||||
try:
|
||||
self.debugging = int(self.debugging)
|
||||
except Exception:
|
||||
self.debugging = False
|
||||
if self.debugging not in range(0, 4):
|
||||
sys.stdout.write('Environment Variable: LOGPROF_DEBUG contains invalid value: %s'
|
||||
% os.getenv('LOGPROF_DEBUG'))
|
||||
if self.debugging == 0: # debugging disabled, don't need to setup logging
|
||||
return
|
||||
if self.debugging == 1:
|
||||
self.debug_level = logging.ERROR
|
||||
elif self.debugging == 2:
|
||||
self.debug_level = logging.INFO
|
||||
elif self.debugging == 3:
|
||||
self.debug_level = logging.DEBUG
|
||||
|
||||
try:
|
||||
logging.basicConfig(filename=self.logfile, level=self.debug_level,
|
||||
format='%(asctime)s - %(name)s - %(message)s\n')
|
||||
except OSError:
|
||||
# Unable to open the default logfile, so create a temporary logfile and tell use about it
|
||||
import tempfile
|
||||
templog = tempfile.NamedTemporaryFile('w', prefix='apparmor', suffix='.log', delete=False)
|
||||
sys.stdout.write("\nCould not open: %s\nLogging to: %s\n" % (self.logfile, templog.name))
|
||||
|
||||
logging.basicConfig(filename=templog.name, level=self.debug_level,
|
||||
format='%(asctime)s - %(name)s - %(message)s\n')
|
||||
|
||||
self.logger = logging.getLogger(module_name)
|
||||
|
||||
def error(self, message):
|
||||
if self.debugging:
|
||||
self.logger.error(message)
|
||||
|
||||
def info(self, message):
|
||||
if self.debugging:
|
||||
self.logger.info(message)
|
||||
|
||||
def debug(self, message):
|
||||
if self.debugging:
|
||||
self.logger.debug(message)
|
||||
|
||||
def shutdown(self):
|
||||
logging.shutdown()
|
||||
#logging.shutdown([self.logger])
|
||||
|
|
297
utils/apparmor/config.py
Normal file
297
utils/apparmor/config.py
Normal file
|
@ -0,0 +1,297 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
from __future__ import with_statement
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
if sys.version_info < (3, 0):
|
||||
import ConfigParser as configparser
|
||||
|
||||
# Class to provide the object[section][option] behavior in Python2
|
||||
class configparser_py2(configparser.ConfigParser):
|
||||
def __getitem__(self, section):
|
||||
section_val = self.items(section)
|
||||
section_options = dict()
|
||||
for option, value in section_val:
|
||||
section_options[option] = value
|
||||
return section_options
|
||||
|
||||
|
||||
else:
|
||||
import configparser
|
||||
|
||||
|
||||
from apparmor.common import AppArmorException, open_file_read # , warn, msg,
|
||||
|
||||
|
||||
# CFG = None
|
||||
# REPO_CFG = None
|
||||
# SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf']
|
||||
class Config(object):
|
||||
def __init__(self, conf_type, conf_dir='/etc/apparmor'):
|
||||
self.CONF_DIR = conf_dir
|
||||
# The type of config file that'll be read and/or written
|
||||
if conf_type == 'shell' or conf_type == 'ini':
|
||||
self.conf_type = conf_type
|
||||
self.input_file = None
|
||||
else:
|
||||
raise AppArmorException("Unknown configuration file type")
|
||||
|
||||
def new_config(self):
|
||||
if self.conf_type == 'shell':
|
||||
config = {'': dict()}
|
||||
elif self.conf_type == 'ini':
|
||||
config = configparser.ConfigParser()
|
||||
return config
|
||||
|
||||
def read_config(self, filename):
|
||||
"""Reads the file and returns a config[section][attribute]=property object"""
|
||||
# LP: Bug #692406
|
||||
# Explicitly disabled repository
|
||||
filepath = self.CONF_DIR + '/' + filename
|
||||
self.input_file = filepath
|
||||
if filename == "repository.conf":
|
||||
config = dict()
|
||||
config['repository'] = {'enabled': 'no'}
|
||||
elif self.conf_type == 'shell':
|
||||
config = self.read_shell(filepath)
|
||||
elif self.conf_type == 'ini':
|
||||
if sys.version_info > (3, 0):
|
||||
config = configparser.ConfigParser()
|
||||
else:
|
||||
config = configparser_py2()
|
||||
# Set the option form to string -prevents forced conversion to lowercase
|
||||
config.optionxform = str
|
||||
if sys.version_info > (3, 0):
|
||||
config.read(filepath)
|
||||
else:
|
||||
try:
|
||||
config.read(filepath)
|
||||
except configparser.ParsingError:
|
||||
tmp_filepath = py2_parser(filepath)
|
||||
config.read(tmp_filepath.name)
|
||||
##config.__get__()
|
||||
return config
|
||||
|
||||
def write_config(self, filename, config):
|
||||
"""Writes the given config to the specified file"""
|
||||
filepath = self.CONF_DIR + '/' + filename
|
||||
permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write
|
||||
try:
|
||||
# Open a temporary file in the CONF_DIR to write the config file
|
||||
config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=self.CONF_DIR)
|
||||
if os.path.exists(self.input_file):
|
||||
# Copy permissions from an existing file to temporary file
|
||||
shutil.copymode(self.input_file, config_file.name)
|
||||
else:
|
||||
# If no existing permission set the file permissions as 0600
|
||||
os.chmod(config_file.name, permission_600)
|
||||
if self.conf_type == 'shell':
|
||||
self.write_shell(filepath, config_file, config)
|
||||
elif self.conf_type == 'ini':
|
||||
self.write_configparser(filepath, config_file, config)
|
||||
config_file.close()
|
||||
except IOError:
|
||||
raise AppArmorException("Unable to write to %s" % filename)
|
||||
else:
|
||||
# Replace the target config file with the temporary file
|
||||
os.rename(config_file.name, filepath)
|
||||
|
||||
def find_first_file(self, file_list):
|
||||
"""Returns name of first matching file None otherwise"""
|
||||
filename = None
|
||||
for f in file_list.split():
|
||||
if os.path.isfile(f):
|
||||
filename = f
|
||||
break
|
||||
return filename
|
||||
|
||||
def find_first_dir(self, dir_list):
|
||||
"""Returns name of first matching directory None otherwise"""
|
||||
dirname = None
|
||||
if dir_list:
|
||||
for direc in dir_list.split():
|
||||
if os.path.isdir(direc):
|
||||
dirname = direc
|
||||
break
|
||||
return dirname
|
||||
|
||||
def read_shell(self, filepath):
|
||||
"""Reads the shell type conf files and returns config[''][option]=value"""
|
||||
config = {'': dict()}
|
||||
with open_file_read(filepath) as conf_file:
|
||||
for line in conf_file:
|
||||
result = shlex.split(line, True)
|
||||
# If not a comment of empty line
|
||||
if result:
|
||||
# option="value" or option=value type
|
||||
if '=' in result[0]:
|
||||
option, value = result[0].split('=')
|
||||
# option type
|
||||
else:
|
||||
option = result[0]
|
||||
value = None
|
||||
config[''][option] = value
|
||||
return config
|
||||
|
||||
def write_shell(self, filepath, f_out, config):
|
||||
"""Writes the config object in shell file format"""
|
||||
# All the options in the file
|
||||
options = [key for key in config[''].keys()]
|
||||
# If a previous file exists modify it keeping the comments
|
||||
if os.path.exists(self.input_file):
|
||||
with open_file_read(self.input_file) as f_in:
|
||||
for line in f_in:
|
||||
result = shlex.split(line, True)
|
||||
# If line is not empty or comment
|
||||
if result:
|
||||
# If option=value or option="value" type
|
||||
if '=' in result[0]:
|
||||
option, value = result[0].split('=')
|
||||
if '#' in line:
|
||||
comment = value.split('#', 1)[1]
|
||||
comment = '#' + comment
|
||||
else:
|
||||
comment = ''
|
||||
# If option exists in the new config file
|
||||
if option in options:
|
||||
# If value is different
|
||||
if value != config[''][option]:
|
||||
value_new = config[''][option]
|
||||
if value_new is not None:
|
||||
# Update value
|
||||
if '"' in line:
|
||||
value_new = '"' + value_new + '"'
|
||||
line = option + '=' + value_new + comment + '\n'
|
||||
else:
|
||||
# If option changed to option type from option=value type
|
||||
line = option + comment + '\n'
|
||||
f_out.write(line)
|
||||
# Remove from remaining options list
|
||||
options.remove(option)
|
||||
else:
|
||||
# If option type
|
||||
option = result[0]
|
||||
value = None
|
||||
# If option exists in the new config file
|
||||
if option in options:
|
||||
# If its no longer option type
|
||||
if config[''][option] is not None:
|
||||
value = config[''][option]
|
||||
line = option + '=' + value + '\n'
|
||||
f_out.write(line)
|
||||
# Remove from remaining options list
|
||||
options.remove(option)
|
||||
else:
|
||||
# If its empty or comment copy as it is
|
||||
f_out.write(line)
|
||||
# If any new options are present
|
||||
if options:
|
||||
for option in options:
|
||||
value = config[''][option]
|
||||
# option type entry
|
||||
if value is None:
|
||||
line = option + '\n'
|
||||
# option=value type entry
|
||||
else:
|
||||
line = option + '=' + value + '\n'
|
||||
f_out.write(line)
|
||||
|
||||
def write_configparser(self, filepath, f_out, config):
|
||||
"""Writes/updates the given file with given config object"""
|
||||
# All the sections in the file
|
||||
sections = config.sections()
|
||||
write = True
|
||||
section = None
|
||||
options = []
|
||||
# If a previous file exists modify it keeping the comments
|
||||
if os.path.exists(self.input_file):
|
||||
with open_file_read(self.input_file) as f_in:
|
||||
for line in f_in:
|
||||
# If its a section
|
||||
if line.lstrip().startswith('['):
|
||||
# If any options from preceding section remain write them
|
||||
if options:
|
||||
for option in options:
|
||||
line_new = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||
f_out.write(line_new)
|
||||
options = []
|
||||
if section in sections:
|
||||
# Remove the written section from the list
|
||||
sections.remove(section)
|
||||
section = line.strip()[1:-1]
|
||||
if section in sections:
|
||||
# enable write for all entries in that section
|
||||
write = True
|
||||
options = config.options(section)
|
||||
# write the section
|
||||
f_out.write(line)
|
||||
else:
|
||||
# disable writing until next valid section
|
||||
write = False
|
||||
# If write enabled
|
||||
elif write:
|
||||
value = shlex.split(line, True)
|
||||
# If the line is empty or a comment
|
||||
if not value:
|
||||
f_out.write(line)
|
||||
else:
|
||||
option, value = line.split('=', 1)
|
||||
try:
|
||||
# split any inline comments
|
||||
value, comment = value.split('#', 1)
|
||||
comment = '#' + comment
|
||||
except ValueError:
|
||||
comment = ''
|
||||
if option.strip() in options:
|
||||
if config[section][option.strip()] != value.strip():
|
||||
value = value.replace(value, config[section][option.strip()])
|
||||
line = option + '=' + value + comment
|
||||
f_out.write(line)
|
||||
options.remove(option.strip())
|
||||
# If any options remain from the preceding section
|
||||
if options:
|
||||
for option in options:
|
||||
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||
f_out.write(line)
|
||||
options = []
|
||||
# If any new sections are present
|
||||
if section in sections:
|
||||
sections.remove(section)
|
||||
for section in sections:
|
||||
f_out.write('\n[%s]\n' % section)
|
||||
options = config.options(section)
|
||||
for option in options:
|
||||
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||
f_out.write(line)
|
||||
|
||||
def py2_parser(filename):
|
||||
"""Returns the de-dented ini file from the new format ini"""
|
||||
tmp = tempfile.NamedTemporaryFile('rw')
|
||||
f_out = open(tmp.name, 'w')
|
||||
if os.path.exists(filename):
|
||||
with open_file_read(filename) as f_in:
|
||||
for line in f_in:
|
||||
# The ini format allows for multi-line entries, with the subsequent
|
||||
# entries being indented deeper hence simple lstrip() is not appropriate
|
||||
if line[:2] == ' ':
|
||||
line = line[2:]
|
||||
elif line[0] == '\t':
|
||||
line = line[1:]
|
||||
f_out.write(line)
|
||||
f_out.flush()
|
||||
return tmp
|
399
utils/apparmor/logparser.py
Normal file
399
utils/apparmor/logparser.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import gettext
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import LibAppArmor
|
||||
from apparmor.common import (AppArmorException, error, debug,
|
||||
open_file_read, valid_path, hasher,
|
||||
open_file_write, convert_regexp,
|
||||
DebugLogger)
|
||||
|
||||
from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
class ReadLog:
|
||||
RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
|
||||
RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
|
||||
MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N')
|
||||
LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix')
|
||||
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
|
||||
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
|
||||
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
|
||||
# Used by netdomain to identify the operation types
|
||||
# New socket names
|
||||
OPERATION_TYPES = {'create': 'net',
|
||||
'post_create': 'net',
|
||||
'bind': 'net',
|
||||
'connect': 'net',
|
||||
'listen': 'net',
|
||||
'accept': 'net',
|
||||
'sendmsg': 'net',
|
||||
'recvmsg': 'net',
|
||||
'getsockname': 'net',
|
||||
'getpeername': 'net',
|
||||
'getsockopt': 'net',
|
||||
'setsockopt': 'net',
|
||||
'sock_shutdown': 'net'
|
||||
}
|
||||
|
||||
def __init__(self, pid, filename, existing_profiles, profile_dir, log):
|
||||
self.filename = filename
|
||||
self.profile_dir = profile_dir
|
||||
self.pid = pid
|
||||
self.existing_profiles = existing_profiles
|
||||
self.log = log
|
||||
self.debug_logger = DebugLogger('ReadLog')
|
||||
self.LOG = None
|
||||
self.logmark = ''
|
||||
self.seenmark = None
|
||||
self.next_log_entry = None
|
||||
|
||||
def prefetch_next_log_entry(self):
|
||||
if self.next_log_entry:
|
||||
sys.stderr.out('A log entry already present: %s' % self.next_log_entry)
|
||||
self.next_log_entry = self.LOG.readline()
|
||||
while not self.RE_LOG_v2_6_syslog.search(self.next_log_entry) and not self.RE_LOG_v2_6_audit.search(self.next_log_entry) and not (self.logmark and self.logmark in self.next_log_entry):
|
||||
self.next_log_entry = self.LOG.readline()
|
||||
if not self.next_log_entry:
|
||||
break
|
||||
|
||||
def get_next_log_entry(self):
|
||||
# If no next log entry fetch it
|
||||
if not self.next_log_entry:
|
||||
self.prefetch_next_log_entry()
|
||||
log_entry = self.next_log_entry
|
||||
self.next_log_entry = None
|
||||
return log_entry
|
||||
|
||||
def peek_at_next_log_entry(self):
|
||||
# Take a peek at the next log entry
|
||||
if not self.next_log_entry:
|
||||
self.prefetch_next_log_entry()
|
||||
return self.next_log_entry
|
||||
|
||||
def throw_away_next_log_entry(self):
|
||||
self.next_log_entry = None
|
||||
|
||||
def parse_log_record(self, record):
|
||||
self.debug_logger.debug('parse_log_record: %s' % record)
|
||||
|
||||
record_event = self.parse_event(record)
|
||||
return record_event
|
||||
|
||||
def parse_event(self, msg):
|
||||
"""Parse the event from log into key value pairs"""
|
||||
msg = msg.strip()
|
||||
self.debug_logger.info('parse_event: %s' % msg)
|
||||
#print(repr(msg))
|
||||
if sys.version_info < (3, 0):
|
||||
# parse_record fails with u'foo' style strings hence typecasting to string
|
||||
msg = str(msg)
|
||||
event = LibAppArmor.parse_record(msg)
|
||||
ev = dict()
|
||||
ev['resource'] = event.info
|
||||
ev['active_hat'] = event.active_hat
|
||||
ev['aamode'] = event.event
|
||||
ev['time'] = event.epoch
|
||||
ev['operation'] = event.operation
|
||||
ev['profile'] = event.profile
|
||||
ev['name'] = event.name
|
||||
ev['name2'] = event.name2
|
||||
ev['attr'] = event.attribute
|
||||
ev['parent'] = event.parent
|
||||
ev['pid'] = event.pid
|
||||
ev['task'] = event.task
|
||||
ev['info'] = event.info
|
||||
dmask = event.denied_mask
|
||||
rmask = event.requested_mask
|
||||
ev['magic_token'] = event.magic_token
|
||||
if ev['operation'] and self.op_type(ev['operation']) == 'net':
|
||||
ev['family'] = event.net_family
|
||||
ev['protocol'] = event.net_protocol
|
||||
ev['sock_type'] = event.net_sock_type
|
||||
LibAppArmor.free_record(event)
|
||||
# Map c (create) to a and d (delete) to w, logprof doesn't support c and d
|
||||
if rmask:
|
||||
rmask = rmask.replace('c', 'a')
|
||||
rmask = rmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(rmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % rmask)
|
||||
if dmask:
|
||||
dmask = dmask.replace('c', 'a')
|
||||
dmask = dmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(dmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % dmask)
|
||||
#print('parse_event:', ev['profile'], dmask, ev['name2'])
|
||||
mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2'])
|
||||
|
||||
ev['denied_mask'] = mask
|
||||
ev['name2'] = name
|
||||
|
||||
mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2'])
|
||||
ev['request_mask'] = mask
|
||||
ev['name2'] = name
|
||||
|
||||
if not ev['time']:
|
||||
ev['time'] = int(time.time())
|
||||
# Remove None keys
|
||||
#for key in ev.keys():
|
||||
# if not ev[key] or not re.search('[\w]+', ev[key]):
|
||||
# ev.pop(key)
|
||||
|
||||
if ev['aamode']:
|
||||
# Convert aamode values to their counter-parts
|
||||
mode_convertor = {0: 'UNKNOWN',
|
||||
1: 'ERROR',
|
||||
2: 'AUDITING',
|
||||
3: 'PERMITTING',
|
||||
4: 'REJECTING',
|
||||
5: 'HINT',
|
||||
6: 'STATUS'
|
||||
}
|
||||
try:
|
||||
ev['aamode'] = mode_convertor[ev['aamode']]
|
||||
except KeyError:
|
||||
ev['aamode'] = None
|
||||
|
||||
if ev['aamode']:
|
||||
#debug_logger.debug(ev)
|
||||
return ev
|
||||
else:
|
||||
return None
|
||||
|
||||
def add_to_tree(self, loc_pid, parent, type, event):
|
||||
self.debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event))
|
||||
if not self.pid.get(loc_pid, False):
|
||||
profile, hat = event[:2]
|
||||
if parent and self.pid.get(parent, False):
|
||||
if not hat:
|
||||
hat = 'null-complain-profile'
|
||||
arrayref = []
|
||||
self.pid[parent].append(arrayref)
|
||||
self.pid[loc_pid] = arrayref
|
||||
for ia in ['fork', loc_pid, profile, hat]:
|
||||
arrayref.append(ia)
|
||||
# self.pid[parent].append(array_ref)
|
||||
# self.pid[loc_pid] = array_ref
|
||||
else:
|
||||
arrayref = []
|
||||
self.log.append(arrayref)
|
||||
self.pid[loc_pid] = arrayref
|
||||
# self.log.append(array_ref)
|
||||
# self.pid[loc_pid] = array_ref
|
||||
self.pid[loc_pid].append([type, loc_pid] + event)
|
||||
#print("\n\npid",self.pid)
|
||||
#print("log",self.log)
|
||||
|
||||
def add_event_to_tree(self, e):
|
||||
aamode = e.get('aamode', 'UNKNOWN')
|
||||
if e.get('type', False):
|
||||
if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']):
|
||||
aamode = 'AUDIT'
|
||||
elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']):
|
||||
aamode = 'PERMITTING'
|
||||
elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']):
|
||||
aamode = 'REJECTING'
|
||||
elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']):
|
||||
aamode = 'HINT'
|
||||
elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']):
|
||||
aamode = 'STATUS'
|
||||
elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']):
|
||||
aamode = 'ERROR'
|
||||
else:
|
||||
aamode = 'UNKNOWN'
|
||||
|
||||
if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']:
|
||||
return None
|
||||
|
||||
if 'profile_set' in e['operation']:
|
||||
return None
|
||||
|
||||
# Skip if AUDIT event was issued due to a change_hat in unconfined mode
|
||||
if not e.get('profile', False):
|
||||
return None
|
||||
|
||||
# Convert new null profiles to old single level null profile
|
||||
if '//null-' in e['profile']:
|
||||
e['profile'] = 'null-complain-profile'
|
||||
|
||||
profile = e['profile']
|
||||
hat = None
|
||||
|
||||
if '//' in e['profile']:
|
||||
profile, hat = e['profile'].split('//')[:2]
|
||||
|
||||
# Filter out change_hat events that aren't from learning
|
||||
if e['operation'] == 'change_hat':
|
||||
if aamode != 'HINT' and aamode != 'PERMITTING':
|
||||
return None
|
||||
profile = e['name']
|
||||
#hat = None
|
||||
if '//' in e['name']:
|
||||
profile, hat = e['name'].split('//')[:2]
|
||||
|
||||
if not hat:
|
||||
hat = profile
|
||||
|
||||
# prog is no longer passed around consistently
|
||||
prog = 'HINT'
|
||||
|
||||
if profile != 'null-complain-profile' and not self.profile_exists(profile):
|
||||
return None
|
||||
if e['operation'] == 'exec':
|
||||
if e.get('info', False) and e['info'] == 'mandatory profile missing':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'exec',
|
||||
[profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']])
|
||||
elif e.get('name2', False) and '\\null-/' in e['name2']:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'exec',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e.get('name', False):
|
||||
self.add_to_tree(e['pid'], e['parent'], 'exec',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
else:
|
||||
self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile'])
|
||||
|
||||
elif 'file_' in e['operation']:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src',
|
||||
'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']:
|
||||
#print(e['operation'], e['name'])
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e['operation'] == 'capable':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'capability',
|
||||
[profile, hat, prog, aamode, e['name'], ''])
|
||||
elif e['operation'] == 'setattr' or 'xattr' in e['operation']:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif 'inode_' in e['operation']:
|
||||
is_domain_change = False
|
||||
if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
|
||||
following = self.peek_at_next_log_entry()
|
||||
if following:
|
||||
entry = self.parse_log_record(following)
|
||||
if entry and entry.get('info', False) == 'set profile':
|
||||
is_domain_change = True
|
||||
self.throw_away_next_log_entry()
|
||||
|
||||
if is_domain_change:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'exec',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']])
|
||||
else:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
|
||||
elif e['operation'] == 'sysctl':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
|
||||
elif e['operation'] == 'clone':
|
||||
parent, child = e['pid'], e['task']
|
||||
if not parent:
|
||||
parent = 'null-complain-profile'
|
||||
if not hat:
|
||||
hat = 'null-complain-profile'
|
||||
arrayref = []
|
||||
if self.pid.get(parent, False):
|
||||
self.pid[parent].append(arrayref)
|
||||
else:
|
||||
self.log.append(arrayref)
|
||||
self.pid[child].append(arrayref)
|
||||
for ia in ['fork', child, profile, hat]:
|
||||
arrayref.append(ia)
|
||||
# if self.pid.get(parent, False):
|
||||
# self.pid[parent] += [arrayref]
|
||||
# else:
|
||||
# self.log += [arrayref]
|
||||
# self.pid[child] = arrayref
|
||||
|
||||
elif self.op_type(e['operation']) == 'net':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'netdomain',
|
||||
[profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']])
|
||||
elif e['operation'] == 'change_hat':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'unknown_hat',
|
||||
[profile, hat, aamode, hat])
|
||||
else:
|
||||
self.debug_logger.debug('UNHANDLED: %s' % e)
|
||||
|
||||
def read_log(self, logmark):
|
||||
self.logmark = logmark
|
||||
seenmark = True
|
||||
if self.logmark:
|
||||
seenmark = False
|
||||
#last = None
|
||||
#event_type = None
|
||||
try:
|
||||
#print(self.filename)
|
||||
self.LOG = open_file_read(self.filename)
|
||||
except IOError:
|
||||
raise AppArmorException('Can not read AppArmor logfile: ' + self.filename)
|
||||
#LOG = open_file_read(log_open)
|
||||
line = True
|
||||
while line:
|
||||
line = self.get_next_log_entry()
|
||||
if not line:
|
||||
break
|
||||
line = line.strip()
|
||||
self.debug_logger.debug('read_log: %s' % line)
|
||||
if self.logmark in line:
|
||||
seenmark = True
|
||||
|
||||
self.debug_logger.debug('read_log: seenmark = %s' % seenmark)
|
||||
if not seenmark:
|
||||
continue
|
||||
|
||||
event = self.parse_log_record(line)
|
||||
#print(event)
|
||||
if event:
|
||||
self.add_event_to_tree(event)
|
||||
self.LOG.close()
|
||||
self.logmark = ''
|
||||
return self.log
|
||||
|
||||
def op_type(self, operation):
|
||||
"""Returns the operation type if known, unkown otherwise"""
|
||||
operation_type = self.OPERATION_TYPES.get(operation, 'unknown')
|
||||
return operation_type
|
||||
|
||||
def profile_exists(self, program):
|
||||
"""Returns True if profile exists, False otherwise"""
|
||||
# Check cache of profiles
|
||||
if self.existing_profiles.get(program, False):
|
||||
return True
|
||||
# Check the disk for profile
|
||||
prof_path = self.get_profile_filename(program)
|
||||
#print(prof_path)
|
||||
if os.path.isfile(prof_path):
|
||||
# Add to cache of profile
|
||||
self.existing_profiles[program] = prof_path
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_profile_filename(self, profile):
|
||||
"""Returns the full profile name"""
|
||||
if profile.startswith('/'):
|
||||
# Remove leading /
|
||||
profile = profile[1:]
|
||||
else:
|
||||
profile = "profile_" + profile
|
||||
profile = profile.replace('/', '.')
|
||||
full_profilename = self.profile_dir + '/' + profile
|
||||
return full_profilename
|
208
utils/apparmor/severity.py
Normal file
208
utils/apparmor/severity.py
Normal file
|
@ -0,0 +1,208 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
from __future__ import with_statement
|
||||
import os
|
||||
import re
|
||||
from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp # , msg, error, debug
|
||||
|
||||
class Severity(object):
|
||||
def __init__(self, dbname=None, default_rank=10):
|
||||
"""Initialises the class object"""
|
||||
self.PROF_DIR = '/etc/apparmor.d' # The profile directory
|
||||
self.severity = dict()
|
||||
self.severity['DATABASENAME'] = dbname
|
||||
self.severity['CAPABILITIES'] = {}
|
||||
self.severity['FILES'] = {}
|
||||
self.severity['REGEXPS'] = {}
|
||||
self.severity['DEFAULT_RANK'] = default_rank
|
||||
# For variable expansions for the profile
|
||||
self.severity['VARIABLES'] = dict()
|
||||
if not dbname:
|
||||
return None
|
||||
|
||||
with open_file_read(dbname) as database: # open(dbname, 'r')
|
||||
for lineno, line in enumerate(database, start=1):
|
||||
line = line.strip() # or only rstrip and lstrip?
|
||||
if line == '' or line.startswith('#'):
|
||||
continue
|
||||
if line.startswith('/'):
|
||||
try:
|
||||
path, read, write, execute = line.split()
|
||||
read, write, execute = int(read), int(write), int(execute)
|
||||
except ValueError:
|
||||
raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line))
|
||||
else:
|
||||
if read not in range(0, 11) or write not in range(0, 11) or execute not in range(0, 11):
|
||||
raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line))
|
||||
path = path.lstrip('/')
|
||||
if '*' not in path:
|
||||
self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute}
|
||||
else:
|
||||
ptr = self.severity['REGEXPS']
|
||||
pieces = path.split('/')
|
||||
for index, piece in enumerate(pieces):
|
||||
if '*' in piece:
|
||||
path = '/'.join(pieces[index:])
|
||||
regexp = convert_regexp(path)
|
||||
ptr[regexp] = {'AA_RANK': {'r': read, 'w': write, 'x': execute}}
|
||||
break
|
||||
else:
|
||||
ptr[piece] = ptr.get(piece, {})
|
||||
ptr = ptr[piece]
|
||||
elif line.startswith('CAP_'):
|
||||
try:
|
||||
resource, severity = line.split()
|
||||
severity = int(severity)
|
||||
except ValueError as e:
|
||||
error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line)
|
||||
#error(error_message)
|
||||
raise AppArmorException(error_message) # from None
|
||||
else:
|
||||
if severity not in range(0, 11):
|
||||
raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line))
|
||||
self.severity['CAPABILITIES'][resource] = severity
|
||||
else:
|
||||
raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line))
|
||||
|
||||
def handle_capability(self, resource):
|
||||
"""Returns the severity of for the capability resource, default value if no match"""
|
||||
if resource in self.severity['CAPABILITIES'].keys():
|
||||
return self.severity['CAPABILITIES'][resource]
|
||||
# raise ValueError("unexpected capability rank input: %s"%resource)
|
||||
warn("unknown capability: %s" % resource)
|
||||
return self.severity['DEFAULT_RANK']
|
||||
|
||||
def check_subtree(self, tree, mode, sev, segments):
|
||||
"""Returns the max severity from the regex tree"""
|
||||
if len(segments) == 0:
|
||||
first = ''
|
||||
else:
|
||||
first = segments[0]
|
||||
rest = segments[1:]
|
||||
path = '/'.join([first] + rest)
|
||||
# Check if we have a matching directory tree to descend into
|
||||
if tree.get(first, False):
|
||||
sev = self.check_subtree(tree[first], mode, sev, rest)
|
||||
# If severity still not found, match against globs
|
||||
if sev is None:
|
||||
# Match against all globs at this directory level
|
||||
for chunk in tree.keys():
|
||||
if '*' in chunk:
|
||||
# Match rest of the path
|
||||
if re.search("^" + chunk, path):
|
||||
# Find max rank
|
||||
if "AA_RANK" in tree[chunk].keys():
|
||||
for m in mode:
|
||||
if sev is None or tree[chunk]["AA_RANK"].get(m, -1) > sev:
|
||||
sev = tree[chunk]["AA_RANK"].get(m, None)
|
||||
return sev
|
||||
|
||||
def handle_file(self, resource, mode):
|
||||
"""Returns the severity for the file, default value if no match found"""
|
||||
resource = resource[1:] # remove initial / from path
|
||||
pieces = resource.split('/') # break path into directory level chunks
|
||||
sev = None
|
||||
# Check for an exact match in the db
|
||||
if resource in self.severity['FILES'].keys():
|
||||
# Find max value among the given modes
|
||||
for m in mode:
|
||||
if sev is None or self.severity['FILES'][resource].get(m, -1) > sev:
|
||||
sev = self.severity['FILES'][resource].get(m, None)
|
||||
else:
|
||||
# Search regex tree for matching glob
|
||||
sev = self.check_subtree(self.severity['REGEXPS'], mode, sev, pieces)
|
||||
if sev is None:
|
||||
# Return default rank if severity cannot be found
|
||||
return self.severity['DEFAULT_RANK']
|
||||
else:
|
||||
return sev
|
||||
|
||||
def rank(self, resource, mode=None):
|
||||
"""Returns the rank for the resource file/capability"""
|
||||
if '@' in resource: # path contains variable
|
||||
return self.handle_variable_rank(resource, mode)
|
||||
elif resource[0] == '/': # file resource
|
||||
return self.handle_file(resource, mode)
|
||||
elif resource[0:4] == 'CAP_': # capability resource
|
||||
return self.handle_capability(resource)
|
||||
else:
|
||||
raise AppArmorException("Unexpected rank input: %s" % resource)
|
||||
|
||||
def handle_variable_rank(self, resource, mode):
|
||||
"""Returns the max possible rank for file resources containing variables"""
|
||||
regex_variable = re.compile('@{([^{.]*)}')
|
||||
rank = None
|
||||
if '@' in resource:
|
||||
variable = regex_variable.search(resource).groups()[0]
|
||||
variable = '@{%s}' % variable
|
||||
#variables = regex_variable.findall(resource)
|
||||
for replacement in self.severity['VARIABLES'][variable]:
|
||||
resource_replaced = self.variable_replace(variable, replacement, resource)
|
||||
rank_new = self.handle_variable_rank(resource_replaced, mode)
|
||||
#rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode)
|
||||
if rank is None or rank_new > rank:
|
||||
rank = rank_new
|
||||
return rank
|
||||
else:
|
||||
return self.handle_file(resource, mode)
|
||||
|
||||
def variable_replace(self, variable, replacement, resource):
|
||||
"""Returns the expanded path for the passed variable"""
|
||||
leading = False
|
||||
trailing = False
|
||||
# Check for leading or trailing / that may need to be collapsed
|
||||
if resource.find("/" + variable) != -1 and resource.find("//" + variable) == -1: # find that a single / exists before variable or not
|
||||
leading = True
|
||||
if resource.find(variable + "/") != -1 and resource.find(variable + "//") == -1:
|
||||
trailing = True
|
||||
if replacement[0] == '/' and replacement[:2] != '//' and leading: # finds if the replacement has leading / or not
|
||||
replacement = replacement[1:]
|
||||
if replacement[-1] == '/' and replacement[-2:] != '//' and trailing:
|
||||
replacement = replacement[:-1]
|
||||
return resource.replace(variable, replacement)
|
||||
|
||||
def load_variables(self, prof_path):
|
||||
"""Loads the variables for the given profile"""
|
||||
regex_include = re.compile('^#?include\s*<(\S*)>')
|
||||
if os.path.isfile(prof_path):
|
||||
with open_file_read(prof_path) as f_in:
|
||||
for line in f_in:
|
||||
line = line.strip()
|
||||
# If any includes, load variables from them first
|
||||
match = regex_include.search(line)
|
||||
if match:
|
||||
new_path = match.groups()[0]
|
||||
new_path = self.PROF_DIR + '/' + new_path
|
||||
self.load_variables(new_path)
|
||||
else:
|
||||
# Remove any comments
|
||||
if '#' in line:
|
||||
line = line.split('#')[0].rstrip()
|
||||
# Expected format is @{Variable} = value1 value2 ..
|
||||
if line.startswith('@') and '=' in line:
|
||||
if '+=' in line:
|
||||
line = line.split('+=')
|
||||
try:
|
||||
self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()]
|
||||
except KeyError:
|
||||
raise AppArmorException("Variable %s was not previously declared, but is being assigned additional value in file: %s" % (line[0], prof_path))
|
||||
else:
|
||||
line = line.split('=')
|
||||
if line[0] in self.severity['VARIABLES'].keys():
|
||||
raise AppArmorException("Variable %s was previously declared in file: %s" % (line[0], prof_path))
|
||||
self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()]
|
||||
|
||||
def unload_variables(self):
|
||||
"""Clears all loaded variables"""
|
||||
self.severity['VARIABLES'] = dict()
|
185
utils/apparmor/tools.py
Normal file
185
utils/apparmor/tools.py
Normal file
|
@ -0,0 +1,185 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import gettext
|
||||
import os
|
||||
import sys
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
from apparmor.common import user_perm
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
class aa_tools:
|
||||
def __init__(self, tool_name, args):
|
||||
self.name = tool_name
|
||||
self.profiledir = args.dir
|
||||
self.profiling = args.program
|
||||
self.check_profile_dir()
|
||||
self.silent = None
|
||||
|
||||
if tool_name in ['audit', 'complain']:
|
||||
self.remove = args.remove
|
||||
elif tool_name == 'disable':
|
||||
self.revert = args.revert
|
||||
self.disabledir = apparmor.profile_dir + '/disable'
|
||||
self.check_disable_dir()
|
||||
elif tool_name == 'autodep':
|
||||
self.force = args.force
|
||||
self.aa_mountpoint = apparmor.check_for_apparmor()
|
||||
elif tool_name == 'cleanprof':
|
||||
self.silent = args.silent
|
||||
|
||||
def check_profile_dir(self):
|
||||
if self.profiledir:
|
||||
apparmor.profile_dir = apparmor.get_full_path(self.profiledir)
|
||||
if not os.path.isdir(apparmor.profile_dir):
|
||||
raise apparmor.AppArmorException("%s is not a directory." % self.profiledir)
|
||||
|
||||
if not user_perm(apparmor.profile_dir):
|
||||
raise apparmor.AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir))
|
||||
|
||||
def check_disable_dir(self):
|
||||
if not os.path.isdir(self.disabledir):
|
||||
raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" % self.disabledir)
|
||||
|
||||
def act(self):
|
||||
for p in self.profiling:
|
||||
if not p:
|
||||
continue
|
||||
|
||||
program = None
|
||||
if os.path.exists(p):
|
||||
program = apparmor.get_full_path(p).strip()
|
||||
else:
|
||||
which = apparmor.which(p)
|
||||
if which:
|
||||
program = apparmor.get_full_path(which)
|
||||
|
||||
apparmor.read_profiles()
|
||||
#If program does not exists on the system but its profile does
|
||||
if not program and apparmor.profile_exists(p):
|
||||
program = p
|
||||
|
||||
if not program or not(os.path.exists(program) or apparmor.profile_exists(program)):
|
||||
if program and not program.startswith('/'):
|
||||
program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '')
|
||||
else:
|
||||
apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p)
|
||||
sys.exit(1)
|
||||
|
||||
if self.name == 'autodep' and program and os.path.exists(program):
|
||||
self.use_autodep(program)
|
||||
|
||||
elif program and apparmor.profile_exists(program):
|
||||
if self.name == 'cleanprof':
|
||||
self.clean_profile(program, p)
|
||||
|
||||
else:
|
||||
filename = apparmor.get_profile_filename(program)
|
||||
|
||||
if not os.path.isfile(filename) or apparmor.is_skippable_file(filename):
|
||||
apparmor.UI_Info(_('Profile for %s not found, skipping') % p)
|
||||
|
||||
elif self.name == 'disable':
|
||||
if not self.revert:
|
||||
apparmor.UI_Info(_('Disabling %s.') % program)
|
||||
self.disable_profile(filename)
|
||||
else:
|
||||
apparmor.UI_Info(_('Enabling %s.') % program)
|
||||
self.enable_profile(filename)
|
||||
|
||||
elif self.name == 'audit':
|
||||
if not self.remove:
|
||||
apparmor.UI_Info(_('Setting %s to audit mode.') % program)
|
||||
else:
|
||||
apparmor.UI_Info(_('Removing audit mode from %s.') % program)
|
||||
apparmor.change_profile_flags(filename, program, 'audit', not self.remove)
|
||||
|
||||
elif self.name == 'complain':
|
||||
if not self.remove:
|
||||
apparmor.set_complain(filename, program)
|
||||
else:
|
||||
apparmor.set_enforce(filename, program)
|
||||
#apparmor.set_profile_flags(filename, self.name)
|
||||
else:
|
||||
# One simply does not walk in here!
|
||||
raise apparmor.AppArmorException('Unknown tool: %s' % self.name)
|
||||
|
||||
cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s' % apparmor.profile_dir, '-R 2>&1', '1>/dev/null'])
|
||||
#cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null'])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
|
||||
else:
|
||||
if '/' not in p:
|
||||
apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (p, p))
|
||||
else:
|
||||
apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p)
|
||||
sys.exit(1)
|
||||
|
||||
def clean_profile(self, program, p):
|
||||
filename = apparmor.get_profile_filename(program)
|
||||
import apparmor.cleanprofile as cleanprofile
|
||||
prof = cleanprofile.Prof(filename)
|
||||
cleanprof = cleanprofile.CleanProf(True, prof, prof)
|
||||
deleted = cleanprof.remove_duplicate_rules(program)
|
||||
apparmor.UI_Info(_("\nDeleted %s rules.") % deleted)
|
||||
apparmor.changed[program] = True
|
||||
|
||||
if filename:
|
||||
if not self.silent:
|
||||
q = apparmor.hasher()
|
||||
q['title'] = 'Changed Local Profiles'
|
||||
q['headers'] = []
|
||||
q['explanation'] = _('The local profile for %s in file %s was changed. Would you like to save it?') % (program, filename)
|
||||
q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT']
|
||||
q['default'] = 'CMD_VIEW_CHANGES'
|
||||
q['options'] = []
|
||||
q['selected'] = 0
|
||||
p = None
|
||||
ans = ''
|
||||
arg = None
|
||||
while ans != 'CMD_SAVE_CHANGES':
|
||||
ans, arg = apparmor.UI_PromptUser(q)
|
||||
if ans == 'CMD_SAVE_CHANGES':
|
||||
apparmor.write_profile_ui_feedback(program)
|
||||
apparmor.reload_base(program)
|
||||
elif ans == 'CMD_VIEW_CHANGES':
|
||||
#oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '')
|
||||
newprofile = apparmor.serialize_profile(apparmor.aa[program], program, '')
|
||||
apparmor.display_changes_with_comments(filename, newprofile)
|
||||
else:
|
||||
apparmor.write_profile_ui_feedback(program)
|
||||
apparmor.reload_base(program)
|
||||
else:
|
||||
raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % p)
|
||||
|
||||
def use_autodep(self, program):
|
||||
apparmor.check_qualifiers(program)
|
||||
|
||||
if os.path.exists(apparmor.get_profile_filename(program) and not self.force):
|
||||
apparmor.UI_Info('Profile for %s already exists - skipping.' % program)
|
||||
else:
|
||||
apparmor.autodep(program)
|
||||
if self.aa_mountpoint:
|
||||
apparmor.reload(program)
|
||||
|
||||
def enable_profile(self, filename):
|
||||
apparmor.delete_symlink('disable', filename)
|
||||
|
||||
def disable_profile(self, filename):
|
||||
apparmor.create_symlink('disable', filename)
|
21
utils/apparmor/translations.py
Normal file
21
utils/apparmor/translations.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2014 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
import gettext
|
||||
|
||||
TRANSLATION_DOMAIN = 'apparmor-utils'
|
||||
|
||||
__apparmor_gettext__ = None
|
||||
|
||||
def init_translation():
|
||||
global __apparmor_gettext__
|
||||
if __apparmor_gettext__ is None:
|
||||
t = gettext.translation(TRANSLATION_DOMAIN, fallback=True)
|
||||
__apparmor_gettext__ = t.gettext
|
||||
return __apparmor_gettext__
|
457
utils/apparmor/ui.py
Normal file
457
utils/apparmor/ui.py
Normal file
|
@ -0,0 +1,457 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import gettext
|
||||
import sys
|
||||
import re
|
||||
from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast
|
||||
|
||||
from apparmor.common import readkey, AppArmorException, DebugLogger
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
# Set up UI logger for separate messages from UI module
|
||||
debug_logger = DebugLogger('UI')
|
||||
|
||||
# The operating mode: yast or text, text by default
|
||||
UI_mode = 'text'
|
||||
|
||||
ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}
|
||||
|
||||
def getkey():
|
||||
key = readkey()
|
||||
if key == '\x1B':
|
||||
key = readkey()
|
||||
if key == '[':
|
||||
key = readkey()
|
||||
if(ARROWS.get(key, False)):
|
||||
key = ARROWS[key]
|
||||
return key.strip()
|
||||
|
||||
def UI_Info(text):
|
||||
debug_logger.info(text)
|
||||
if UI_mode == 'text':
|
||||
sys.stdout.write(text + '\n')
|
||||
else:
|
||||
yastLog(text)
|
||||
|
||||
def UI_Important(text):
|
||||
debug_logger.debug(text)
|
||||
if UI_mode == 'text':
|
||||
sys.stdout.write('\n' + text + '\n')
|
||||
else:
|
||||
SendDataToYast({'type': 'dialog-error',
|
||||
'message': text
|
||||
})
|
||||
path, yarg = GetDataFromYast()
|
||||
|
||||
def get_translated_hotkey(translated, cmsg=''):
|
||||
msg = 'PromptUser: ' + _('Invalid hotkey for')
|
||||
|
||||
# Originally (\S) was used but with translations it would not work :(
|
||||
if re.search('\((\S+)\)', translated, re.LOCALE):
|
||||
return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0]
|
||||
else:
|
||||
if cmsg:
|
||||
raise AppArmorException(cmsg)
|
||||
else:
|
||||
raise AppArmorException('%s %s' % (msg, translated))
|
||||
|
||||
def UI_YesNo(text, default):
|
||||
debug_logger.debug('UI_YesNo: %s: %s %s' % (UI_mode, text, default))
|
||||
default = default.lower()
|
||||
ans = None
|
||||
if UI_mode == 'text':
|
||||
yes = _('(Y)es')
|
||||
no = _('(N)o')
|
||||
yeskey = get_translated_hotkey(yes).lower()
|
||||
nokey = get_translated_hotkey(no).lower()
|
||||
ans = 'XXXINVALIDXXX'
|
||||
while ans not in ['y', 'n']:
|
||||
sys.stdout.write('\n' + text + '\n')
|
||||
if default == 'y':
|
||||
sys.stdout.write('\n[%s] / %s\n' % (yes, no))
|
||||
else:
|
||||
sys.stdout.write('\n%s / [%s]\n' % (yes, no))
|
||||
ans = getkey()
|
||||
if ans:
|
||||
# Get back to english from localised answer
|
||||
ans = ans.lower()
|
||||
if ans == yeskey:
|
||||
ans = 'y'
|
||||
elif ans == nokey:
|
||||
ans = 'n'
|
||||
elif ans == 'left':
|
||||
default = 'y'
|
||||
elif ans == 'right':
|
||||
default = 'n'
|
||||
else:
|
||||
ans = 'XXXINVALIDXXX'
|
||||
continue # If user presses any other button ask again
|
||||
else:
|
||||
ans = default
|
||||
|
||||
else:
|
||||
SendDataToYast({'type': 'dialog-yesno',
|
||||
'question': text
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
ans = yarg['answer']
|
||||
if not ans:
|
||||
ans = default
|
||||
return ans
|
||||
|
||||
def UI_YesNoCancel(text, default):
|
||||
debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default))
|
||||
default = default.lower()
|
||||
ans = None
|
||||
if UI_mode == 'text':
|
||||
yes = _('(Y)es')
|
||||
no = _('(N)o')
|
||||
cancel = _('(C)ancel')
|
||||
|
||||
yeskey = get_translated_hotkey(yes).lower()
|
||||
nokey = get_translated_hotkey(no).lower()
|
||||
cancelkey = get_translated_hotkey(cancel).lower()
|
||||
|
||||
ans = 'XXXINVALIDXXX'
|
||||
while ans not in ['c', 'n', 'y']:
|
||||
sys.stdout.write('\n' + text + '\n')
|
||||
if default == 'y':
|
||||
sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel))
|
||||
elif default == 'n':
|
||||
sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel))
|
||||
else:
|
||||
sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel))
|
||||
ans = getkey()
|
||||
if ans:
|
||||
# Get back to english from localised answer
|
||||
ans = ans.lower()
|
||||
if ans == yeskey:
|
||||
ans = 'y'
|
||||
elif ans == nokey:
|
||||
ans = 'n'
|
||||
elif ans == cancelkey:
|
||||
ans = 'c'
|
||||
elif ans == 'left':
|
||||
if default == 'n':
|
||||
default = 'y'
|
||||
elif default == 'c':
|
||||
default = 'n'
|
||||
elif ans == 'right':
|
||||
if default == 'y':
|
||||
default = 'n'
|
||||
elif default == 'n':
|
||||
default = 'c'
|
||||
else:
|
||||
ans = default
|
||||
else:
|
||||
SendDataToYast({'type': 'dialog-yesnocancel',
|
||||
'question': text
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
ans = yarg['answer']
|
||||
if not ans:
|
||||
ans = default
|
||||
return ans
|
||||
|
||||
def UI_GetString(text, default):
|
||||
debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default))
|
||||
string = default
|
||||
if UI_mode == 'text':
|
||||
sys.stdout.write('\n' + text)
|
||||
string = sys.stdin.readline()
|
||||
else:
|
||||
SendDataToYast({'type': 'dialog-getstring',
|
||||
'label': text,
|
||||
'default': default
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
string = yarg['string']
|
||||
return string.strip()
|
||||
|
||||
def UI_GetFile(file):
|
||||
debug_logger.debug('UI_GetFile: %s' % UI_mode)
|
||||
filename = None
|
||||
if UI_mode == 'text':
|
||||
sys.stdout.write(file['description'] + '\n')
|
||||
filename = sys.stdin.read()
|
||||
else:
|
||||
file['type'] = 'dialog-getfile'
|
||||
SendDataToYast(file)
|
||||
ypath, yarg = GetDataFromYast()
|
||||
if yarg['answer'] == 'okay':
|
||||
filename = yarg['filename']
|
||||
return filename
|
||||
|
||||
def UI_BusyStart(message):
|
||||
debug_logger.debug('UI_BusyStart: %s' % UI_mode)
|
||||
if UI_mode == 'text':
|
||||
UI_Info(message)
|
||||
else:
|
||||
SendDataToYast({'type': 'dialog-busy-start',
|
||||
'message': message
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
|
||||
def UI_BusyStop():
|
||||
debug_logger.debug('UI_BusyStop: %s' % UI_mode)
|
||||
if UI_mode != 'text':
|
||||
SendDataToYast({'type': 'dialog-busy-stop'})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
|
||||
CMDS = {'CMD_ALLOW': _('(A)llow'),
|
||||
'CMD_OTHER': _('(M)ore'),
|
||||
'CMD_AUDIT_NEW': _('Audi(t)'),
|
||||
'CMD_AUDIT_OFF': _('Audi(t) off'),
|
||||
'CMD_AUDIT_FULL': _('Audit (A)ll'),
|
||||
#'CMD_OTHER': '(O)pts',
|
||||
'CMD_USER_ON': _('(O)wner permissions on'),
|
||||
'CMD_USER_OFF': _('(O)wner permissions off'),
|
||||
'CMD_DENY': _('(D)eny'),
|
||||
'CMD_ABORT': _('Abo(r)t'),
|
||||
'CMD_FINISHED': _('(F)inish'),
|
||||
'CMD_ix': _('(I)nherit'),
|
||||
'CMD_px': _('(P)rofile'),
|
||||
'CMD_px_safe': _('(P)rofile Clean Exec'),
|
||||
'CMD_cx': _('(C)hild'),
|
||||
'CMD_cx_safe': _('(C)hild Clean Exec'),
|
||||
'CMD_nx': _('(N)amed'),
|
||||
'CMD_nx_safe': _('(N)amed Clean Exec'),
|
||||
'CMD_ux': _('(U)nconfined'),
|
||||
'CMD_ux_safe': _('(U)nconfined Clean Exec'),
|
||||
'CMD_pix': _('(P)rofile Inherit'),
|
||||
'CMD_pix_safe': _('(P)rofile Inherit Clean Exec'),
|
||||
'CMD_cix': _('(C)hild Inherit'),
|
||||
'CMD_cix_safe': _('(C)hild Inherit Clean Exec'),
|
||||
'CMD_nix': _('(N)amed Inherit'),
|
||||
'CMD_nix_safe': _('(N)amed Inherit Clean Exec'),
|
||||
'CMD_EXEC_IX_ON': _('(X) ix On'),
|
||||
'CMD_EXEC_IX_OFF': _('(X) ix Off'),
|
||||
'CMD_SAVE': _('(S)ave Changes'),
|
||||
'CMD_CONTINUE': _('(C)ontinue Profiling'),
|
||||
'CMD_NEW': _('(N)ew'),
|
||||
'CMD_GLOB': _('(G)lob'),
|
||||
'CMD_GLOBEXT': _('Glob with (E)xtension'),
|
||||
'CMD_ADDHAT': _('(A)dd Requested Hat'),
|
||||
'CMD_USEDEFAULT': _('(U)se Default Hat'),
|
||||
'CMD_SCAN': _('(S)can system log for AppArmor events'),
|
||||
'CMD_HELP': _('(H)elp'),
|
||||
'CMD_VIEW_PROFILE': _('(V)iew Profile'),
|
||||
'CMD_USE_PROFILE': _('(U)se Profile'),
|
||||
'CMD_CREATE_PROFILE': _('(C)reate New Profile'),
|
||||
'CMD_UPDATE_PROFILE': _('(U)pdate Profile'),
|
||||
'CMD_IGNORE_UPDATE': _('(I)gnore Update'),
|
||||
'CMD_SAVE_CHANGES': _('(S)ave Changes'),
|
||||
'CMD_SAVE_SELECTED': _('Save Selec(t)ed Profile'),
|
||||
'CMD_UPLOAD_CHANGES': _('(U)pload Changes'),
|
||||
'CMD_VIEW_CHANGES': _('(V)iew Changes'),
|
||||
'CMD_VIEW_CHANGES_CLEAN': _('View Changes b/w (C)lean profiles'),
|
||||
'CMD_VIEW': _('(V)iew'),
|
||||
'CMD_ENABLE_REPO': _('(E)nable Repository'),
|
||||
'CMD_DISABLE_REPO': _('(D)isable Repository'),
|
||||
'CMD_ASK_NEVER': _('(N)ever Ask Again'),
|
||||
'CMD_ASK_LATER': _('Ask Me (L)ater'),
|
||||
'CMD_YES': _('(Y)es'),
|
||||
'CMD_NO': _('(N)o'),
|
||||
'CMD_ALL_NET': _('Allow All (N)etwork'),
|
||||
'CMD_NET_FAMILY': _('Allow Network Fa(m)ily'),
|
||||
'CMD_OVERWRITE': _('(O)verwrite Profile'),
|
||||
'CMD_KEEP': _('(K)eep Profile'),
|
||||
'CMD_CONTINUE': _('(C)ontinue'),
|
||||
'CMD_IGNORE_ENTRY': _('(I)gnore')
|
||||
}
|
||||
|
||||
def UI_PromptUser(q, params=''):
|
||||
cmd = None
|
||||
arg = None
|
||||
if UI_mode == 'text':
|
||||
cmd, arg = Text_PromptUser(q)
|
||||
else:
|
||||
q['type'] = 'wizard'
|
||||
SendDataToYast(q)
|
||||
ypath, yarg = GetDataFromYast()
|
||||
if not cmd:
|
||||
cmd = 'CMD_ABORT'
|
||||
arg = yarg['selected']
|
||||
if cmd == 'CMD_ABORT':
|
||||
confirm_and_abort()
|
||||
cmd = 'XXXINVALIDXXX'
|
||||
elif cmd == 'CMD_FINISHED':
|
||||
if not params:
|
||||
confirm_and_finish()
|
||||
cmd = 'XXXINVALIDXXX'
|
||||
return (cmd, arg)
|
||||
|
||||
def confirm_and_abort():
|
||||
ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
|
||||
if ans == 'y':
|
||||
UI_Info(_('Abandoning all changes.'))
|
||||
#shutdown_yast()
|
||||
#for prof in created:
|
||||
# delete_profile(prof)
|
||||
sys.exit(0)
|
||||
|
||||
def UI_ShortMessage(title, message):
|
||||
SendDataToYast({'type': 'short-dialog-message',
|
||||
'headline': title,
|
||||
'message': message
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
|
||||
def UI_LongMessage(title, message):
|
||||
SendDataToYast({'type': 'long-dialog-message',
|
||||
'headline': title,
|
||||
'message': message
|
||||
})
|
||||
ypath, yarg = GetDataFromYast()
|
||||
|
||||
def confirm_and_finish():
|
||||
sys.stdout.write(_('FINISHING...\n'))
|
||||
sys.exit(0)
|
||||
|
||||
def Text_PromptUser(question):
|
||||
title = question['title']
|
||||
explanation = question['explanation']
|
||||
headers = question['headers']
|
||||
functions = question['functions']
|
||||
|
||||
default = question['default']
|
||||
options = question['options']
|
||||
selected = question.get('selected', 0)
|
||||
helptext = question['helptext']
|
||||
if helptext:
|
||||
functions.append('CMD_HELP')
|
||||
|
||||
menu_items = []
|
||||
keys = dict()
|
||||
|
||||
for cmd in functions:
|
||||
if not CMDS.get(cmd, False):
|
||||
raise AppArmorException(_('PromptUser: Unknown command %s') % cmd)
|
||||
|
||||
menutext = CMDS[cmd]
|
||||
|
||||
key = get_translated_hotkey(menutext).lower()
|
||||
# Duplicate hotkey
|
||||
if keys.get(key, False):
|
||||
raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext))
|
||||
|
||||
keys[key] = cmd
|
||||
|
||||
if default and default == cmd:
|
||||
menutext = '[%s]' % menutext
|
||||
|
||||
menu_items.append(menutext)
|
||||
|
||||
default_key = 0
|
||||
if default and CMDS[default]:
|
||||
defaulttext = CMDS[default]
|
||||
defmsg = _('PromptUser: Invalid hotkey in default item')
|
||||
|
||||
default_key = get_translated_hotkey(defaulttext, defmsg).lower()
|
||||
|
||||
if not keys.get(default_key, False):
|
||||
raise AppArmorException(_('PromptUser: Invalid default %s') % default)
|
||||
|
||||
widest = 0
|
||||
header_copy = headers[:]
|
||||
while header_copy:
|
||||
header = header_copy.pop(0)
|
||||
header_copy.pop(0)
|
||||
if len(header) > widest:
|
||||
widest = len(header)
|
||||
widest += 1
|
||||
|
||||
formatstr = '%-' + str(widest) + 's %s\n'
|
||||
|
||||
function_regexp = '^('
|
||||
function_regexp += '|'.join(keys.keys())
|
||||
if options:
|
||||
function_regexp += '|\d'
|
||||
function_regexp += ')$'
|
||||
|
||||
ans = 'XXXINVALIDXXX'
|
||||
while not re.search(function_regexp, ans, flags=re.IGNORECASE):
|
||||
|
||||
prompt = '\n'
|
||||
if title:
|
||||
prompt += '= %s =\n\n' % title
|
||||
|
||||
if headers:
|
||||
header_copy = headers[:]
|
||||
while header_copy:
|
||||
header = header_copy.pop(0)
|
||||
value = header_copy.pop(0)
|
||||
prompt += formatstr % (header + ':', value)
|
||||
prompt += '\n'
|
||||
|
||||
if explanation:
|
||||
prompt += explanation + '\n\n'
|
||||
|
||||
if options:
|
||||
for index, option in enumerate(options):
|
||||
if selected == index:
|
||||
format_option = ' [%s - %s]'
|
||||
else:
|
||||
format_option = ' %s - %s '
|
||||
prompt += format_option % (index + 1, option)
|
||||
prompt += '\n'
|
||||
|
||||
prompt += ' / '.join(menu_items)
|
||||
|
||||
sys.stdout.write(prompt + '\n')
|
||||
|
||||
ans = getkey().lower()
|
||||
|
||||
if ans:
|
||||
if ans == 'up':
|
||||
if options and selected > 0:
|
||||
selected -= 1
|
||||
ans = 'XXXINVALIDXXX'
|
||||
|
||||
elif ans == 'down':
|
||||
if options and selected < len(options) - 1:
|
||||
selected += 1
|
||||
ans = 'XXXINVALIDXXX'
|
||||
|
||||
# elif keys.get(ans, False) == 'CMD_HELP':
|
||||
# sys.stdout.write('\n%s\n' %helptext)
|
||||
# ans = 'XXXINVALIDXXX'
|
||||
|
||||
elif is_number(ans) == 10:
|
||||
# If they hit return choose default option
|
||||
ans = default_key
|
||||
|
||||
elif options and re.search('^\d$', ans):
|
||||
ans = int(ans)
|
||||
if ans > 0 and ans <= len(options):
|
||||
selected = ans - 1
|
||||
ans = 'XXXINVALIDXXX'
|
||||
|
||||
if keys.get(ans, False) == 'CMD_HELP':
|
||||
sys.stdout.write('\n%s\n' % helptext)
|
||||
ans = 'again'
|
||||
|
||||
if keys.get(ans, False):
|
||||
ans = keys[ans]
|
||||
|
||||
return ans, selected
|
||||
|
||||
def is_number(number):
|
||||
try:
|
||||
return int(number)
|
||||
except:
|
||||
return False
|
103
utils/apparmor/yasti.py
Normal file
103
utils/apparmor/yasti.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import re
|
||||
#import ycp
|
||||
import os
|
||||
import sys
|
||||
|
||||
from apparmor.common import error, DebugLogger
|
||||
|
||||
# Set up UI logger for separate messages from YaST module
|
||||
debug_logger = DebugLogger('YaST')
|
||||
|
||||
|
||||
def setup_yast():
|
||||
# To-Do
|
||||
pass
|
||||
|
||||
def shutdown_yast():
|
||||
# To-Do
|
||||
pass
|
||||
|
||||
def yastLog(text):
|
||||
ycp.y2milestone(text)
|
||||
|
||||
def SendDataToYast(data):
|
||||
debug_logger.info('SendDataToYast: Waiting for YCP command')
|
||||
for line in sys.stdin:
|
||||
ycommand, ypath, yargument = ParseCommand(line)
|
||||
if ycommand and ycommand == 'Read':
|
||||
debug_logger.info('SendDataToYast: Sending--%s' % data)
|
||||
Return(data)
|
||||
return True
|
||||
else:
|
||||
debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line)
|
||||
error('SendDataToYast: didn\'t receive YCP command before connection died')
|
||||
|
||||
def GetDataFromYast():
|
||||
debug_logger.inf('GetDataFromYast: Waiting for YCP command')
|
||||
for line in sys.stdin:
|
||||
debug_logger.info('GetDataFromYast: YCP: %s' % line)
|
||||
ycommand, ypath, yarg = ParseCommand(line)
|
||||
debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg)
|
||||
if ycommand and ycommand == 'Write':
|
||||
Return('true')
|
||||
return ypath, yarg
|
||||
else:
|
||||
debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line)
|
||||
error('GetDataFromYast: didn\'t receive YCP command before connection died')
|
||||
|
||||
def ParseCommand(commands):
|
||||
term = ParseTerm(commands)
|
||||
if term:
|
||||
command = term[0]
|
||||
term = term[1:]
|
||||
else:
|
||||
command = ''
|
||||
path = ''
|
||||
pathref = None
|
||||
if term:
|
||||
pathref = term[0]
|
||||
term = term[1:]
|
||||
if pathref:
|
||||
if pathref.strip():
|
||||
path = pathref.strip()
|
||||
elif command != 'result':
|
||||
ycp.y2error('The first arguement is not a path. (%s)' % pathref)
|
||||
argument = None
|
||||
if term:
|
||||
argument = term[0]
|
||||
if len(term) > 1:
|
||||
ycp.y2warning('Superfluous command arguments ignored')
|
||||
return (command, path, argument)
|
||||
|
||||
def ParseTerm(inp):
|
||||
regex_term = re.compile('^\s*`?(\w*)\s*')
|
||||
term = regex_term.search(inp)
|
||||
ret = []
|
||||
symbol = None
|
||||
if term:
|
||||
symbol = term.groups()[0]
|
||||
else:
|
||||
ycp.y2error('No term symbol')
|
||||
ret.append(symbol)
|
||||
inp = regex_term.sub('', inp)
|
||||
if not inp.startswith('('):
|
||||
ycp.y2error('No term parantheses')
|
||||
argref, err, rest = ParseYcpTermBody(inp)
|
||||
if err:
|
||||
ycp.y2error('%s (%s)' % (err, rest))
|
||||
else:
|
||||
ret += argref
|
||||
return ret
|
10
utils/po/README
Normal file
10
utils/po/README
Normal file
|
@ -0,0 +1,10 @@
|
|||
GENERATING TRANSLATION MESSAGES
|
||||
|
||||
To generate the messages.pot file:
|
||||
|
||||
Run the following command in Translate.
|
||||
python pygettext.py ../apparmor/*.py ../Tools/aa*
|
||||
|
||||
It will generate the messages.pot file in the Translate directory.
|
||||
|
||||
You might need to provide the full path to pygettext.py from your python installation. It will typically be in the /path/to/python/libs/Tools/i18n/pygettext.py
|
149
utils/test/aa_test.py
Normal file
149
utils/test/aa_test.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
sys.path.append('../')
|
||||
import apparmor.aa
|
||||
import apparmor.logparser
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC,
|
||||
'w': apparmor.aamode.AA_MAY_WRITE,
|
||||
'r': apparmor.aamode.AA_MAY_READ,
|
||||
'a': apparmor.aamode.AA_MAY_APPEND,
|
||||
'l': apparmor.aamode.AA_MAY_LINK,
|
||||
'k': apparmor.aamode.AA_MAY_LOCK,
|
||||
'm': apparmor.aamode.AA_EXEC_MMAP,
|
||||
'i': apparmor.aamode.AA_EXEC_INHERIT,
|
||||
'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE,
|
||||
'U': apparmor.aamode.AA_EXEC_UNCONFINED,
|
||||
'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE,
|
||||
'P': apparmor.aamode.AA_EXEC_PROFILE,
|
||||
'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE,
|
||||
'C': apparmor.aamode.AA_EXEC_CHILD,
|
||||
}
|
||||
|
||||
def test_loadinclude(self):
|
||||
apparmor.aa.loadincludes()
|
||||
|
||||
def test_path_globs(self):
|
||||
globs = {
|
||||
'/foo/': '/*/',
|
||||
'/foo': '/*',
|
||||
'/b*': '/*',
|
||||
'/*b': '/*',
|
||||
'/*': '/*',
|
||||
'/*/': '/*/',
|
||||
'/*.foo/': '/*/',
|
||||
'/**.foo/': '/**/',
|
||||
'/foo/*/': '/**/',
|
||||
'/usr/foo/*': '/usr/**',
|
||||
'/usr/foo/**': '/usr/**',
|
||||
'/usr/foo/bar**': '/usr/foo/**',
|
||||
'/usr/foo/**bar': '/usr/foo/**',
|
||||
'/usr/bin/foo**bar': '/usr/bin/**',
|
||||
'/usr/foo/**/bar': '/usr/foo/**/*',
|
||||
'/usr/foo/**/*': '/usr/foo/**',
|
||||
'/usr/foo/*/bar': '/usr/foo/*/*',
|
||||
'/usr/bin/foo*bar': '/usr/bin/*',
|
||||
'/usr/bin/*foo*': '/usr/bin/*',
|
||||
'/usr/foo/*/*': '/usr/foo/**',
|
||||
'/usr/foo/*/**': '/usr/foo/**',
|
||||
'/**': '/**',
|
||||
'/**/': '/**/'
|
||||
}
|
||||
for path in globs.keys():
|
||||
self.assertEqual(apparmor.aa.glob_path(path), globs[path], 'Unexpected glob generated for path: %s'%path)
|
||||
|
||||
def test_path_withext_globs(self):
|
||||
globs = {
|
||||
'/foo/bar': '/foo/bar',
|
||||
'/foo/**/bar': '/foo/**/bar',
|
||||
'/foo.bar': '/*.bar',
|
||||
'/*.foo': '/*.foo' ,
|
||||
'/usr/*.bar': '/**.bar',
|
||||
'/usr/**.bar': '/**.bar',
|
||||
'/usr/foo**.bar': '/usr/**.bar',
|
||||
'/usr/foo*.bar': '/usr/*.bar',
|
||||
'/usr/fo*oo.bar': '/usr/*.bar',
|
||||
'/usr/*foo*.bar': '/usr/*.bar',
|
||||
'/usr/**foo.bar': '/usr/**.bar',
|
||||
'/usr/*foo.bar': '/usr/*.bar',
|
||||
'/usr/foo.b*': '/usr/*.b*'
|
||||
}
|
||||
for path in globs.keys():
|
||||
self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path)
|
||||
|
||||
def test_parse_event(self):
|
||||
parser = apparmor.logparser.ReadLog('', '', '', '', '')
|
||||
event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30'
|
||||
parsed_event = parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name')
|
||||
self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], set(['w', 'a', '::w', '::a']))
|
||||
#print(parsed_event)
|
||||
|
||||
#event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0'
|
||||
#parsed_event = apparmor.aa.parse_event(event)
|
||||
#print(parsed_event)
|
||||
|
||||
event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000'
|
||||
parsed_event = parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name')
|
||||
self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], set(['r', 'w', 'a','::r' , '::w', '::a']))
|
||||
#print(parsed_event)
|
||||
|
||||
|
||||
def test_modes_to_string(self):
|
||||
|
||||
|
||||
for string in self.MODE_TEST.keys():
|
||||
mode = self.MODE_TEST[string]
|
||||
self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string))
|
||||
|
||||
def test_string_to_modes(self):
|
||||
|
||||
#self.assertEqual(apparmor.aa.str_to_mode('wc'), 32270)
|
||||
MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC,
|
||||
'w': apparmor.aamode.AA_MAY_WRITE,
|
||||
'r': apparmor.aamode.AA_MAY_READ,
|
||||
'a': apparmor.aamode.AA_MAY_APPEND,
|
||||
'l': apparmor.aamode.AA_MAY_LINK,
|
||||
'k': apparmor.aamode.AA_MAY_LOCK,
|
||||
'm': apparmor.aamode.AA_EXEC_MMAP,
|
||||
'i': apparmor.aamode.AA_EXEC_INHERIT,
|
||||
'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, # Unconfined + Unsafe
|
||||
'U': apparmor.aamode.AA_EXEC_UNCONFINED,
|
||||
'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, # Profile + unsafe
|
||||
'P': apparmor.aamode.AA_EXEC_PROFILE,
|
||||
'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe
|
||||
'C': apparmor.aamode.AA_EXEC_CHILD,
|
||||
}
|
||||
|
||||
#while MODE_TEST:
|
||||
# string,mode = MODE_TEST.popitem()
|
||||
# self.assertEqual(apparmor.aamode.str_to_mode(string), mode)
|
||||
|
||||
#self.assertEqual(apparmor.aa.str_to_mode('C'), 2048)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
19
utils/test/cleanprof_test.in
Normal file
19
utils/test/cleanprof_test.in
Normal file
|
@ -0,0 +1,19 @@
|
|||
# A simple test comment which will persist
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/bin/a/simple/cleanprof/test/profile {
|
||||
# Just for the heck of it, this comment wont see the day of light
|
||||
#include <abstractions/base>
|
||||
#Below rule comes from abstractions/base
|
||||
allow /usr/share/X11/locale/** r,
|
||||
allow /home/*/** r,
|
||||
allow /home/foo/bar r,
|
||||
allow /home/foo/** w,
|
||||
}
|
||||
|
||||
/usr/bin/other/cleanprof/test/profile {
|
||||
# This one shouldn't be affected by the processing
|
||||
# However this comment will be wiped, need to change that
|
||||
allow /home/*/** rw,
|
||||
allow /home/foo/bar r,
|
||||
}
|
17
utils/test/cleanprof_test.out
Normal file
17
utils/test/cleanprof_test.out
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <tunables/global>
|
||||
|
||||
# A simple test comment which will persist
|
||||
|
||||
|
||||
/usr/bin/a/simple/cleanprof/test/profile {
|
||||
#include <abstractions/base>
|
||||
|
||||
/home/*/** r,
|
||||
/home/foo/** w,
|
||||
|
||||
}
|
||||
/usr/bin/other/cleanprof/test/profile {
|
||||
/home/*/** rw,
|
||||
/home/foo/bar r,
|
||||
|
||||
}
|
41
utils/test/common_test.py
Normal file
41
utils/test/common_test.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import unittest
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.append('../')
|
||||
import apparmor.common
|
||||
import apparmor.config
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
|
||||
def test_RegexParser(self):
|
||||
tests = apparmor.config.Config('ini')
|
||||
tests.CONF_DIR = '.'
|
||||
regex_tests = tests.read_config('regex_tests.ini')
|
||||
for regex in regex_tests.sections():
|
||||
parsed_regex = re.compile(apparmor.common.convert_regexp(regex))
|
||||
for regex_testcase in regex_tests.options(regex):
|
||||
self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex: %s' %regex)
|
||||
|
||||
#def test_readkey(self):
|
||||
# print("Please press the Y button on the keyboard.")
|
||||
# self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.test_RegexParser']
|
||||
unittest.main()
|
52
utils/test/config_test.py
Normal file
52
utils/test/config_test.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
sys.path.append('../')
|
||||
import apparmor.config as config
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
|
||||
def test_IniConfig(self):
|
||||
ini_config = config.Config('ini')
|
||||
ini_config.CONF_DIR = '.'
|
||||
conf = ini_config.read_config('logprof.conf')
|
||||
logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs']
|
||||
logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes']
|
||||
logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser'
|
||||
|
||||
self.assertEqual(conf.sections(), logprof_sections)
|
||||
self.assertEqual(conf.options('settings'), logprof_sections_options)
|
||||
self.assertEqual(conf['settings']['parser'], logprof_settings_parser)
|
||||
|
||||
def test_ShellConfig(self):
|
||||
shell_config = config.Config('shell')
|
||||
shell_config.CONF_DIR = '.'
|
||||
conf = shell_config.read_config('easyprof.conf')
|
||||
easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR']
|
||||
easyprof_Policygroup = '/usr/share/apparmor/easyprof/policygroups'
|
||||
easyprof_Templates = '/usr/share/apparmor/easyprof/templates'
|
||||
|
||||
self.assertEqual(sorted(list(conf[''].keys())), sorted(easyprof_sections))
|
||||
self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup)
|
||||
self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testConfig']
|
||||
unittest.main()
|
131
utils/test/logprof.conf
Normal file
131
utils/test/logprof.conf
Normal file
|
@ -0,0 +1,131 @@
|
|||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2004-2006 Novell/SUSE
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
[settings]
|
||||
profiledir = /etc/apparmor.d /etc/subdomain.d
|
||||
inactive_profiledir = /usr/share/doc/apparmor-profiles/extras
|
||||
logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages
|
||||
|
||||
parser = /sbin/apparmor_parser /sbin/subdomain_parser
|
||||
ldd = /usr/bin/ldd
|
||||
logger = /bin/logger /usr/bin/logger
|
||||
|
||||
# customize how file ownership permissions are presented
|
||||
# 0 - off
|
||||
# 1 - default of what ever mode the log reported
|
||||
# 2 - force the new permissions to be user
|
||||
# 3 - force all perms on the rule to be user
|
||||
default_owner_prompt = 1
|
||||
|
||||
# custom directory locations to look for #includes
|
||||
#
|
||||
# each name should be a valid directory containing possible #include
|
||||
# candidate files under the profile dir which by default is /etc/apparmor.d.
|
||||
#
|
||||
# So an entry of my-includes will allow /etc/apparmor.d/my-includes to
|
||||
# be used by the yast UI and profiling tools as a source of #include
|
||||
# files.
|
||||
custom_includes =
|
||||
|
||||
|
||||
[repository]
|
||||
distro = ubuntu-intrepid
|
||||
url = http://apparmor.test.opensuse.org/backend/api
|
||||
preferred_user = ubuntu
|
||||
|
||||
[qualifiers]
|
||||
# things will be painfully broken if bash has a profile
|
||||
/bin/bash = icnu
|
||||
/bin/ksh = icnu
|
||||
/bin/dash = icnu
|
||||
|
||||
# these programs can't function if they're confined
|
||||
/bin/mount = u
|
||||
/etc/init.d/subdomain = u
|
||||
/sbin/cardmgr = u
|
||||
/sbin/subdomain_parser = u
|
||||
/usr/sbin/genprof = u
|
||||
/usr/sbin/logprof = u
|
||||
/usr/lib/YaST2/servers_non_y2/ag_genprof = u
|
||||
/usr/lib/YaST2/servers_non_y2/ag_logprof = u
|
||||
|
||||
# these ones shouln't have their own profiles
|
||||
/bin/awk = icn
|
||||
/bin/cat = icn
|
||||
/bin/chmod = icn
|
||||
/bin/chown = icn
|
||||
/bin/cp = icn
|
||||
/bin/gawk = icn
|
||||
/bin/grep = icn
|
||||
/bin/gunzip = icn
|
||||
/bin/gzip = icn
|
||||
/bin/kill = icn
|
||||
/bin/ln = icn
|
||||
/bin/ls = icn
|
||||
/bin/mkdir = icn
|
||||
/bin/mv = icn
|
||||
/bin/readlink = icn
|
||||
/bin/rm = icn
|
||||
/bin/sed = icn
|
||||
/bin/touch = icn
|
||||
/sbin/killall5 = icn
|
||||
/usr/bin/find = icn
|
||||
/usr/bin/killall = icn
|
||||
/usr/bin/nice = icn
|
||||
/usr/bin/perl = icn
|
||||
/usr/bin/tr = icn
|
||||
|
||||
[required_hats]
|
||||
^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT
|
||||
^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT
|
||||
|
||||
[defaulthat]
|
||||
^.+/apache(|2|2-prefork)$ = DEFAULT_URI
|
||||
^.+/httpd(|2|2-prefork)$ = DEFAULT_URI
|
||||
|
||||
[globs]
|
||||
# /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib*
|
||||
/lib/lib[^\/]+so[^\/]*$ = /lib/lib*so*
|
||||
|
||||
# strip kernel version numbers from kernel module accesses
|
||||
^/lib/modules/[^\/]+\/ = /lib/modules/*/
|
||||
|
||||
# strip pid numbers from /proc accesses
|
||||
^/proc/\d+/ = /proc/*/
|
||||
|
||||
# if it looks like a home directory, glob out the username
|
||||
^/home/[^\/]+ = /home/*
|
||||
|
||||
# if they use any perl modules, grant access to all
|
||||
^/usr/lib/perl5/.+$ = /usr/lib/perl5/**
|
||||
|
||||
# locale foo
|
||||
^/usr/lib/locale/.+$ = /usr/lib/locale/**
|
||||
^/usr/share/locale/.+$ = /usr/share/locale/**
|
||||
|
||||
# timezone fun
|
||||
^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/**
|
||||
|
||||
# /foobar/fonts/baz -> /foobar/fonts/**
|
||||
/fonts/.+$ = /fonts/**
|
||||
|
||||
# turn /foo/bar/baz.8907234 into /foo/bar/baz.*
|
||||
# BUGBUG - this one looked weird because it would suggest a glob for
|
||||
# BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.*
|
||||
# \.\d+$ = .*
|
||||
|
||||
# some various /etc/security poo -- dunno about these ones...
|
||||
^/etc/security/_[^\/]+$ = /etc/security/*
|
||||
^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/*
|
||||
^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so
|
||||
|
||||
^/etc/pam.d/[^\/]+$ = /etc/pam.d/*
|
||||
^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh
|
||||
|
153
utils/test/minitools_test.py
Normal file
153
utils/test/minitools_test.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import atexit
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
import filecmp
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
|
||||
# Path for the program
|
||||
test_path = '/usr/sbin/ntpd'
|
||||
# Path for the target file containing profile
|
||||
local_profilename = './profiles/usr.sbin.ntpd'
|
||||
|
||||
python_interpreter = 'python'
|
||||
if sys.version_info >= (3, 0):
|
||||
python_interpreter = 'python3'
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_audit(self):
|
||||
#Set ntpd profile to audit mode and check if it was correctly set
|
||||
str(subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True))
|
||||
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Audit flag could not be set in profile %s'%local_profilename)
|
||||
|
||||
#Remove audit mode from ntpd profile and check if it was correctly removed
|
||||
subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename)
|
||||
|
||||
|
||||
def test_complain(self):
|
||||
#Set ntpd profile to complain mode and check if it was correctly set
|
||||
subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename)
|
||||
|
||||
#Set ntpd profile to enforce mode and check if it was correctly set
|
||||
subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename)
|
||||
self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename)
|
||||
|
||||
# Set audit flag and then complain flag in a profile
|
||||
subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True)
|
||||
subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename)
|
||||
|
||||
#Remove complain flag first i.e. set to enforce mode
|
||||
subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename)
|
||||
self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename)
|
||||
|
||||
#Remove audit flag
|
||||
subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
def test_enforce(self):
|
||||
#Set ntpd profile to complain mode and check if it was correctly set
|
||||
subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename)
|
||||
|
||||
|
||||
#Set ntpd profile to enforce mode and check if it was correctly set
|
||||
subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename)
|
||||
self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename)
|
||||
self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename)
|
||||
|
||||
|
||||
def test_disable(self):
|
||||
#Disable the ntpd profile and check if it was correctly disabled
|
||||
subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename)
|
||||
|
||||
#Enable the ntpd profile and check if it was correctly re-enabled
|
||||
subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles -r %s'%(python_interpreter, test_path), shell=True)
|
||||
|
||||
self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename)
|
||||
|
||||
|
||||
def test_autodep(self):
|
||||
pass
|
||||
|
||||
def test_unconfined(self):
|
||||
output = subprocess.check_output('%s ./../Tools/aa-unconfined'%python_interpreter, shell=True)
|
||||
|
||||
output_force = subprocess.check_output('%s ./../Tools/aa-unconfined --paranoid'%python_interpreter, shell=True)
|
||||
|
||||
self.assertIsNot(output, '', 'Failed to run aa-unconfined')
|
||||
|
||||
self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode')
|
||||
|
||||
|
||||
def test_cleanprof(self):
|
||||
input_file = 'cleanprof_test.in'
|
||||
output_file = 'cleanprof_test.out'
|
||||
#We position the local testfile
|
||||
shutil.copy('./%s'%input_file, './profiles')
|
||||
#Our silly test program whose profile we wish to clean
|
||||
cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile'
|
||||
|
||||
subprocess.check_output('%s ./../Tools/aa-cleanprof -d ./profiles -s %s' % (python_interpreter, cleanprof_test), shell=True)
|
||||
|
||||
#Strip off the first line (#modified line)
|
||||
subprocess.check_output('sed -i 1d ./profiles/%s'%(input_file), shell=True)
|
||||
|
||||
self.assertEqual(filecmp.cmp('./profiles/%s'%input_file, './%s'%output_file, False), True, 'Failed to cleanup profile properly')
|
||||
|
||||
|
||||
def clean_profile_dir():
|
||||
#Wipe the local profiles from the test directory
|
||||
shutil.rmtree('./profiles')
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
|
||||
if os.path.exists('./profiles'):
|
||||
shutil.rmtree('./profiles')
|
||||
|
||||
#copy the local profiles to the test directory
|
||||
#Should be the set of cleanprofile
|
||||
shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True)
|
||||
|
||||
apparmor.profile_dir = './profiles'
|
||||
|
||||
atexit.register(clean_profile_dir)
|
||||
|
||||
unittest.main()
|
51
utils/test/regex_tests.ini
Normal file
51
utils/test/regex_tests.ini
Normal file
|
@ -0,0 +1,51 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
[/foo/**/bar/]
|
||||
/foo/user/tools/bar/ = True
|
||||
/foo/apparmor/bar/ = True
|
||||
/foo/apparmor/bar = False
|
||||
|
||||
[/foo/*/bar/]
|
||||
/foo/apparmor/bar/ = True
|
||||
/foo/apparmor/tools/bar/ = False
|
||||
/foo/apparmor/bar = False
|
||||
|
||||
[/foo/{foo,bar,user,other}/bar/]
|
||||
/foo/user/bar/ = True
|
||||
/foo/bar/bar/ = True
|
||||
/foo/wrong/bar/ = False
|
||||
|
||||
[/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/]
|
||||
/foo/user/test,ca}se/aa/bar/ = True
|
||||
/foo/bar/test,ca}se/sd/bar/ = True
|
||||
/foo/wrong/user/bar/ = False
|
||||
/foo/user/wrong/bar/ = False
|
||||
/foo/wrong/aa/bar/ = False
|
||||
|
||||
[/foo/user/ba?/]
|
||||
/foo/user/bar/ = True
|
||||
/foo/user/bar/apparmor/ = False
|
||||
/foo/user/ba/ = False
|
||||
/foo/user/ba// = False
|
||||
|
||||
[/foo/user/bar/**]
|
||||
/foo/user/bar/apparmor = True
|
||||
/foo/user/bar/apparmor/tools = True
|
||||
/foo/user/bar/ = False
|
||||
|
||||
[/foo/user/bar/*]
|
||||
/foo/user/bar/apparmor = True
|
||||
/foo/user/bar/apparmor/tools = False
|
||||
/foo/user/bar/ = False
|
||||
/foo/user/bar/apparmor/ = False
|
1
utils/test/runtests-py2.sh
Normal file
1
utils/test/runtests-py2.sh
Normal file
|
@ -0,0 +1 @@
|
|||
for file in *.py ; do echo "running $file..." ; python $file; echo; done
|
1
utils/test/runtests-py3.sh
Normal file
1
utils/test/runtests-py3.sh
Normal file
|
@ -0,0 +1 @@
|
|||
for file in *.py ; do echo "running $file..." ; python3 $file; echo; done
|
460
utils/test/severity.db
Normal file
460
utils/test/severity.db
Normal file
|
@ -0,0 +1,460 @@
|
|||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# Allow this process to 0wn the machine:
|
||||
CAP_SYS_ADMIN 10
|
||||
CAP_SYS_CHROOT 10
|
||||
CAP_SYS_MODULE 10
|
||||
CAP_SYS_PTRACE 10
|
||||
CAP_SYS_RAWIO 10
|
||||
CAP_MAC_ADMIN 10
|
||||
CAP_MAC_OVERRIDE 10
|
||||
# Allow other processes to 0wn the machine:
|
||||
CAP_SETPCAP 9
|
||||
CAP_SETFCAP 9
|
||||
CAP_CHOWN 9
|
||||
CAP_FSETID 9
|
||||
CAP_MKNOD 9
|
||||
CAP_LINUX_IMMUTABLE 9
|
||||
CAP_DAC_OVERRIDE 9
|
||||
CAP_SETGID 9
|
||||
CAP_SETUID 9
|
||||
CAP_FOWNER 9
|
||||
# Denial of service, bypass audit controls, information leak
|
||||
CAP_SYS_TIME 8
|
||||
CAP_NET_ADMIN 8
|
||||
CAP_SYS_RESOURCE 8
|
||||
CAP_KILL 8
|
||||
CAP_IPC_OWNER 8
|
||||
CAP_SYS_PACCT 8
|
||||
CAP_SYS_BOOT 8
|
||||
CAP_NET_BIND_SERVICE 8
|
||||
CAP_NET_RAW 8
|
||||
CAP_SYS_NICE 8
|
||||
CAP_LEASE 8
|
||||
CAP_IPC_LOCK 8
|
||||
CAP_SYS_TTY_CONFIG 8
|
||||
CAP_AUDIT_CONTROL 8
|
||||
CAP_AUDIT_WRITE 8
|
||||
CAP_SYSLOG 8
|
||||
CAP_WAKE_ALARM 8
|
||||
CAP_BLOCK_SUSPEND 8
|
||||
CAP_DAC_READ_SEARCH 7
|
||||
# unused
|
||||
CAP_NET_BROADCAST 0
|
||||
|
||||
# filename r w x
|
||||
# 'hard drives' are generally 4 10 0
|
||||
/**/lost+found/** 5 5 0
|
||||
/boot/** 7 10 0
|
||||
/etc/passwd* 4 8 0
|
||||
/etc/group* 4 8 0
|
||||
/etc/shadow* 7 9 0
|
||||
/etc/shadow* 7 9 0
|
||||
/home/*/.ssh/** 7 9 0
|
||||
/home/*/.gnupg/** 5 7 0
|
||||
/home/** 4 6 0
|
||||
/srv/** 4 6 0
|
||||
/proc/** 6 9 0
|
||||
/proc/sys/kernel/hotplug 2 10 0
|
||||
/proc/sys/kernel/modprobe 2 10 0
|
||||
/proc/kallsyms 7 0 0
|
||||
/sys/** 4 8 0
|
||||
/sys/power/state 2 8 0
|
||||
/sys/firmware/** 2 10 0
|
||||
/dev/pts/* 8 9 0
|
||||
/dev/ptmx 8 9 0
|
||||
/dev/pty* 8 9 0
|
||||
/dev/null 0 0 0
|
||||
/dev/adbmouse 3 8 0
|
||||
/dev/ataraid 9 10 0
|
||||
/dev/zero 0 0 0
|
||||
/dev/agpgart* 8 10 0
|
||||
/dev/aio 3 3 0
|
||||
/dev/cbd/* 5 5 0
|
||||
/dev/cciss/* 4 10 0
|
||||
/dev/capi* 4 6 0
|
||||
/dev/cfs0 4 10 0
|
||||
/dev/compaq/* 4 10 0
|
||||
/dev/cdouble* 4 8 0
|
||||
/dev/cpu** 5 5 0
|
||||
/dev/cpu**microcode 1 10 0
|
||||
/dev/double* 4 8 0
|
||||
/dev/hd* 4 10 0
|
||||
/dev/sd* 4 10 0
|
||||
/dev/ida/* 4 10 0
|
||||
/dev/input/* 4 8 0
|
||||
/dev/mapper/control 4 10 0
|
||||
/dev/*mem 8 10 0
|
||||
/dev/loop* 4 10 0
|
||||
/dev/lp* 0 4 0
|
||||
/dev/md* 4 10 0
|
||||
/dev/msr 4 10 0
|
||||
/dev/nb* 4 10 0
|
||||
/dev/ram* 8 10 0
|
||||
/dev/rd/* 4 10 0
|
||||
/dev/*random 3 1 0
|
||||
/dev/sbpcd* 4 0 0
|
||||
/dev/rtc 6 0 0
|
||||
/dev/sd* 4 10 0
|
||||
/dev/sc* 4 10 0
|
||||
/dev/sg* 4 10 0
|
||||
/dev/st* 4 10 0
|
||||
/dev/snd/* 3 8 0
|
||||
/dev/usb/mouse* 4 6 0
|
||||
/dev/usb/hid* 4 6 0
|
||||
/dev/usb/tty* 4 6 0
|
||||
/dev/tty* 8 9 0
|
||||
/dev/stderr 0 0 0
|
||||
/dev/stdin 0 0 0
|
||||
/dev/stdout 0 0 0
|
||||
/dev/ubd* 4 10 0
|
||||
/dev/usbmouse* 4 6 0
|
||||
/dev/userdma 8 10 0
|
||||
/dev/vcs* 8 9 0
|
||||
/dev/xta* 4 10 0
|
||||
/dev/zero 0 0 0
|
||||
/dev/inittcl 8 10 0
|
||||
/dev/log 5 7 0
|
||||
/etc/fstab 3 8 0
|
||||
/etc/mtab 3 5 0
|
||||
/etc/SuSEconfig/* 1 8 0
|
||||
/etc/X11/* 2 7 0
|
||||
/etc/X11/xinit/* 2 8 0
|
||||
/etc/SuSE-release 1 5 0
|
||||
/etc/issue* 1 3 0
|
||||
/etc/motd 1 3 0
|
||||
/etc/aliases.d/* 1 7 0
|
||||
/etc/cron* 1 9 0
|
||||
/etc/cups/* 2 7 0
|
||||
/etc/default/* 3 8 0
|
||||
/etc/init.d/* 1 10 0
|
||||
/etc/permissions.d/* 1 8 0
|
||||
/etc/ppp/* 2 6 0
|
||||
/etc/ppp/*secrets 8 6 0
|
||||
/etc/profile.d/* 1 8 0
|
||||
/etc/skel/* 0 7 0
|
||||
/etc/sysconfig/* 4 10 0
|
||||
/etc/xinetd.d/* 1 9 0
|
||||
/etc/termcap/* 1 4 0
|
||||
/etc/ld.so.* 1 9 0
|
||||
/etc/pam.d/* 3 9 0
|
||||
/etc/udev/* 3 9 0
|
||||
/etc/insserv.conf 3 6 0
|
||||
/etc/security/* 1 9 0
|
||||
/etc/securetty 0 7 0
|
||||
/etc/sudoers 4 9 0
|
||||
/etc/hotplug/* 2 10 0
|
||||
/etc/xinitd.conf 1 9 0
|
||||
/etc/gpm/* 2 10 0
|
||||
/etc/ssl/** 2 7 0
|
||||
/etc/shadow* 5 9 0
|
||||
/etc/bash.bashrc 1 9 0
|
||||
/etc/csh.cshrc 1 9 0
|
||||
/etc/csh.login 1 9 0
|
||||
/etc/inittab 1 10 0
|
||||
/etc/profile* 1 9 0
|
||||
/etc/shells 1 5 0
|
||||
/etc/alternatives 1 6 0
|
||||
/etc/sysctl.conf 3 7 0
|
||||
/etc/dev.d/* 1 8 0
|
||||
/etc/manpath.config 1 6 0
|
||||
/etc/permissions* 1 8 0
|
||||
/etc/evms.conf 3 8 0
|
||||
/etc/exports 3 8 0
|
||||
/etc/samba/* 5 8 0
|
||||
/etc/ssh/* 3 8 0
|
||||
/etc/ssh/ssh_host_*key 8 8 0
|
||||
/etc/krb5.conf 4 8 0
|
||||
/etc/ntp.conf 3 8 0
|
||||
/etc/auto.* 3 8 0
|
||||
/etc/postfix/* 3 7 0
|
||||
/etc/postfix/*passwd* 6 7 0
|
||||
/etc/postfix/*cert* 6 7 0
|
||||
/etc/foomatic/* 3 5 0
|
||||
/etc/printcap 3 5 0
|
||||
/etc/youservers 4 9 0
|
||||
/etc/grub.conf 7 10 0
|
||||
/etc/modules.conf 4 10 0
|
||||
/etc/resolv.conf 2 7 0
|
||||
/etc/apache2/** 3 7 0
|
||||
/etc/apache2/**ssl** 7 7 0
|
||||
/etc/subdomain.d/** 6 10 0
|
||||
/etc/apparmor.d/** 6 10 0
|
||||
/etc/apparmor/** 6 10 0
|
||||
/var/log/** 3 8 0
|
||||
/var/adm/SuSEconfig/** 3 8 0
|
||||
/var/adm/** 3 7 0
|
||||
/var/lib/rpm/** 4 8 0
|
||||
/var/run/nscd/* 3 3 0
|
||||
/var/run/.nscd_socket 3 3 0
|
||||
/usr/share/doc/** 1 1 0
|
||||
/usr/share/man/** 3 5 0
|
||||
/usr/X11/man/** 3 5 0
|
||||
/usr/share/info/** 2 4 0
|
||||
/usr/share/java/** 2 5 0
|
||||
/usr/share/locale/** 2 4 0
|
||||
/usr/share/sgml/** 2 4 0
|
||||
/usr/share/YaST2/** 3 9 0
|
||||
/usr/share/ghostscript/** 3 5 0
|
||||
/usr/share/terminfo/** 1 8 0
|
||||
/usr/share/latex2html/** 2 4 0
|
||||
/usr/share/cups/** 5 6 0
|
||||
/usr/share/susehelp/** 2 6 0
|
||||
/usr/share/susehelp/cgi-bin/** 3 7 7
|
||||
/usr/share/zoneinfo/** 2 7 0
|
||||
/usr/share/zsh/** 3 6 0
|
||||
/usr/share/vim/** 3 8 0
|
||||
/usr/share/groff/** 3 7 0
|
||||
/usr/share/vnc/** 3 8 0
|
||||
/usr/share/wallpapers/** 2 4 0
|
||||
/usr/X11** 3 8 5
|
||||
/usr/X11*/bin/XFree86 3 8 8
|
||||
/usr/X11*/bin/Xorg 3 8 8
|
||||
/usr/X11*/bin/sux 3 8 8
|
||||
/usr/X11*/bin/xconsole 3 7 7
|
||||
/usr/X11*/bin/xhost 3 7 7
|
||||
/usr/X11*/bin/xauth 3 7 7
|
||||
/usr/X11*/bin/ethereal 3 6 8
|
||||
/usr/lib/ooo-** 3 6 5
|
||||
/usr/lib/lsb/** 2 8 8
|
||||
/usr/lib/pt_chwon 2 8 5
|
||||
/usr/lib/tcl** 2 5 3
|
||||
/usr/lib/lib*so* 3 8 4
|
||||
/usr/lib/iptables/* 2 8 2
|
||||
/usr/lib/perl5/** 4 10 6
|
||||
/usr/lib/gconv/* 4 7 4
|
||||
/usr/lib/locale/** 4 8 0
|
||||
/usr/lib/jvm/** 5 7 5
|
||||
/usr/lib/sasl*/** 5 8 4
|
||||
/usr/lib/jvm-exports/** 5 7 5
|
||||
/usr/lib/jvm-private/** 5 7 5
|
||||
/usr/lib/python*/** 5 7 5
|
||||
/usr/lib/libkrb5* 4 8 4
|
||||
/usr/lib/postfix/* 4 7 4
|
||||
/usr/lib/rpm/** 4 8 6
|
||||
/usr/lib/rpm/gnupg/** 4 9 0
|
||||
/usr/lib/apache2** 4 7 4
|
||||
/usr/lib/mailman/** 4 6 4
|
||||
/usr/bin/ldd 1 7 4
|
||||
/usr/bin/netcat 5 7 8
|
||||
/usr/bin/clear 2 6 3
|
||||
/usr/bin/reset 2 6 3
|
||||
/usr/bin/tput 2 6 3
|
||||
/usr/bin/tset 2 6 3
|
||||
/usr/bin/file 2 6 3
|
||||
/usr/bin/ftp 3 7 5
|
||||
/usr/bin/busybox 4 8 6
|
||||
/usr/bin/rbash 4 8 5
|
||||
/usr/bin/screen 3 6 5
|
||||
/usr/bin/getfacl 3 7 4
|
||||
/usr/bin/setfacl 3 7 9
|
||||
/usr/bin/*awk* 3 7 7
|
||||
/usr/bin/sudo 2 9 10
|
||||
/usr/bin/lsattr 2 6 5
|
||||
/usr/bin/chattr 2 7 8
|
||||
/usr/bin/sed 3 7 6
|
||||
/usr/bin/grep 2 7 2
|
||||
/usr/bin/chroot 2 6 10
|
||||
/usr/bin/dircolors 2 9 3
|
||||
/usr/bin/cut 2 7 2
|
||||
/usr/bin/du 2 7 3
|
||||
/usr/bin/env 2 7 2
|
||||
/usr/bin/head 2 7 2
|
||||
/usr/bin/tail 2 7 2
|
||||
/usr/bin/install 2 8 4
|
||||
/usr/bin/link 2 6 4
|
||||
/usr/bin/logname 2 6 2
|
||||
/usr/bin/md5sum 2 8 3
|
||||
/usr/bin/mkfifo 2 6 10
|
||||
/usr/bin/nice 2 7 7
|
||||
/usr/bin/nohup 2 7 7
|
||||
/usr/bin/printf 2 7 1
|
||||
/usr/bin/readlink 2 7 3
|
||||
/usr/bin/seq 2 7 1
|
||||
/usr/bin/sha1sum 2 8 3
|
||||
/usr/bin/shred 2 7 3
|
||||
/usr/bin/sort 2 7 3
|
||||
/usr/bin/split 2 7 3
|
||||
/usr/bin/stat 2 7 4
|
||||
/usr/bin/sum 2 8 3
|
||||
/usr/bin/tac 2 7 3
|
||||
/usr/bin/tail 3 8 4
|
||||
/usr/bin/tee 2 7 3
|
||||
/usr/bin/test 2 8 4
|
||||
/usr/bin/touch 2 7 3
|
||||
/usr/bin/tr 2 8 3
|
||||
/usr/bin/tsort 2 7 3
|
||||
/usr/bin/tty 2 7 3
|
||||
/usr/bin/unexpand 2 7 3
|
||||
/usr/bin/uniq 2 7 3
|
||||
/usr/bin/unlink 2 8 4
|
||||
/usr/bin/uptime 2 7 3
|
||||
/usr/bin/users 2 8 4
|
||||
/usr/bin/vdir 2 8 4
|
||||
/usr/bin/wc 2 7 3
|
||||
/usr/bin/who 2 8 4
|
||||
/usr/bin/whoami 2 8 4
|
||||
/usr/bin/yes 1 6 1
|
||||
/usr/bin/ed 2 7 5
|
||||
/usr/bin/red 2 7 4
|
||||
/usr/bin/find 2 8 5
|
||||
/usr/bin/xargs 2 7 5
|
||||
/usr/bin/ispell 2 7 4
|
||||
/usr/bin/a2p 2 7 5
|
||||
/usr/bin/perlcc 2 7 5
|
||||
/usr/bin/perldoc 2 7 5
|
||||
/usr/bin/pod2* 2 7 5
|
||||
/usr/bin/prove 2 7 5
|
||||
/usr/bin/perl 2 10 7
|
||||
/usr/bin/perl* 2 10 7
|
||||
/usr/bin/suidperl 2 8 8
|
||||
/usr/bin/csh 2 8 8
|
||||
/usr/bin/tcsh 2 8 8
|
||||
/usr/bin/tree 2 6 5
|
||||
/usr/bin/last 2 7 5
|
||||
/usr/bin/lastb 2 7 5
|
||||
/usr/bin/utmpdump 2 6 5
|
||||
/usr/bin/alsamixer 2 6 8
|
||||
/usr/bin/amixer 2 6 8
|
||||
/usr/bin/amidi 2 6 8
|
||||
/usr/bin/aoss 2 6 8
|
||||
/usr/bin/aplay 2 6 8
|
||||
/usr/bin/aplaymidi 2 6 8
|
||||
/usr/bin/arecord 2 6 8
|
||||
/usr/bin/arecordmidi 2 6 8
|
||||
/usr/bin/aseqnet 2 6 8
|
||||
/usr/bin/aserver 2 6 8
|
||||
/usr/bin/iecset 2 6 8
|
||||
/usr/bin/rview 2 6 5
|
||||
/usr/bin/ex 2 7 5
|
||||
/usr/bin/enscript 2 6 5
|
||||
/usr/bin/genscript 2 6 5
|
||||
/usr/bin/xdelta 2 6 5
|
||||
/usr/bin/edit 2 6 5
|
||||
/usr/bin/vimtutor 2 6 5
|
||||
/usr/bin/rvim 2 6 5
|
||||
/usr/bin/vim 2 8 7
|
||||
/usr/bin/vimdiff 2 8 7
|
||||
/usr/bin/aspell 2 6 5
|
||||
/usr/bin/xxd 2 6 5
|
||||
/usr/bin/spell 2 6 5
|
||||
/usr/bin/eqn 2 6 5
|
||||
/usr/bin/eqn2graph 2 6 5
|
||||
/usr/bin/word-list-compress 2 6 4
|
||||
/usr/bin/afmtodit 2 6 4
|
||||
/usr/bin/hpf2dit 2 6 4
|
||||
/usr/bin/geqn 2 6 4
|
||||
/usr/bin/grn 2 6 4
|
||||
/usr/bin/grodvi 2 6 4
|
||||
/usr/bin/groff 2 6 5
|
||||
/usr/bin/groffer 2 6 4
|
||||
/usr/bin/grolj4 2 6 4
|
||||
/usr/bin/grotty 2 6 4
|
||||
/usr/bin/gtbl 2 6 4
|
||||
/usr/bin/pic2graph 2 6 4
|
||||
/usr/bin/indxbib 2 6 4
|
||||
/usr/bin/lkbib 2 6 4
|
||||
/usr/bin/lookbib 2 6 4
|
||||
/usr/bin/mmroff 2 6 4
|
||||
/usr/bin/neqn 2 6 4
|
||||
/usr/bin/pfbtops 2 6 4
|
||||
/usr/bin/pic 2 6 4
|
||||
/usr/bin/tfmtodit 2 6 4
|
||||
/usr/bin/tbl 2 6 4
|
||||
/usr/bin/post-grohtml 2 6 4
|
||||
/usr/bin/pre-grohtml 2 6 4
|
||||
/usr/bin/refer 2 6 4
|
||||
/usr/bin/soelim 2 6 4
|
||||
/usr/bin/disable-paste 2 6 6
|
||||
/usr/bin/troff 2 6 4
|
||||
/usr/bin/strace-graph 2 6 4
|
||||
/usr/bin/gpm-root 2 6 7
|
||||
/usr/bin/hltest 2 6 7
|
||||
/usr/bin/mev 2 6 6
|
||||
/usr/bin/mouse-test 2 6 6
|
||||
/usr/bin/strace 2 8 9
|
||||
/usr/bin/scsiformat 2 7 10
|
||||
/usr/bin/lsscsi 2 7 7
|
||||
/usr/bin/scsiinfo 2 7 7
|
||||
/usr/bin/sg_* 2 7 7
|
||||
/usr/bin/build-classpath 2 6 6
|
||||
/usr/bin/build-classpath-directory 2 6 6
|
||||
/usr/bin/build-jar-repository 2 6 6
|
||||
/usr/bin/diff-jars 2 6 6
|
||||
/usr/bin/jvmjar 2 6 6
|
||||
/usr/bin/rebuild-jar-repository 2 6 6
|
||||
/usr/bin/scriptreplay 2 6 5
|
||||
/usr/bin/cal 2 6 3
|
||||
/usr/bin/chkdupexe 2 6 5
|
||||
/usr/bin/col 2 6 4
|
||||
/usr/bin/colcrt 2 6 4
|
||||
/usr/bin/colrm 2 6 3
|
||||
/usr/bin/column 2 6 4
|
||||
/usr/bin/cytune 2 6 6
|
||||
/usr/bin/ddate 2 6 3
|
||||
/usr/bin/fdformat 2 6 6
|
||||
/usr/bin/getopt 2 8 6
|
||||
/usr/bin/hexdump 2 6 4
|
||||
/usr/bin/hostid 2 6 4
|
||||
/usr/bin/ipcrm 2 7 7
|
||||
/usr/bin/ipcs 2 7 6
|
||||
/usr/bin/isosize 2 6 4
|
||||
/usr/bin/line 2 6 4
|
||||
/usr/bin/look 2 6 5
|
||||
/usr/bin/mcookie 2 7 5
|
||||
/usr/bin/mesg 2 6 4
|
||||
/usr/bin/namei 2 6 5
|
||||
/usr/bin/rename 2 6 5
|
||||
/usr/bin/renice 2 6 7
|
||||
/usr/bin/rev 2 6 5
|
||||
/usr/bin/script 2 6 6
|
||||
/usr/bin/ChangeSymlinks 2 8 8
|
||||
/usr/bin/setfdprm 2 6 7
|
||||
/usr/bin/setsid 2 6 3
|
||||
/usr/bin/setterm 2 6 5
|
||||
/usr/bin/tailf 2 6 4
|
||||
/usr/bin/time 2 6 4
|
||||
/usr/bin/ul 2 6 4
|
||||
/usr/bin/wall 2 6 5
|
||||
/usr/bin/whereis 2 6 4
|
||||
/usr/bin/which 2 6 3
|
||||
/usr/bin/c_rehash 2 7 6
|
||||
/usr/bin/openssl 2 8 6
|
||||
/usr/bin/lsdev 2 6 5
|
||||
/usr/bin/procinfo 2 6 5
|
||||
/usr/bin/socklist 2 6 5
|
||||
/usr/bin/filesize 2 6 3
|
||||
/usr/bin/linkto 2 6 3
|
||||
/usr/bin/mkinfodir 2 6 5
|
||||
/usr/bin/old 2 6 4
|
||||
/usr/bin/rpmlocate 2 6 5
|
||||
/usr/bin/safe-rm 2 8 6
|
||||
/usr/bin/safe-rmdir 2 8 6
|
||||
/usr/bin/setJava 2 6 1
|
||||
/usr/bin/vmstat 2 6 4
|
||||
/usr/bin/top 2 6 6
|
||||
/usr/bin/pinentry* 2 7 6
|
||||
/usr/bin/free 2 8 4
|
||||
/usr/bin/pmap 2 6 5
|
||||
/usr/bin/slabtop 2 6 4
|
||||
/usr/bin/tload 2 6 4
|
||||
/usr/bin/watch 2 6 3
|
||||
/usr/bin/w 2 6 4
|
||||
/usr/bin/pstree.x11 2 6 4
|
||||
/usr/bin/pstree 2 6 4
|
||||
/usr/bin/snice 2 6 6
|
||||
/usr/bin/skill 2 6 7
|
||||
/usr/bin/pgrep 2 6 4
|
||||
/usr/bin/killall 2 6 7
|
||||
/usr/bin/curl 2 7 7
|
||||
/usr/bin/slptool 2 7 8
|
||||
/usr/bin/ldap* 2 7 7
|
||||
/usr/bin/whatis 2 7 5
|
460
utils/test/severity_broken.db
Normal file
460
utils/test/severity_broken.db
Normal file
|
@ -0,0 +1,460 @@
|
|||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# Allow this process to 0wn the machine:
|
||||
CAP_SYS_ADMIN 10
|
||||
CAP_SYS_CHROOT 10
|
||||
CAP_SYS_MODULE
|
||||
CAP_SYS_PTRACE 10
|
||||
CAP_SYS_RAWIO 10
|
||||
CAP_MAC_ADMIN 10
|
||||
CAP_MAC_OVERRIDE 10
|
||||
# Allow other processes to 0wn the machine:
|
||||
CAP_SETPCAP 9
|
||||
CAP_SETFCAP 9
|
||||
CAP_CHOWN 9
|
||||
CAP_FSETID 9
|
||||
CAP_MKNOD 9
|
||||
CAP_LINUX_IMMUTABLE 9
|
||||
CAP_DAC_OVERRIDE 9
|
||||
CAP_SETGID 9
|
||||
CAP_SETUID 9
|
||||
CAP_FOWNER 9
|
||||
# Denial of service, bypass audit controls, information leak
|
||||
CAP_SYS_TIME 8
|
||||
CAP_NET_ADMIN 8
|
||||
CAP_SYS_RESOURCE 8
|
||||
CAP_KILL 8
|
||||
CAP_IPC_OWNER 8
|
||||
CAP_SYS_PACCT 8
|
||||
CAP_SYS_BOOT 8
|
||||
CAP_NET_BIND_SERVICE 8
|
||||
CAP_NET_RAW 8
|
||||
CAP_SYS_NICE 8
|
||||
CAP_LEASE 8
|
||||
CAP_IPC_LOCK 8
|
||||
CAP_SYS_TTY_CONFIG 8
|
||||
CAP_AUDIT_CONTROL 8
|
||||
CAP_AUDIT_WRITE 8
|
||||
CAP_SYSLOG 8
|
||||
CAP_WAKE_ALARM 8
|
||||
CAP_BLOCK_SUSPEND 8
|
||||
CAP_DAC_READ_SEARCH 7
|
||||
# unused
|
||||
CAP_NET_BROADCAST 0
|
||||
|
||||
# filename r w x
|
||||
# 'hard drives' are generally 4 10 0
|
||||
/**/lost+found/** 5 5 0
|
||||
/boot/** 7 10 0
|
||||
/etc/passwd* 4 8 0
|
||||
/etc/group* 4 8 0
|
||||
/etc/shadow* 7 9 0
|
||||
/etc/shadow* 7 9 0
|
||||
/home/*/.ssh/** 7 9 0
|
||||
/home/*/.gnupg/** 5 7 0
|
||||
/home/** 4 6 0
|
||||
/srv/** 4 6 0
|
||||
/proc/** 6 9 0
|
||||
/proc/sys/kernel/hotplug 2 10 0
|
||||
/proc/sys/kernel/modprobe 2 10 0
|
||||
/proc/kallsyms 7 0 0
|
||||
/sys/** 4 8 0
|
||||
/sys/power/state 2 8 0
|
||||
/sys/firmware/** 2 10 0
|
||||
/dev/pts/* 8 9 0
|
||||
/dev/ptmx 8 9 0
|
||||
/dev/pty* 8 9 0
|
||||
/dev/null 0 0 0
|
||||
/dev/adbmouse 3 8 0
|
||||
/dev/ataraid 9 10 0
|
||||
/dev/zero 0 0 0
|
||||
/dev/agpgart* 8 10 0
|
||||
/dev/aio 3 3 0
|
||||
/dev/cbd/* 5 5 0
|
||||
/dev/cciss/* 4 10 0
|
||||
/dev/capi* 4 6 0
|
||||
/dev/cfs0 4 10 0
|
||||
/dev/compaq/* 4 10 0
|
||||
/dev/cdouble* 4 8 0
|
||||
/dev/cpu** 5 5 0
|
||||
/dev/cpu**microcode 1 10 0
|
||||
/dev/double* 4 8 0
|
||||
/dev/hd* 4 10 0
|
||||
/dev/sd* 4 10 0
|
||||
/dev/ida/* 4 10 0
|
||||
/dev/input/* 4 8 0
|
||||
/dev/mapper/control 4 10 0
|
||||
/dev/*mem 8 10 0
|
||||
/dev/loop* 4 10 0
|
||||
/dev/lp* 0 4 0
|
||||
/dev/md* 4 10 0
|
||||
/dev/msr 4 10 0
|
||||
/dev/nb* 4 10 0
|
||||
/dev/ram* 8 10 0
|
||||
/dev/rd/* 4 10 0
|
||||
/dev/*random 3 1 0
|
||||
/dev/sbpcd* 4 0 0
|
||||
/dev/rtc 6 0 0
|
||||
/dev/sd* 4 10 0
|
||||
/dev/sc* 4 10 0
|
||||
/dev/sg* 4 10 0
|
||||
/dev/st* 4 10 0
|
||||
/dev/snd/* 3 8 0
|
||||
/dev/usb/mouse* 4 6 0
|
||||
/dev/usb/hid* 4 6 0
|
||||
/dev/usb/tty* 4 6 0
|
||||
/dev/tty* 8 9 0
|
||||
/dev/stderr 0 0 0
|
||||
/dev/stdin 0 0 0
|
||||
/dev/stdout 0 0 0
|
||||
/dev/ubd* 4 10 0
|
||||
/dev/usbmouse* 4 6 0
|
||||
/dev/userdma 8 10 0
|
||||
/dev/vcs* 8 9 0
|
||||
/dev/xta* 4 10 0
|
||||
/dev/zero 0 0 0
|
||||
/dev/inittcl 8 10 0
|
||||
/dev/log 5 7 0
|
||||
/etc/fstab 3 8 0
|
||||
/etc/mtab 3 5 0
|
||||
/etc/SuSEconfig/* 1 8 0
|
||||
/etc/X11/* 2 7 0
|
||||
/etc/X11/xinit/* 2 8 0
|
||||
/etc/SuSE-release 1 5 0
|
||||
/etc/issue* 1 3 0
|
||||
/etc/motd 1 3 0
|
||||
/etc/aliases.d/* 1 7 0
|
||||
/etc/cron* 1 9 0
|
||||
/etc/cups/* 2 7 0
|
||||
/etc/default/* 3 8 0
|
||||
/etc/init.d/* 1 10 0
|
||||
/etc/permissions.d/* 1 8 0
|
||||
/etc/ppp/* 2 6 0
|
||||
/etc/ppp/*secrets 8 6 0
|
||||
/etc/profile.d/* 1 8 0
|
||||
/etc/skel/* 0 7 0
|
||||
/etc/sysconfig/* 4 10 0
|
||||
/etc/xinetd.d/* 1 9 0
|
||||
/etc/termcap/* 1 4 0
|
||||
/etc/ld.so.* 1 9 0
|
||||
/etc/pam.d/* 3 9 0
|
||||
/etc/udev/* 3 9 0
|
||||
/etc/insserv.conf 3 6 0
|
||||
/etc/security/* 1 9 0
|
||||
/etc/securetty 0 7 0
|
||||
/etc/sudoers 4 9 0
|
||||
/etc/hotplug/* 2 10 0
|
||||
/etc/xinitd.conf 1 9 0
|
||||
/etc/gpm/* 2 10 0
|
||||
/etc/ssl/** 2 7 0
|
||||
/etc/shadow* 5 9 0
|
||||
/etc/bash.bashrc 1 9 0
|
||||
/etc/csh.cshrc 1 9 0
|
||||
/etc/csh.login 1 9 0
|
||||
/etc/inittab 1 10 0
|
||||
/etc/profile* 1 9 0
|
||||
/etc/shells 1 5 0
|
||||
/etc/alternatives 1 6 0
|
||||
/etc/sysctl.conf 3 7 0
|
||||
/etc/dev.d/* 1 8 0
|
||||
/etc/manpath.config 1 6 0
|
||||
/etc/permissions* 1 8 0
|
||||
/etc/evms.conf 3 8 0
|
||||
/etc/exports 3 8 0
|
||||
/etc/samba/* 5 8 0
|
||||
/etc/ssh/* 3 8 0
|
||||
/etc/ssh/ssh_host_*key 8 8 0
|
||||
/etc/krb5.conf 4 8 0
|
||||
/etc/ntp.conf 3 8 0
|
||||
/etc/auto.* 3 8 0
|
||||
/etc/postfix/* 3 7 0
|
||||
/etc/postfix/*passwd* 6 7 0
|
||||
/etc/postfix/*cert* 6 7 0
|
||||
/etc/foomatic/* 3 5 0
|
||||
/etc/printcap 3 5 0
|
||||
/etc/youservers 4 9 0
|
||||
/etc/grub.conf 7 10 0
|
||||
/etc/modules.conf 4 10 0
|
||||
/etc/resolv.conf 2 7 0
|
||||
/etc/apache2/** 3 7 0
|
||||
/etc/apache2/**ssl** 7 7 0
|
||||
/etc/subdomain.d/** 6 10 0
|
||||
/etc/apparmor.d/** 6 10 0
|
||||
/etc/apparmor/** 6 10 0
|
||||
/var/log/** 3 8 0
|
||||
/var/adm/SuSEconfig/** 3 8 0
|
||||
/var/adm/** 3 7 0
|
||||
/var/lib/rpm/** 4 8 0
|
||||
/var/run/nscd/* 3 3 0
|
||||
/var/run/.nscd_socket 3 3 0
|
||||
/usr/share/doc/** 1 1 0
|
||||
/usr/share/man/** 3 5 0
|
||||
/usr/X11/man/** 3 5 0
|
||||
/usr/share/info/** 2 4 0
|
||||
/usr/share/java/** 2 5 0
|
||||
/usr/share/locale/** 2 4 0
|
||||
/usr/share/sgml/** 2 4 0
|
||||
/usr/share/YaST2/** 3 9 0
|
||||
/usr/share/ghostscript/** 3 5 0
|
||||
/usr/share/terminfo/** 1 8 0
|
||||
/usr/share/latex2html/** 2 4 0
|
||||
/usr/share/cups/** 5 6 0
|
||||
/usr/share/susehelp/** 2 6 0
|
||||
/usr/share/susehelp/cgi-bin/** 3 7 7
|
||||
/usr/share/zoneinfo/** 2 7 0
|
||||
/usr/share/zsh/** 3 6 0
|
||||
/usr/share/vim/** 3 8 0
|
||||
/usr/share/groff/** 3 7 0
|
||||
/usr/share/vnc/** 3 8 0
|
||||
/usr/share/wallpapers/** 2 4 0
|
||||
/usr/X11** 3 8 5
|
||||
/usr/X11*/bin/XFree86 3 8 8
|
||||
/usr/X11*/bin/Xorg 3 8 8
|
||||
/usr/X11*/bin/sux 3 8 8
|
||||
/usr/X11*/bin/xconsole 3 7 7
|
||||
/usr/X11*/bin/xhost 3 7 7
|
||||
/usr/X11*/bin/xauth 3 7 7
|
||||
/usr/X11*/bin/ethereal 3 6 8
|
||||
/usr/lib/ooo-** 3 6 5
|
||||
/usr/lib/lsb/** 2 8 8
|
||||
/usr/lib/pt_chwon 2 8 5
|
||||
/usr/lib/tcl** 2 5 3
|
||||
/usr/lib/lib*so* 3 8 4
|
||||
/usr/lib/iptables/* 2 8 2
|
||||
/usr/lib/perl5/** 4 10 6
|
||||
/usr/lib/gconv/* 4 7 4
|
||||
/usr/lib/locale/** 4 8 0
|
||||
/usr/lib/jvm/** 5 7 5
|
||||
/usr/lib/sasl*/** 5 8 4
|
||||
/usr/lib/jvm-exports/** 5 7 5
|
||||
/usr/lib/jvm-private/** 5 7 5
|
||||
/usr/lib/python*/** 5 7 5
|
||||
/usr/lib/libkrb5* 4 8 4
|
||||
/usr/lib/postfix/* 4 7 4
|
||||
/usr/lib/rpm/** 4 8 6
|
||||
/usr/lib/rpm/gnupg/** 4 9 0
|
||||
/usr/lib/apache2** 4 7 4
|
||||
/usr/lib/mailman/** 4 6 4
|
||||
/usr/bin/ldd 1 7 4
|
||||
/usr/bin/netcat 5 7 8
|
||||
/usr/bin/clear 2 6 3
|
||||
/usr/bin/reset 2 6 3
|
||||
/usr/bin/tput 2 6 3
|
||||
/usr/bin/tset 2 6 3
|
||||
/usr/bin/file 2 6 3
|
||||
/usr/bin/ftp 3 7 5
|
||||
/usr/bin/busybox 4 8 6
|
||||
/usr/bin/rbash 4 8 5
|
||||
/usr/bin/screen 3 6 5
|
||||
/usr/bin/getfacl 3 7 4
|
||||
/usr/bin/setfacl 3 7 9
|
||||
/usr/bin/*awk* 3 7 7
|
||||
/usr/bin/sudo 2 9 10
|
||||
/usr/bin/lsattr 2 6 5
|
||||
/usr/bin/chattr 2 7 8
|
||||
/usr/bin/sed 3 7 6
|
||||
/usr/bin/grep 2 7 2
|
||||
/usr/bin/chroot 2 6 10
|
||||
/usr/bin/dircolors 2 9 3
|
||||
/usr/bin/cut 2 7 2
|
||||
/usr/bin/du 2 7 3
|
||||
/usr/bin/env 2 7 2
|
||||
/usr/bin/head 2 7 2
|
||||
/usr/bin/tail 2 7 2
|
||||
/usr/bin/install 2 8 4
|
||||
/usr/bin/link 2 6 4
|
||||
/usr/bin/logname 2 6 2
|
||||
/usr/bin/md5sum 2 8 3
|
||||
/usr/bin/mkfifo 2 6 10
|
||||
/usr/bin/nice 2 7 7
|
||||
/usr/bin/nohup 2 7 7
|
||||
/usr/bin/printf 2 7 1
|
||||
/usr/bin/readlink 2 7 3
|
||||
/usr/bin/seq 2 7 1
|
||||
/usr/bin/sha1sum 2 8 3
|
||||
/usr/bin/shred 2 7 3
|
||||
/usr/bin/sort 2 7 3
|
||||
/usr/bin/split 2 7 3
|
||||
/usr/bin/stat 2 7 4
|
||||
/usr/bin/sum 2 8 3
|
||||
/usr/bin/tac 2 7 3
|
||||
/usr/bin/tail 3 8 4
|
||||
/usr/bin/tee 2 7 3
|
||||
/usr/bin/test 2 8 4
|
||||
/usr/bin/touch 2 7 3
|
||||
/usr/bin/tr 2 8 3
|
||||
/usr/bin/tsort 2 7 3
|
||||
/usr/bin/tty 2 7 3
|
||||
/usr/bin/unexpand 2 7 3
|
||||
/usr/bin/uniq 2 7 3
|
||||
/usr/bin/unlink 2 8 4
|
||||
/usr/bin/uptime 2 7 3
|
||||
/usr/bin/users 2 8 4
|
||||
/usr/bin/vdir 2 8 4
|
||||
/usr/bin/wc 2 7 3
|
||||
/usr/bin/who 2 8 4
|
||||
/usr/bin/whoami 2 8 4
|
||||
/usr/bin/yes 1 6 1
|
||||
/usr/bin/ed 2 7 5
|
||||
/usr/bin/red 2 7 4
|
||||
/usr/bin/find 2 8 5
|
||||
/usr/bin/xargs 2 7 5
|
||||
/usr/bin/ispell 2 7 4
|
||||
/usr/bin/a2p 2 7 5
|
||||
/usr/bin/perlcc 2 7 5
|
||||
/usr/bin/perldoc 2 7 5
|
||||
/usr/bin/pod2* 2 7 5
|
||||
/usr/bin/prove 2 7 5
|
||||
/usr/bin/perl 2 10 7
|
||||
/usr/bin/perl* 2 10 7
|
||||
/usr/bin/suidperl 2 8 8
|
||||
/usr/bin/csh 2 8 8
|
||||
/usr/bin/tcsh 2 8 8
|
||||
/usr/bin/tree 2 6 5
|
||||
/usr/bin/last 2 7 5
|
||||
/usr/bin/lastb 2 7 5
|
||||
/usr/bin/utmpdump 2 6 5
|
||||
/usr/bin/alsamixer 2 6 8
|
||||
/usr/bin/amixer 2 6 8
|
||||
/usr/bin/amidi 2 6 8
|
||||
/usr/bin/aoss 2 6 8
|
||||
/usr/bin/aplay 2 6 8
|
||||
/usr/bin/aplaymidi 2 6 8
|
||||
/usr/bin/arecord 2 6 8
|
||||
/usr/bin/arecordmidi 2 6 8
|
||||
/usr/bin/aseqnet 2 6 8
|
||||
/usr/bin/aserver 2 6 8
|
||||
/usr/bin/iecset 2 6 8
|
||||
/usr/bin/rview 2 6 5
|
||||
/usr/bin/ex 2 7 5
|
||||
/usr/bin/enscript 2 6 5
|
||||
/usr/bin/genscript 2 6 5
|
||||
/usr/bin/xdelta 2 6 5
|
||||
/usr/bin/edit 2 6 5
|
||||
/usr/bin/vimtutor 2 6 5
|
||||
/usr/bin/rvim 2 6 5
|
||||
/usr/bin/vim 2 8 7
|
||||
/usr/bin/vimdiff 2 8 7
|
||||
/usr/bin/aspell 2 6 5
|
||||
/usr/bin/xxd 2 6 5
|
||||
/usr/bin/spell 2 6 5
|
||||
/usr/bin/eqn 2 6 5
|
||||
/usr/bin/eqn2graph 2 6 5
|
||||
/usr/bin/word-list-compress 2 6 4
|
||||
/usr/bin/afmtodit 2 6 4
|
||||
/usr/bin/hpf2dit 2 6 4
|
||||
/usr/bin/geqn 2 6 4
|
||||
/usr/bin/grn 2 6 4
|
||||
/usr/bin/grodvi 2 6 4
|
||||
/usr/bin/groff 2 6 5
|
||||
/usr/bin/groffer 2 6 4
|
||||
/usr/bin/grolj4 2 6 4
|
||||
/usr/bin/grotty 2 6 4
|
||||
/usr/bin/gtbl 2 6 4
|
||||
/usr/bin/pic2graph 2 6 4
|
||||
/usr/bin/indxbib 2 6 4
|
||||
/usr/bin/lkbib 2 6 4
|
||||
/usr/bin/lookbib 2 6 4
|
||||
/usr/bin/mmroff 2 6 4
|
||||
/usr/bin/neqn 2 6 4
|
||||
/usr/bin/pfbtops 2 6 4
|
||||
/usr/bin/pic 2 6 4
|
||||
/usr/bin/tfmtodit 2 6 4
|
||||
/usr/bin/tbl 2 6 4
|
||||
/usr/bin/post-grohtml 2 6 4
|
||||
/usr/bin/pre-grohtml 2 6 4
|
||||
/usr/bin/refer 2 6 4
|
||||
/usr/bin/soelim 2 6 4
|
||||
/usr/bin/disable-paste 2 6 6
|
||||
/usr/bin/troff 2 6 4
|
||||
/usr/bin/strace-graph 2 6 4
|
||||
/usr/bin/gpm-root 2 6 7
|
||||
/usr/bin/hltest 2 6 7
|
||||
/usr/bin/mev 2 6 6
|
||||
/usr/bin/mouse-test 2 6 6
|
||||
/usr/bin/strace 2 8 9
|
||||
/usr/bin/scsiformat 2 7 10
|
||||
/usr/bin/lsscsi 2 7 7
|
||||
/usr/bin/scsiinfo 2 7 7
|
||||
/usr/bin/sg_* 2 7 7
|
||||
/usr/bin/build-classpath 2 6 6
|
||||
/usr/bin/build-classpath-directory 2 6 6
|
||||
/usr/bin/build-jar-repository 2 6 6
|
||||
/usr/bin/diff-jars 2 6 6
|
||||
/usr/bin/jvmjar 2 6 6
|
||||
/usr/bin/rebuild-jar-repository 2 6 6
|
||||
/usr/bin/scriptreplay 2 6 5
|
||||
/usr/bin/cal 2 6 3
|
||||
/usr/bin/chkdupexe 2 6 5
|
||||
/usr/bin/col 2 6 4
|
||||
/usr/bin/colcrt 2 6 4
|
||||
/usr/bin/colrm 2 6 3
|
||||
/usr/bin/column 2 6 4
|
||||
/usr/bin/cytune 2 6 6
|
||||
/usr/bin/ddate 2 6 3
|
||||
/usr/bin/fdformat 2 6 6
|
||||
/usr/bin/getopt 2 8 6
|
||||
/usr/bin/hexdump 2 6 4
|
||||
/usr/bin/hostid 2 6 4
|
||||
/usr/bin/ipcrm 2 7 7
|
||||
/usr/bin/ipcs 2 7 6
|
||||
/usr/bin/isosize 2 6 4
|
||||
/usr/bin/line 2 6 4
|
||||
/usr/bin/look 2 6 5
|
||||
/usr/bin/mcookie 2 7 5
|
||||
/usr/bin/mesg 2 6 4
|
||||
/usr/bin/namei 2 6 5
|
||||
/usr/bin/rename 2 6 5
|
||||
/usr/bin/renice 2 6 7
|
||||
/usr/bin/rev 2 6 5
|
||||
/usr/bin/script 2 6 6
|
||||
/usr/bin/ChangeSymlinks 2 8 8
|
||||
/usr/bin/setfdprm 2 6 7
|
||||
/usr/bin/setsid 2 6 3
|
||||
/usr/bin/setterm 2 6 5
|
||||
/usr/bin/tailf 2 6 4
|
||||
/usr/bin/time 2 6 4
|
||||
/usr/bin/ul 2 6 4
|
||||
/usr/bin/wall 2 6 5
|
||||
/usr/bin/whereis 2 6 4
|
||||
/usr/bin/which 2 6 3
|
||||
/usr/bin/c_rehash 2 7 6
|
||||
/usr/bin/openssl 2 8 6
|
||||
/usr/bin/lsdev 2 6 5
|
||||
/usr/bin/procinfo 2 6 5
|
||||
/usr/bin/socklist 2 6 5
|
||||
/usr/bin/filesize 2 6 3
|
||||
/usr/bin/linkto 2 6 3
|
||||
/usr/bin/mkinfodir 2 6 5
|
||||
/usr/bin/old 2 6 4
|
||||
/usr/bin/rpmlocate 2 6 5
|
||||
/usr/bin/safe-rm 2 8 6
|
||||
/usr/bin/safe-rmdir 2 8 6
|
||||
/usr/bin/setJava 2 6 1
|
||||
/usr/bin/vmstat 2 6 4
|
||||
/usr/bin/top 2 6 6
|
||||
/usr/bin/pinentry* 2 7 6
|
||||
/usr/bin/free 2 8 4
|
||||
/usr/bin/pmap 2 6 5
|
||||
/usr/bin/slabtop 2 6 4
|
||||
/usr/bin/tload 2 6 4
|
||||
/usr/bin/watch 2 6 3
|
||||
/usr/bin/w 2 6 4
|
||||
/usr/bin/pstree.x11 2 6 4
|
||||
/usr/bin/pstree 2 6 4
|
||||
/usr/bin/snice 2 6 6
|
||||
/usr/bin/skill 2 6 7
|
||||
/usr/bin/pgrep 2 6 4
|
||||
/usr/bin/killall 2 6 7
|
||||
/usr/bin/curl 2 7 7
|
||||
/usr/bin/slptool 2 7 8
|
||||
/usr/bin/ldap* 2 7 7
|
||||
/usr/bin/whatis 2 7 5
|
90
utils/test/severity_test.py
Normal file
90
utils/test/severity_test.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.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 as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.append('../')
|
||||
|
||||
import apparmor.severity as severity
|
||||
from apparmor.common import AppArmorException
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
#copy the local profiles to the test directory
|
||||
if os.path.exists('./profiles'):
|
||||
shutil.rmtree('./profiles')
|
||||
shutil.copytree('/etc/apparmor.d/', './profiles/', symlinks=True)
|
||||
|
||||
def tearDown(self):
|
||||
#Wipe the local profiles from the test directory
|
||||
shutil.rmtree('./profiles')
|
||||
|
||||
def testRank_Test(self):
|
||||
sev_db = severity.Severity('severity.db')
|
||||
rank = sev_db.rank('/usr/bin/whatis', 'x')
|
||||
self.assertEqual(rank, 5, 'Wrong rank')
|
||||
rank = sev_db.rank('/etc', 'x')
|
||||
self.assertEqual(rank, 10, 'Wrong rank')
|
||||
rank = sev_db.rank('/dev/doublehit', 'x')
|
||||
self.assertEqual(rank, 0, 'Wrong rank')
|
||||
rank = sev_db.rank('/dev/doublehit', 'rx')
|
||||
self.assertEqual(rank, 4, 'Wrong rank')
|
||||
rank = sev_db.rank('/dev/doublehit', 'rwx')
|
||||
self.assertEqual(rank, 8, 'Wrong rank')
|
||||
rank = sev_db.rank('/dev/tty10', 'rwx')
|
||||
self.assertEqual(rank, 9, 'Wrong rank')
|
||||
rank = sev_db.rank('/var/adm/foo/**', 'rx')
|
||||
self.assertEqual(rank, 3, 'Wrong rank')
|
||||
rank = sev_db.rank('CAP_KILL')
|
||||
self.assertEqual(rank, 8, 'Wrong rank')
|
||||
rank = sev_db.rank('CAP_SETPCAP')
|
||||
self.assertEqual(rank, 9, 'Wrong rank')
|
||||
self.assertEqual(sev_db.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('/etc/**', 'r') , 10, 'Invalid Rank')
|
||||
|
||||
# Load all variables for /sbin/klogd and test them
|
||||
sev_db.load_variables('profiles/sbin.klogd')
|
||||
self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank')
|
||||
|
||||
sev_db.unload_variables()
|
||||
|
||||
sev_db.load_variables('profiles/usr.sbin.dnsmasq')
|
||||
self.assertEqual(sev_db.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank')
|
||||
self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank')
|
||||
|
||||
#self.assertEqual(sev_db.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank')
|
||||
|
||||
def testInvalid(self):
|
||||
sev_db = severity.Severity('severity.db')
|
||||
rank = sev_db.rank('/dev/doublehit', 'i')
|
||||
self.assertEqual(rank, 10, 'Wrong')
|
||||
try:
|
||||
severity.Severity('severity_broken.db')
|
||||
except AppArmorException:
|
||||
pass
|
||||
rank = sev_db.rank('CAP_UNKOWN')
|
||||
rank = sev_db.rank('CAP_K*')
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
Loading…
Add table
Reference in a new issue