371 lines
14 KiB
Python
371 lines
14 KiB
Python
#! /usr/bin/python -Es
|
|
# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
|
|
# Authors: Dan Walsh <dwalsh@redhat.com>
|
|
#
|
|
# Copyright (C) 2006-2013 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation; version 2 only
|
|
#
|
|
# 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.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
|
|
import sepolgen.audit as audit
|
|
import sepolgen.policygen as policygen
|
|
import sepolgen.interfaces as interfaces
|
|
import sepolgen.output as output
|
|
import sepolgen.objectmodel as objectmodel
|
|
import sepolgen.defaults as defaults
|
|
import sepolgen.module as module
|
|
from sepolgen.sepolgeni18n import _
|
|
import selinux.audit2why as audit2why
|
|
import locale
|
|
try:
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
except:
|
|
pass
|
|
|
|
|
|
class AuditToPolicy:
|
|
VERSION = "%prog .1"
|
|
SYSLOG = "/var/log/messages"
|
|
|
|
def __init__(self):
|
|
self.__options = None
|
|
self.__parser = None
|
|
self.__avs = None
|
|
|
|
def __parse_options(self):
|
|
from optparse import OptionParser
|
|
|
|
parser = OptionParser(version=self.VERSION)
|
|
parser.add_option("-b", "--boot", action="store_true", dest="boot", default=False,
|
|
help="audit messages since last boot conflicts with -i")
|
|
parser.add_option("-a", "--all", action="store_true", dest="audit", default=False,
|
|
help="read input from audit log - conflicts with -i")
|
|
parser.add_option("-p", "--policy", dest="policy", default=None, help="Policy file to use for analysis")
|
|
parser.add_option("-d", "--dmesg", action="store_true", dest="dmesg", default=False,
|
|
help="read input from dmesg - conflicts with --all and --input")
|
|
parser.add_option("-i", "--input", dest="input",
|
|
help="read input from <input> - conflicts with -a")
|
|
parser.add_option("-l", "--lastreload", action="store_true", dest="lastreload", default=False,
|
|
help="read input only after the last reload")
|
|
parser.add_option("-r", "--requires", action="store_true", dest="requires", default=False,
|
|
help="generate require statements for rules")
|
|
parser.add_option("-m", "--module", dest="module",
|
|
help="set the module name - implies --requires")
|
|
parser.add_option("-M", "--module-package", dest="module_package",
|
|
help="generate a module package - conflicts with -o and -m")
|
|
parser.add_option("-o", "--output", dest="output",
|
|
help="append output to <filename>, conflicts with -M")
|
|
parser.add_option("-D", "--dontaudit", action="store_true",
|
|
dest="dontaudit", default=False,
|
|
help="generate policy with dontaudit rules")
|
|
parser.add_option("-R", "--reference", action="store_true", dest="refpolicy",
|
|
default=True, help="generate refpolicy style output")
|
|
|
|
parser.add_option("-N", "--noreference", action="store_false", dest="refpolicy",
|
|
default=False, help="do not generate refpolicy style output")
|
|
parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
|
|
default=False, help="explain generated output")
|
|
parser.add_option("-e", "--explain", action="store_true", dest="explain_long",
|
|
default=False, help="fully explain generated output")
|
|
parser.add_option("-t", "--type", help="only process messages with a type that matches this regex",
|
|
dest="type")
|
|
parser.add_option("--perm-map", dest="perm_map", help="file name of perm map")
|
|
parser.add_option("--interface-info", dest="interface_info", help="file name of interface information")
|
|
parser.add_option("--debug", dest="debug", action="store_true", default=False,
|
|
help="leave generated modules for -M")
|
|
parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"),
|
|
help="Translates SELinux audit messages into a description of why the access was denied")
|
|
|
|
options, args = parser.parse_args()
|
|
|
|
# Make -d, -a, and -i conflict
|
|
if options.audit is True or options.boot:
|
|
if options.input is not None:
|
|
sys.stderr.write("error: --all/--boot conflicts with --input\n")
|
|
if options.dmesg is True:
|
|
sys.stderr.write("error: --all/--boot conflicts with --dmesg\n")
|
|
if options.input is not None and options.dmesg is True:
|
|
sys.stderr.write("error: --input conflicts with --dmesg\n")
|
|
|
|
# Turn on requires generation if a module name is given. Also verify
|
|
# the module name.
|
|
if options.module:
|
|
name = options.module
|
|
else:
|
|
name = options.module_package
|
|
if name:
|
|
options.requires = True
|
|
if not module.is_valid_name(name):
|
|
sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n')
|
|
sys.exit(2)
|
|
|
|
# Make -M and -o conflict
|
|
if options.module_package:
|
|
if options.output:
|
|
sys.stderr.write("error: --module-package conflicts with --output\n")
|
|
sys.exit(2)
|
|
if options.module:
|
|
sys.stderr.write("error: --module-package conflicts with --module\n")
|
|
sys.exit(2)
|
|
|
|
self.__options = options
|
|
|
|
def __read_input(self):
|
|
parser = audit.AuditParser(last_load_only=self.__options.lastreload)
|
|
|
|
filename = None
|
|
messages = None
|
|
f = None
|
|
|
|
# Figure out what input we want
|
|
if self.__options.input is not None:
|
|
filename = self.__options.input
|
|
elif self.__options.dmesg:
|
|
messages = audit.get_dmesg_msgs()
|
|
elif self.__options.audit:
|
|
try:
|
|
messages = audit.get_audit_msgs()
|
|
except OSError as e:
|
|
sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
|
|
sys.exit(1)
|
|
elif self.__options.boot:
|
|
try:
|
|
messages = audit.get_audit_boot_msgs()
|
|
except OSError as e:
|
|
sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
|
|
sys.exit(1)
|
|
else:
|
|
# This is the default if no input is specified
|
|
f = sys.stdin
|
|
|
|
# Get the input
|
|
if filename is not None:
|
|
try:
|
|
f = open(filename)
|
|
except IOError as e:
|
|
sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e)))
|
|
sys.exit(1)
|
|
|
|
if f is not None:
|
|
parser.parse_file(f)
|
|
f.close()
|
|
|
|
if messages is not None:
|
|
parser.parse_string(messages)
|
|
|
|
self.__parser = parser
|
|
|
|
def __process_input(self):
|
|
if self.__options.type:
|
|
avcfilter = audit.AVCTypeFilter(self.__options.type)
|
|
self.__avs = self.__parser.to_access(avcfilter)
|
|
csfilter = audit.ComputeSidTypeFilter(self.__options.type)
|
|
self.__role_types = self.__parser.to_role(csfilter)
|
|
else:
|
|
self.__avs = self.__parser.to_access()
|
|
self.__role_types = self.__parser.to_role()
|
|
|
|
def __load_interface_info(self):
|
|
# Load interface info file
|
|
if self.__options.interface_info:
|
|
fn = self.__options.interface_info
|
|
else:
|
|
fn = defaults.interface_info()
|
|
try:
|
|
fd = open(fn)
|
|
except:
|
|
sys.stderr.write("could not open interface info [%s]\n" % fn)
|
|
sys.exit(1)
|
|
|
|
ifs = interfaces.InterfaceSet()
|
|
ifs.from_file(fd)
|
|
fd.close()
|
|
|
|
# Also load perm maps
|
|
if self.__options.perm_map:
|
|
fn = self.__options.perm_map
|
|
else:
|
|
fn = defaults.perm_map()
|
|
try:
|
|
fd = open(fn)
|
|
except:
|
|
sys.stderr.write("could not open perm map [%s]\n" % fn)
|
|
sys.exit(1)
|
|
|
|
perm_maps = objectmodel.PermMappings()
|
|
perm_maps.from_file(fd)
|
|
|
|
return (ifs, perm_maps)
|
|
|
|
def __output_modulepackage(self, writer, generator):
|
|
generator.set_module_name(self.__options.module_package)
|
|
filename = self.__options.module_package + ".te"
|
|
packagename = self.__options.module_package + ".pp"
|
|
|
|
try:
|
|
fd = open(filename, "w")
|
|
except IOError as e:
|
|
sys.stderr.write("could not write output file: %s\n" % str(e))
|
|
sys.exit(1)
|
|
|
|
writer.write(generator.get_module(), fd)
|
|
fd.close()
|
|
|
|
mc = module.ModuleCompiler()
|
|
|
|
try:
|
|
mc.create_module_package(filename, self.__options.refpolicy)
|
|
except RuntimeError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
|
|
sys.stdout.write(_("******************** IMPORTANT ***********************\n"))
|
|
sys.stdout.write((_("To make this policy package active, execute:" +
|
|
"\n\nsemodule -i %s\n\n") % packagename))
|
|
|
|
def __output_audit2why(self):
|
|
import selinux
|
|
import sepolicy
|
|
for i in self.__parser.avc_msgs:
|
|
rc = i.type
|
|
data = i.data
|
|
if rc >= 0:
|
|
print("%s\n\tWas caused by:" % i.message)
|
|
if rc == audit2why.ALLOW:
|
|
print("\t\tUnknown - would be allowed by active policy")
|
|
print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
|
|
print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
|
|
continue
|
|
if rc == audit2why.DONTAUDIT:
|
|
print("\t\tUnknown - should be dontaudit'd by active policy")
|
|
print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
|
|
print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
|
|
continue
|
|
if rc == audit2why.BOOLEAN:
|
|
if len(data) > 1:
|
|
print("\tOne of the following booleans was set incorrectly.")
|
|
for b in data:
|
|
print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(b[0]))
|
|
print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1]))
|
|
else:
|
|
print("\tThe boolean %s was set incorrectly. " % (data[0][0]))
|
|
print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(data[0][0]))
|
|
print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1]))
|
|
continue
|
|
|
|
if rc == audit2why.TERULE:
|
|
print("\t\tMissing type enforcement (TE) allow rule.\n")
|
|
print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n")
|
|
continue
|
|
|
|
if rc == audit2why.CONSTRAINT:
|
|
print() # !!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access.\n"
|
|
print("#Constraint rule:")
|
|
print("\n#\t" + data[0])
|
|
for reason in data[1:]:
|
|
print("#\tPossible cause is the source %s and target %s are different.\n" % reason)
|
|
|
|
if rc == audit2why.RBAC:
|
|
print("\t\tMissing role allow rule.\n")
|
|
print("\t\tAdd an allow rule for the role pair.\n")
|
|
continue
|
|
|
|
if rc == audit2why.BOUNDS:
|
|
print("\t\tTypebounds violation.\n")
|
|
print("\t\tAdd an allow rule for the parent type.\n")
|
|
continue
|
|
|
|
audit2why.finish()
|
|
return
|
|
|
|
def __output(self):
|
|
|
|
if self.__options.audit2why:
|
|
try:
|
|
return self.__output_audit2why()
|
|
except RuntimeError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
|
|
g = policygen.PolicyGenerator()
|
|
|
|
g.set_gen_dontaudit(self.__options.dontaudit)
|
|
|
|
if self.__options.module:
|
|
g.set_module_name(self.__options.module)
|
|
|
|
# Interface generation
|
|
if self.__options.refpolicy:
|
|
ifs, perm_maps = self.__load_interface_info()
|
|
g.set_gen_refpol(ifs, perm_maps)
|
|
|
|
# Explanation
|
|
if self.__options.verbose:
|
|
g.set_gen_explain(policygen.SHORT_EXPLANATION)
|
|
if self.__options.explain_long:
|
|
g.set_gen_explain(policygen.LONG_EXPLANATION)
|
|
|
|
# Requires
|
|
if self.__options.requires:
|
|
g.set_gen_requires(True)
|
|
|
|
# Generate the policy
|
|
g.add_access(self.__avs)
|
|
g.add_role_types(self.__role_types)
|
|
|
|
# Output
|
|
writer = output.ModuleWriter()
|
|
|
|
# Module package
|
|
if self.__options.module_package:
|
|
self.__output_modulepackage(writer, g)
|
|
else:
|
|
# File or stdout
|
|
if self.__options.module:
|
|
g.set_module_name(self.__options.module)
|
|
|
|
if self.__options.output:
|
|
fd = open(self.__options.output, "a")
|
|
else:
|
|
fd = sys.stdout
|
|
writer.write(g.get_module(), fd)
|
|
|
|
def main(self):
|
|
try:
|
|
self.__parse_options()
|
|
if self.__options.policy:
|
|
audit2why.init(self.__options.policy)
|
|
else:
|
|
audit2why.init()
|
|
|
|
self.__read_input()
|
|
self.__process_input()
|
|
self.__output()
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|
|
except ValueError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
except IOError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
app = AuditToPolicy()
|
|
app.main()
|