First set of tools in their alpha release, logprof and genprof are pre-bleeding edge so dont hurt yourself or worse your distro.

This commit is contained in:
Kshitij Gupta 2013-08-21 11:26:09 +05:30
parent 1fb521418d
commit 5490dddbda
14 changed files with 703 additions and 236 deletions

54
Tools/aa-audit.py Normal file
View file

@ -0,0 +1,54 @@
#!/usr/bin/python
import sys
import os
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='Switch the given programs to audit mode')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('program', type=str, nargs='+', help='name of program to hswitch to audit mode')
args = parser.parse_args()
profiling = args.program
profiledir = args.d
if profiledir:
apparmor.profile_dir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profile_dir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
for p in 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)
if os.path.exists(program):
apparmor.read_profiles()
filename = apparmor.get_profile_filename(program)
if not os.path.isfile(filename) or apparmor.is_skippable_file(filename):
continue
sys.stdout.write(_('Setting %s to audit mode.\n')%program)
apparmor.set_profile_flags(filename, 'audit')
cmd_info = apparmor.cmd(['cat', filename, '|', 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 is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p))
else:
apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p)
sys.exit(1)
sys.exit(0)

53
Tools/aa-autodep.py Normal file
View file

@ -0,0 +1,53 @@
#!/usr/bin/python
import sys
import os
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='Disable the profile for the given programs')
parser.add_argument('--force', type=str, help='path to profiles')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled')
args = parser.parse_args()
force = args.force
profiledir = args.d
profiling = args.program
aa_mountpoint = apparmor.check_for_apparmor()
if profiledir:
apparmor.profile_dir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profile_dir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
for p in 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.check_qualifiers(program)
if os.path.exists(program):
if os.path.exists(apparmor.get_profile_filename(program) and not force):
apparmor.UI_Info('Profile for %s already exists - skipping.'%program)
else:
apparmor.autodep(program)
if aa_mountpoint:
apparmor.reload(program)
else:
if '/' not in p:
apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p))
else:
apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p)
sys.exit(1)
sys.exit(0)

55
Tools/aa-complain.py Normal file
View file

@ -0,0 +1,55 @@
#!/usr/bin/python
import sys
import os
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='Switch the given program to complain mode')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('program', type=str, nargs='+', help='name of program to switch to complain mode')
args = parser.parse_args()
profiling = args.program
profiledir = args.d
if profiledir:
apparmor.profile_dir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profile_dir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
for p in 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)
if os.path.exists(program):
apparmor.read_profiles()
filename = apparmor.get_profile_filename(program)
if not os.path.isfile(filename) or apparmor.is_skippable_file(filename):
continue
sys.stdout.write(_('Setting %s to complain mode.\n')%program)
apparmor.set_profile_flags(filename, 'complain')
cmd_info = apparmor.cmd(['cat', filename, '|', 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 is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p))
else:
apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p)
sys.exit(1)
sys.exit(0)

67
Tools/aa-disable.py Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/python
import sys
import os
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='Disable the profile for the given programs')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled')
args = parser.parse_args()
profiledir = args.d
profiling = args.program
if profiledir:
apparmor.profile_dir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profile_dir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
disabledir = apparmor.profile_dir+'/disable'
if not os.path.isdir(disabledir):
raise apparmor.AppArmorException("Can't find AppArmor disable directorys %s." %disabledir)
for p in 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)
if os.path.exists(program):
apparmor.read_profiles()
filename = apparmor.get_profile_filename(program)
if not os.path.isfile(filename) or apparmor.is_skippable_file(filename):
continue
bname = os.path.basename(filename)
if not bname:
apparmor.AppArmorException(_('Unable to find basename for %s.')%filename)
sys.stdout.write(_('Disabling %s.\n')%program)
link = '%s/%s'%(disabledir, bname)
if not os.path.exists(link):
try:
os.symlink(filename, link)
except:
raise apparmor.AppArmorException('Could not create %s symlink to %s.'%(link, filename))
cmd_info = apparmor.cmd(['cat', filename, '|', 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 is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p))
else:
apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p)
sys.exit(1)
sys.exit(0)

67
Tools/aa-enforce.py Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/python
import sys
import os
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='Switch the given program to enforce mode')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('program', type=str, nargs='+', help='name of program to switch to enforce mode')
args = parser.parse_args()
profiledir = args.d
profiling = args.program
if profiledir:
apparmor.profile_dir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profile_dir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
for p in 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)
if os.path.exists(program):
apparmor.read_profiles()
filename = apparmor.get_profile_filename(program)
if not os.path.isfile(filename) or apparmor.is_skippable_file(filename):
continue
sys.stdout.write(_('Setting %s to enforce mode.\n')%program)
apparmor.set_profile_flags(filename, '')
# Remove symlink from profile_dir/force-complain
complainlink = filename
complainlink = re.sub('^%s'%apparmor.profile_dir, '%s/force-complain'%apparmor.profile_dir, complainlink)
if os.path.exists(complainlink):
os.remove(complainlink)
# remove symlink in profile_dir/disable
disablelink = filename
disablelink = re.sub('^%s'%apparmor.profile_dir, '%s/disable'%apparmor.profile_dir, disablelink)
if os.path.exists(disablelink):
os.remove(disablelink)
cmd_info = apparmor.cmd(['cat', filename, '|', 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 is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p))
else:
apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p)
sys.exit(1)
sys.exit(0)

148
Tools/aa-genprof.py Normal file
View file

@ -0,0 +1,148 @@
#!/usr/bin/python
import sys
import subprocess
import os
import re
import atexit
import argparse
import apparmor.aa as apparmor
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', type=str, help='path to profiles')
parser.add_argument('-f', 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.d
filename = args.f
aa_mountpoint = apparmor.check_for_apparmor()
if not aa_mountpoint:
raise apparmor.AppArmorException(_('AppArmor seems to have not been 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("Can't find AppArmor profiles in %s." %profiledir)
# if not profiling:
# profiling = apparmor.UI_GetString(_('Please enter the program to profile: '), '')
# if profiling:
# profiling = profiling.strip()
# else:
# sys.exit(0)
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 is correct, please run 'which %s' in another window in order to find the fully-qualified path.") %(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)
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)

View file

@ -1,15 +1,30 @@
#!/usr/bin/python
import sys
sys.path.append('../')
import apparmor.aa
import apparmor.aa as apparmor
import os
import argparse
logmark = ''
parser = argparse.ArgumentParser(description='Process log entries to generate profiles')
parser.add_argument('-d', type=str, help='path to profiles')
parser.add_argument('-f', type=str, help='path to logfile')
parser.add_argument('-m', type=str, help='mark in the log to start processing after')
args = parser.parse_args()
apparmor.aa.loadincludes()
profiledir = args.d
filename = args.f
logmark = args.m or ''
apparmor.aa.do_logprof_pass(logmark)
aa_mountpoint = apparmor.check_for_apparmor()
if not aa_mountpoint:
raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.'))
if profiledir:
apparmor.profiledir = apparmor.get_full_path(profiledir)
if not os.path.isdir(apparmor.profiledir):
raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir)
apparmor.loadincludes()
apparmor.do_logprof_pass(logmark)
sys.exit(0)

69
Tools/aa-unconfined.py Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/python
import sys
import os
import re
import argparse
import apparmor.aa as apparmor
parser = argparse.ArgumentParser(description='')
parser.add_argument('--paranoid', type=str)
args = parser.parse_args()
paranoid = args.paranoid
aa_mountpoint = apparmor.check_for_apparmor()
if not aa_mountpoint:
raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.'))
pids = []
if paranoid:
pids = list(filter(lambda x: re.search('^\d+$', x), apparmor.get_subdirectories('/proc')))
else:
regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)')
output = apparmor.cmd(['netstat','-nlp'])[1].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(lambda x: int(x), set(pids)))
for pid in sorted(pids):
try:
prog = os.readlink('/proc/%s/exe'%pid)
except:
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 = ''
if not attr:
if re.search('^(/usr)?/bin/(python|perl|bash)', prog):
cmdline = re.sub('\0', ' ', cmdline)
cmdline = re.sub('\s+$', '', cmdline).strip()
sys.stdout.write(_('%s %s (%s) not confined\n')%(pid, prog, cmdline))
else:
if pname and pname[-1] == ')':
pname += ' '
sys.stdout.write(_('%s %s %snot confined\n')%(pid, prog, pname))
else:
if re.search('^(/usr)?/bin/(python|perl|bash)', prog):
cmdline = re.sub('\0', ' ', cmdline)
cmdline = re.sub('\s+$', '', cmdline).strip()
sys.stdout.write(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr))
else:
if pname and pname[-1] == ')':
pname += ' '
sys.stdout.write(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr))
sys.exit(0)

View file

@ -18,7 +18,7 @@ import apparmor.logparser
import apparmor.severity
import LibAppArmor
from apparmor.common import (AppArmorException, error, debug, msg,
from apparmor.common import (AppArmorException, error, debug, msg, cmd,
open_file_read, valid_path,
hasher, open_file_write, convert_regexp, DebugLogger)
@ -228,7 +228,7 @@ def complain(path):
set_profile_flags(prof_filename, 'complain')
def enforce(path):
"""Sets the profile to complain mode if it exists"""
"""Sets the profile to enforce mode if it exists"""
prof_filename, name = name_to_prof_filename(path)
if not prof_filename :
fatal_error("Can't find %s" % path)
@ -241,7 +241,9 @@ def head(file):
if os.path.isfile(file):
with open_file_read(file) as f_in:
first = f_in.readline().rstrip()
return first
return first
else:
raise AppArmorException('Unable to read first line from: %s : File Not Found' %file)
def get_output(params):
"""Returns the return code output by running the program with the args given in the list"""
@ -484,7 +486,7 @@ def autodep(bin_name, pname=''):
if not bin_name and pname.startswith('/'):
bin_name = pname
if not repo_cfg and not cfg['repository'].get('url', False):
repo_conf = apparmor.config.Config('shell')
repo_conf = apparmor.config.Config('shell', CONFDIR)
repo_cfg = repo_conf.read_config('repository.conf')
if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later':
UI_ask_to_enable_repo()
@ -511,6 +513,16 @@ def autodep(bin_name, pname=''):
filelist.file = hasher()
filelist[file][include]['tunables/global'] = True
write_profile_ui_feedback(pname)
def get_profile_flags(filename):
flags = 'enforce'
with open_file_read(filename) as f_in:
for line in f_in:
if RE_PROFILE_START.search(line):
flags = RE_PROFILE_START.search(line).groups()[6]
return flags
return flags
def set_profile_flags(prof_filename, newflags):
"""Reads the old profile file and updates the flags accordingly"""
@ -821,6 +833,7 @@ def handle_children(profile, hat, root):
else:
typ = entry.pop(0)
if typ == 'fork':
# If type is fork then we (should) have pid, profile and hat
pid, p, h = entry[:3]
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
profile = p
@ -830,6 +843,7 @@ def handle_children(profile, hat, root):
else:
profile_changes[pid] = profile
elif typ == 'unknown_hat':
# If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry
pid, p, h, aamode, uhat = entry[:5]
if not regex_nullcomplain.search(p):
profile = p
@ -882,9 +896,11 @@ def handle_children(profile, hat, root):
elif ans == 'CMD_USEDEFAULT':
hat = default_hat
elif ans == 'CMD_DENY':
# As unknown hat is denied no entry for it should be made
return None
elif typ == 'capability':
# If capability then we (should) have pid, profile, hat, program, mode, capability
pid, p, h, prog, aamode, capability = entry[:6]
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
profile = p
@ -894,6 +910,7 @@ def handle_children(profile, hat, root):
prelog[aamode][profile][hat]['capability'][capability] = True
elif typ == 'path' or typ == 'exec':
# If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name
pid, p, h, prog, aamode, mode, detail, to_name = entry[:8]
if not mode:
mode = set()
@ -1086,6 +1103,7 @@ def handle_children(profile, hat, root):
default = None
if 'p' in options and os.path.exists(get_profile_filename(exec_target)):
default = 'CMD_px'
sys.stdout.write('Target profile exists: %s\n' %get_profile_filename(exec_target))
elif 'i' in options:
default = 'CMD_ix'
elif 'c' in options:
@ -1309,6 +1327,7 @@ def handle_children(profile, hat, root):
return None
elif typ == 'netdomain':
# If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol
pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8]
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
@ -1717,15 +1736,17 @@ def ask_the_questions():
else:
if aa[profile][hat]['allow']['path'][path].get('mode', False):
mode |= aa[profile][hat]['allow']['path'][path]['mode']
deleted = 0
deleted = []
for entry in aa[profile][hat]['allow']['path'].keys():
if path == entry:
continue
if matchregexp(path, entry):
if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']):
aa[profile][hat]['allow']['path'].pop(entry)
deleted += 1
deleted.append(entry)
for entry in deleted:
aa[profile][hat]['allow']['path'].pop(entry)
deleted = len(deleted)
if owner_toggle == 0:
mode = flatten_mode(mode)
@ -1948,13 +1969,15 @@ def delete_net_duplicates(netrules, incnetrules):
return deleted
def delete_cap_duplicates(profilecaps, inccaps):
deleted = 0
deleted = []
if profilecaps and inccaps:
for capname in profilecaps.keys():
if inccaps[capname].get('set', False) == 1:
profilecaps.pop(capname)
deleted += 1
return deleted
deleted.append(capname)
for capname in deleted:
profilecaps.pop(capname)
return len(deleted)
def delete_path_duplicates(profile, incname, allow):
deleted = []
@ -2047,7 +2070,7 @@ def match_net_includes(profile, family, nettype):
return newincludes
def do_logprof_pass(logmark='', pid=pid):
def do_logprof_pass(logmark='', passno=0, pid=pid):
# set up variables for this pass
t = hasher()
# transitions = hasher()
@ -2066,9 +2089,10 @@ def do_logprof_pass(logmark='', pid=pid):
# filelist = hasher()
UI_Info(_('Reading log entries from %s.') %filename)
UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir)
read_profiles()
if not passno:
UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir)
read_profiles()
if not sev_db:
sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
@ -2158,7 +2182,7 @@ def save_profiles():
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_VIEW_CHANGES_CLEAN', 'CMD_ABORT']
q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT']
q['default'] = 'CMD_VIEW_CHANGES'
q['options'] = changed
q['selected'] = 0
@ -2167,7 +2191,14 @@ def save_profiles():
arg = None
while ans != 'CMD_SAVE_CHANGES':
ans, arg = UI_PromptUser(q)
if ans == 'CMD_VIEW_CHANGES':
if ans == 'CMD_SAVE_SELECTED':
profile_name = list(changed.keys())[arg]
write_profile_ui_feedback(profile_name)
reload_base(profile_name)
changed.pop(profile_name)
#q['options'] = changed
elif ans == 'CMD_VIEW_CHANGES':
which = list(changed.keys())[arg]
oldprofile = None
if aa[which][which].get('filename', False):
@ -2485,9 +2516,8 @@ def parse_profile_data(data, file, do_include):
profile_data[profile][hat]['external'] = True
else:
hat = profile
# Profile stored
existing_profiles[profile] = file
# Profile stored
existing_profiles[profile] = file
flags = matches[6]
@ -2959,7 +2989,7 @@ def write_cap_rules(prof_data, depth, allow):
for cap in sorted(prof_data[allow]['capability'].keys()):
audit = ''
if prof_data[allow]['capability'][cap].get('audit', False):
audit = 'audit'
audit = 'audit '
if prof_data[allow]['capability'][cap].get('set', False):
data.append('%s%s%scapability %s,' %(pre, audit, allowstr, cap))
data.append('')
@ -3837,7 +3867,7 @@ def reload_base(bin_path):
def reload(bin_path):
bin_path = find_executable(bin_path)
if not bin:
if not bin_path:
return None
return reload_base(bin_path)
@ -3955,6 +3985,7 @@ def check_qualifiers(program):
'them is likely to break the rest of the system. If you know what you\'re\n' +
'doing and are certain you want to create a profile for this program, edit\n' +
'the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.') %program)
return False
def get_subdirectories(current_dir):
"""Returns a list of all directories directly inside given directory"""
@ -4048,7 +4079,7 @@ def matchregexp(new, old):
######Initialisations######
conf = apparmor.config.Config('ini')
conf = apparmor.config.Config('ini', CONFDIR)
cfg = conf.read_config('logprof.conf')
#print(cfg['settings'])

View file

@ -28,8 +28,8 @@ from apparmor.common import AppArmorException, warn, msg, open_file_read
# REPO_CFG = None
# SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf']
class Config:
def __init__(self, conf_type):
self.CONF_DIR = '/etc/apparmor'
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

View file

@ -50,7 +50,7 @@ class ReadLog:
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) or self.RE_LOG_v2_6_audit.search(self.next_log_entry)) or (self.logmark and re.search(self.logmark, self.next_log_entry)):
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
@ -328,8 +328,11 @@ class ReadLog:
except IOError:
raise AppArmorException('Can not read AppArmor logfile: ' + self.filename)
#LOG = open_file_read(log_open)
line = self.get_next_log_entry()
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:
@ -337,13 +340,12 @@ class ReadLog:
self.debug_logger.debug('read_log: seenmark = %s' %seenmark)
if not seenmark:
line = self.get_next_log_entry()
continue
continue
event = self.parse_log_record(line)
#print(event)
if event:
self.add_event_to_tree(event)
line = self.get_next_log_entry()
self.LOG.close()
self.logmark = ''
return self.log

View file

@ -17,53 +17,50 @@ class Severity:
self.severity['VARIABLES'] = dict()
if not dbname:
return None
try:
database = open_file_read(dbname)#open(dbname, 'r')
except IOError:
raise AppArmorException("Could not open severity database: %s" % dbname)
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}
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:
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
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:
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))
database.close()
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"""
@ -187,11 +184,11 @@ class Severity:
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 values" % line[0])
raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values 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" % line[0])
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):

View file

@ -42,25 +42,43 @@ def UI_Important(text):
})
path, yarg = GetDataFromYast()
def get_translated_hotkey(translated, cmsg=''):
msg = 'PromptUser: '+_('Invalid hotkey for')
if re.search('\((\S)\)', translated):
return re.search('\((\S)\)', translated).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))
ans = default
default = default.lower()
ans = None
if UI_mode == 'text':
yes = '(Y)es'
no = '(N)o'
usrmsg = 'PromptUser: Invalid hotkey for'
yeskey = 'y'
nokey = '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()#readkey()
if ans:
ans = ans.lower()
else:
ans = default
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'
else:
ans = 'n'
else:
ans = default
else:
SendDataToYast({
'type': 'dialog-yesno',
@ -74,16 +92,19 @@ def UI_YesNo(text, default):
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 = 'y'
nokey = 'n'
cancelkey = 'c'
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 != 'c' and ans != 'n' and ans != 'y':
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))
@ -91,9 +112,16 @@ def UI_YesNoCancel(text, default):
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()#readkey()
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'
else:
ans = default
else:
@ -111,7 +139,7 @@ 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 + '\n')
sys.stdout.write('\n' + text)
string = sys.stdin.readline()
else:
SendDataToYast({
@ -198,6 +226,7 @@ CMDS = {
'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',
@ -216,7 +245,7 @@ CMDS = {
'CMD_IGNORE_ENTRY': '(I)gnore'
}
def UI_PromptUser(q):
def UI_PromptUser(q, params=''):
cmd = None
arg = None
if UI_mode == 'text':
@ -230,10 +259,11 @@ def UI_PromptUser(q):
arg = yarg['selected']
if cmd == 'CMD_ABORT':
confirm_and_abort()
cmd == 'XXXINVALIDXXX'
cmd = 'XXXINVALIDXXX'
elif cmd == 'CMD_FINISHED':
confirm_and_finish()
cmd == 'XXXINVALIDXXX'
if not params:
confirm_and_finish()
cmd = 'XXXINVALIDXXX'
return (cmd, arg)
def confirm_and_abort():
@ -287,11 +317,7 @@ def Text_PromptUser(question):
menutext = _(CMDS[cmd])
menuhotkey = re.search('\((\S)\)', menutext)
if not menuhotkey:
raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in'), menutext))
key = menuhotkey.groups()[0].lower()
key = get_translated_hotkey(menutext).lower()
# Duplicate hotkey
if keys.get(key, False):
raise AppArmorException('PromptUser: %s %s: %s' %(_('Duplicate hotkey for'), cmd, menutext))
@ -306,12 +332,9 @@ def Text_PromptUser(question):
default_key = 0
if default and CMDS[default]:
defaulttext = _(CMDS[default])
defmsg = 'PromptUser: ' + _('Invalid hotkey in default item')
defaulthotkey = re.search('\((\S)\)', defaulttext)
if not menuhotkey:
raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in default item'), defaulttext))
default_key = defaulthotkey.groups()[0].lower()
default_key = get_translated_hotkey(defaulttext, defmsg).lower()
if not keys.get(default_key, False):
raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default))

View file

@ -1,114 +0,0 @@
def write_header(prof_data, depth, name, embedded_hat, write_flags):
pre = ' ' * depth
data = []
name = quote_if_needed(name)
if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)):
name = 'profile %s' % name
if write_flags and prof_data['flags']:
data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags']))
else:
data.append('%s%s {' % (pre, name))
return data
def write_rules(prof_data, depth):
data = write_alias(prof_data, depth)
data += write_list_vars(prof_data, depth)
data += write_includes(prof_data, depth)
data += write_rlimits(prof_data, depth)
data += write_capabilities(prof_data, depth)
data += write_netdomain(prof_data, depth)
data += write_links(prof_data, depth)
data += write_paths(prof_data, depth)
data += write_change_profile(prof_data, depth)
return data
def write_piece(profile_data, depth, name, nhat, write_flags):
pre = ' ' * depth
data = []
wname = None
inhat = False
if name == nhat:
wname = name
else:
wname = name + '//' + nhat
name = nhat
inhat = True
data += ['begin header']
data += write_header(profile_data[name], depth, wname, False, write_flags)
data +=['end header']
data += write_rules(profile_data[name], depth+1)
pre2 = ' ' * (depth+1)
# External hat declarations
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
if profile_data[hat].get('declared', False):
data.append('%s^%s,' %(pre2, hat))
if not inhat:
# Embedded hats
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
if not profile_data[hat]['external'] and not profile_data[hat]['declared']:
data.append('')
if profile_data[hat]['profile']:
data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags)))
else:
data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags)))
data += list(map(str, write_rules(profile_data[hat], depth+2)))
data.append('%s}' %pre2)
data.append('%s}' %pre)
# External hats
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
if name == nhat and profile_data[hat].get('external', False):
data.append('')
data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags)))
data.append(' }')
return data
def serialize_profile(profile_data, name, options):
string = ''
include_metadata = False
include_flags = True
data= []
if options:# and type(options) == dict:
if options.get('METADATA', False):
include_metadata = True
if options.get('NO_FLAGS', False):
include_flags = False
if include_metadata:
string = '# Last Modified: %s\n' %time.time()
if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url']
and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']):
repo = profile_data[name]['repo']
string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id'])
elif profile_data[name]['repo']['neversubmit']:
string += '# REPOSITORY: NEVERSUBMIT\n'
if profile_data[name].get('initial_comment', False):
comment = profile_data[name]['initial_comment']
comment.replace('\\n', '\n')
string += comment + '\n'
prof_filename = get_profile_filename(name)
if filelist.get(prof_filename, False):
data += write_alias(filelist[prof_filename], 0)
data += write_list_vars(filelist[prof_filename], 0)
data += write_includes(filelist[prof_filename], 0)
data += write_piece(profile_data, 0, name, name, include_flags)
string += '\n'.join(data)
return string+'\n'