| Current File : //bin/compliance |
#!/usr/bin/python2.7 -E
#
# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
#
#
# This program provides information about installed compliance
# benchmarks and allows the user to verify against them
# and generate result reports and compliance guides.
#
# It has the following commands:
# list [-a] [-b] [-t] [-v] [-p]
# list -b [-v] [-p] [benchmark...]
# list -a [-v] [assessment...]
# guide [-p profile] [-b benchmark] [-o file]
# guide -a
# assess [-p profile] [-b benchmark] [-a assessment]
# assess -t tailoring [-a assessment]
# report [-f format] [-s what] [-a assessment] [-o file]
# delete assessment
# tailor [-t tailoring] [subcommand]
# compliance set-policy [-b benchmark [-p profile]] [-t tailoring]
# compliance get-policy
# help
#
import errno
import fcntl
import getopt
import gettext
import locale
import os
import os.path as path
import re
import signal
import shutil
import solaris.zones as zones
import sys
import time
# import compliance modules
from compliance.common import *
import compliance.smf_util as smf_util
import compliance.tailor as tailor
import compliance.xccdf_util as xccdf_util
_ = gettext.gettext
opt_verbose = 0
def usage(m):
f = sys.stderr
p = progname
sys.stdout.flush()
if m:
print >> f, m
print >> f, _("Usage:")
print >> f, _("\t%s list [-a] [-b] [-p] [-t] [-v]") % p
print >> f, _("\t%s list -b [-v] [-p] [benchmark ...]") % p
print >> f, _("\t%s list -a [-v] [assessment ...]") % p
print >> f, _("\t%s guide [-p profile] [-b benchmark] [-o file]") % p
print >> f, _("\t%s guide -a") % p
print >> f, _("\t%s assess [-b benchmark] [-p profile] [-a assessment]") % p
print >> f, _("\t%s assess -t tailoring [-a assessment]") % p
fmt = _("\t%s report [-f format] [-s what] [-a assessment] [-o file]")
print >> f, fmt % p
print >> f, _("\t%s delete assessment") % p
print >> f, _("\t%s tailor [-t tailoring] [subcommand]") % p
fmt = _("\t%s set-policy [-b benchmark [-p profile]] [-t tailoring]")
print >> f, fmt % p
print >> f, _("\t%s get-policy") % p
exit(m and 1)
#
# get options for the particular command
#
def getcmdopt(options):
try:
opts, pargs = getopt.getopt(sys.argv[2:], options)
except getopt.GetoptError as e:
usage(e)
return (opts, pargs)
#
# compliance list [-b [-p] [profile]] [-a assessment]
#
def do_list():
"""
process list command
"""
exit_val = 0
opts, pargs = getcmdopt("abptv")
opt_a = False
opt_b = False
opt_p = False
opt_t = False
global opt_verbose
for opt, arg in opts:
if opt == '-a':
opt_a = True
elif opt == '-b':
opt_b = True
elif opt == '-p':
opt_p = True
elif opt == '-t':
opt_t = True
elif opt == '-v':
opt_verbose += 1
if pargs:
if not any((opt_b, opt_a)):
usage(_("Extra arguments require -b or -a"))
if opt_a and any((opt_b, opt_p)):
usage(_("Extra arguments used with -a and either -b or -p"))
if opt_t:
usage(_("Extra arguments can't be used with -t"))
# list benchmarks
if any((opt_b, opt_p)) or not any((opt_a, opt_t)):
benchinfo = []
indent = ""
sepr = " "
if not any((opt_b, opt_p)) or any((opt_a, opt_t)):
print _("Benchmarks:")
indent = "\t"
sepr = "\n"
if opt_p or opt_verbose:
indent = ""
sepr = "\n"
benchmarks = xccdf_util.bench_list()
if len(pargs):
for arg in pargs:
if benchmarks.count(arg) == 0:
if path.isabs(arg):
benchmarks.append(arg)
else:
print >> sys.stderr, _("No benchmark '%s' found") % arg
exit_val = 1
for benchmark in benchmarks:
xccdf = benchmark
if not path.isabs(benchmark):
xccdf = xccdf_util.bench_path(benchmark)
if len(pargs) and pargs.count(benchmark) == 0:
continue
info = benchmark
try:
collections = xccdf_util.xccdf_collect(filename=xccdf)
except Exception as e:
print >> sys.stderr, e
continue
if opt_p:
info += ":\t"
proflist = [p.id for p in collections["Profile"] if p.id]
if len(proflist):
info += strjoin(", ", proflist)
else:
info += _("<No profiles specified>")
if opt_verbose:
b = collections["Benchmark"][0]
if b.title:
info += "\n\t\t" + b.title
if opt_verbose > 1:
info += "\n\t"
infolist = [r.title for r in collections["Rule"] if r.title]
if len(infolist):
info += strjoin("\n\t", infolist)
benchinfo.append(info)
if len(benchmarks):
if len(benchinfo):
benchinfo.sort()
print indent + strjoin(sepr + indent, benchinfo)
else:
print indent + _("No benchmarks available")
# list assessments
if opt_a or not any((opt_b, opt_p, opt_t)):
indent = ""
sepr = "\n"
if not opt_a or any((opt_b, opt_p, opt_t)):
print _("Assessments:")
indent = "\t"
if opt_verbose:
indent = ""
try:
assessments = os.listdir(ASSESSMENTS)
except:
assessments = []
if len(pargs):
for arg in pargs:
if assessments.count(arg) == 0:
print >> sys.stderr, _("No assessment '%s' found") % arg
exit_val = 1
assessmentinfo = []
for assessment in assessments:
if len(pargs) and pargs.count(assessment) == 0:
continue
info = assessment
if opt_verbose:
info += ":\t"
try:
reports = os.listdir(path.join(ASSESSMENTS, assessment))
except:
reports = []
if len(reports):
reports.sort()
info += strjoin(" ", reports)
else:
info += _("No reports have been generated")
assessmentinfo.append(info)
if len(assessments):
if len(assessmentinfo):
assessmentinfo.sort()
print indent + strjoin(sepr + indent, assessmentinfo)
else:
print indent + _("No assessments available")
# list tailorings
if opt_t:
if any((opt_a, opt_b, opt_p)):
print _("Tailorings:")
tailor.list_tailorings(opt_verbose)
exit(exit_val)
# get file mtime
def getmtime(fpath, mtime=0):
try:
statbuf = os.stat(fpath)
mtime = statbuf.st_mtime
except:
pass
return mtime
#
# create a new process
#
def newproc():
try:
child = os.fork()
except Exception as e:
fatal(3, _("Cannot create child process: %s") % e)
return child
#
# print exec file name and arguments
#
def printexecargs(f, a):
print >> f, "+", a[0], strjoin(" ", a[1:], "'")
#
# compliance guide [-a] [-p profile] [-b benchmark] [-o file]
#
def do_guide():
"""
process guide command
"""
opts, pargs = getcmdopt("ab:o:p:qv")
opt_a = None
opt_b = None
opt_o = None
opt_p = None
global opt_verbose
for opt, arg in opts:
if (opt == '-a'):
opt_a = True
if (opt == '-b'):
opt_b = arg
elif (opt == '-o'):
opt_o = arg
elif (opt == '-p'):
opt_p = arg
elif (opt == '-v'):
opt_verbose += 1
if not service and len(pargs) > 0:
usage(_("Unrecognized parameters specified"))
if opt_a and (opt_b or opt_o or opt_p):
usage(_("Option -a cannot be used with other options"))
guides_locked = []
def lock_guides():
if guides_locked:
return
lock = path.join(GUIDES, ".lock")
try:
lockf = open(lock, 'w')
fcntl.lockf(lockf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB, 1, 1, 0)
guides_locked.append(True)
except Exception as e:
# no service error
fatal(serviceOK(1), _("Cannot lock guides storage: %s") % str(e))
def guide_path(benchmark, profile):
gfile = benchmark
if profile:
gfile += "." + profile
gfile += ".html"
return path.join(GUIDES, gfile)
def gen_guide(xccdf, profile, gfile):
myargv = [OSCAP, "xccdf", "generate"]
myargv.append("guide")
if profile:
myargv.extend(["--profile", profile])
myargv.extend(["--output", gfile, xccdf])
if opt_verbose:
printexecargs(sys.stderr, myargv)
status = os.spawnv(os.P_WAIT, myargv[0], myargv)
return os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0
if opt_a or service:
#
# process all installed benchmarks and associated profiles
# as a service process, silently give up
# if we can't create directories or lock guides
#
if not path.isdir(GUIDES):
create_shared_dirs(GUIDES, "guides")
lock_guides()
benchmarks = xccdf_util.bench_list()
for benchmark in benchmarks:
xccdf = xccdf_util.bench_path(benchmark)
xccdftime = getmtime(xccdf, time.time())
guidefile = guide_path(benchmark, None)
guidetime = getmtime(guidefile)
if guidetime > xccdftime:
if not service:
print guidefile
elif gen_guide(xccdf, None, guidefile):
print guidefile
else:
continue
proflist = xccdf_util.benchmark_profiles(xccdf)
for profile in proflist:
guidefile = guide_path(benchmark, profile)
guidetime = getmtime(guidefile)
if guidetime > xccdftime:
if not service:
print guidefile
elif gen_guide(xccdf, profile, guidefile):
print guidefile
return
#
# generate a single guide specified by options
#
if not opt_b:
opt_b = "solaris"
xccdf = opt_b
if not path.isabs(opt_b):
xccdf = xccdf_util.bench_path(opt_b)
if not path.isfile(xccdf):
fatal(1, _("Benchmark tests file not found: %s") % xccdf)
if opt_p:
proflist = xccdf_util.benchmark_profiles(xccdf)
if opt_p not in proflist:
fatal(1, _("Benchmark %(b)s has no '%(p)s' profile") %
{"b": opt_b, "p": opt_p})
if opt_o:
guidefile = opt_o
ruid = os.getuid()
if os.geteuid() != ruid:
# drop privileges before accessing user-specified pathname
os.setreuid(ruid, ruid)
else:
if not path.isdir(GUIDES):
create_shared_dirs(GUIDES, "guides")
gfile = opt_b
if opt_p:
gfile += "." + opt_p
gfile += ".html"
guidefile = path.join(GUIDES, gfile)
if not path.isfile(guidefile):
lock_guides()
if not opt_o and path.isfile(guidefile) or gen_guide(xccdf, opt_p,
guidefile):
print guidefile
exit(0)
else:
print _("Unable to create guide file: %s") % guidefile
exit(1)
#
# compliance assess [-p profile] [-b benchmark] [-a assessment]
#
def do_assess():
"""
process assess command
"""
opts, pargs = getcmdopt("a:b:p:qt:v")
opt_a = None
opt_b = None
opt_p = None
opt_q = False
opt_t = None
global opt_verbose
for opt, arg in opts:
if (opt == '-a'):
opt_a = arg
elif (opt == '-b'):
opt_b = arg
elif (opt == '-p'):
opt_p = arg
elif (opt == '-q'):
opt_q = True
elif (opt == '-t'):
opt_t = arg
elif (opt == '-v'):
opt_verbose += 1
user = realuser()
if not haveauth(AUTH_ASSESS, user):
fatal(1, _("User %(u)s lacks authorization %(a)s") %
{"u": user, "a": AUTH_ASSESS})
if not any((opt_b, opt_p, opt_t)):
(opt_b, opt_p, opt_t) = smf_util.get_policy()
msgs = []
if opt_t:
if opt_b:
opt_t = "%s/%s" % (opt_b, opt_t)
opt_b = None
if opt_p:
usage(_("Option -t cannot be used with -p option"))
# compute benchmark and profile from tailoring
(opt_b, opt_p, tpath, msgs) = tailor.getassessopts(opt_t)
if not opt_b or not opt_p:
fatal(1, _("Unable to use tailoring '%(t)s': %(m)s") %
{"t": opt_t, "m": msgs})
if not opt_b:
opt_b = "solaris"
xccdf = xccdf_util.bench_path(opt_b)
if not path.isfile(xccdf):
fatal(1, _("Benchmark tests file not found: %s") % xccdf)
if not opt_p:
proflist = xccdf_util.benchmark_profiles(xccdf)
if len(proflist):
opt_p = proflist[0]
if len(pargs) > 0:
usage(_("Unrecognized parameters specified"))
if not opt_a:
timept = time.time()
now = time.localtime(timept)
if opt_t:
opt_a = re.sub("/", "-", opt_t)
else:
opt_a = opt_b
if opt_p:
opt_a += "." + opt_p
opt_a += time.strftime(".%F,%R", now)
if not opt_q:
print _("Assessment will be named '%s'") % opt_a
if re.search("/", opt_a):
fatal(1, _("Invalid assessment name: '%s'") % opt_a)
assessmentrep = path.join(ASSESSMENTS, opt_a)
if path.isdir(assessmentrep):
fatal(1, _("Assessment repository already exists: %s") % assessmentrep)
else:
create_shared_dirs(COMPLIANCE_VAR, "compliance")
# make the assessments and assessment directories mode 0711
create_shared_dirs(assessmentrep, "assessment", umask=0066)
try: # set isglobalzone indicator file
isgzpath = path.join(COMPLIANCE_VAR, ".isglobalzone")
if zones.getzoneid() == 0:
isgzfile = open(isgzpath, "w+")
isgzfile.close()
else:
os.remove(isgzpath)
except:
pass
log = path.join(assessmentrep, "log")
results = path.join(assessmentrep, RESULTS)
report = path.join(assessmentrep, REPORT)
evalargv = [OSCAP, "xccdf", "eval"]
if opt_t:
evalargv.extend(["--tailoring-file", tpath])
if opt_p:
evalargv.extend(["--profile", opt_p])
evalargv.extend(["--results", results])
evalargv.extend(["--report", report])
evalargv.append(xccdf)
sys.stdout.flush()
logf = None
try:
logf = open(log, 'w')
os.fchmod(logf.fileno(), 0644)
# lock usage -
# byte 0: assessment active
# byte 1: directory contents being updated
fcntl.lockf(logf.fileno(), fcntl.LOCK_EX, 2, 0, 0)
printexecargs(logf, evalargv)
for m in msgs:
print >> logf, m
logf.flush()
except:
fatal(3, _("Can't establish log: '%s'") % log)
for m in msgs:
print >> sys.stderr, m
sys.stderr.flush()
if not opt_q:
teeargv = ["/usr/bin/tee", "-ai", log]
teeinfd, evaloutfd = os.pipe()
evalout = os.fdopen(evaloutfd, "w")
teechild = newproc()
if teechild == 0:
os.dup2(teeinfd, sys.stdin.fileno())
evalout.close()
try:
os.execv(teeargv[0], teeargv)
except Exception, e:
fatal(3, _("Failed to execute: %(c)s: %(e)s") %
{"c": strjoin(" ", teeargv, "'"), "e": e})
os.close(teeinfd)
else:
evalout = logf
evalchild = newproc()
if evalchild == 0:
os.dup2(evalout.fileno(), sys.stdout.fileno())
if not opt_q:
os.dup2(evalout.fileno(), sys.stderr.fileno())
try:
os.execv(evalargv[0], evalargv)
except Exception, e:
fatal(3, _("Failed to execute: %(c)s: %(e)s") %
{"c": strjoin(" ", evalargv, "'"), "e": e})
evalout.close()
# wait for teechild, evalchild
sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
if not opt_q:
_proc, status = os.waitpid(teechild, 0)
_proc, status = os.waitpid(evalchild, 0)
signal.signal(signal.SIGINT, sigint_handler)
if not os.WIFEXITED(status):
fatal(3, _("Execution failure: %s") % strjoin(" ", evalargv, "'"))
eval_status = os.WEXITSTATUS(status)
if not opt_q:
print _("Assessment %s completed") % opt_a
elif eval_status == 2:
# oscap's exit code for successful run with failures
eval_status = 0
exit(eval_status)
#
# compliance report [-f format] [-a assessment]
#
def do_report():
"""
process report command
"""
opts, pargs = getcmdopt("a:f:o:s:v")
opt_a = None
opt_f = None
opt_o = None
opt_s = None
global opt_verbose
for opt, arg in opts:
if (opt == '-a'):
opt_a = arg
elif (opt == '-f'):
opt_f = arg
elif (opt == '-o'):
opt_o = arg
elif (opt == '-s'):
opt_s = arg
elif (opt == '-v'):
opt_verbose += 1
def bad_assessment(_rfile):
print (_("The assessment '%s' has a corrupted repository.") % opt_a)
print (_("Please delete and retake the assessment."))
return 0
def do_html(rfile):
xccdf = path.join(assessmentrep, RESULTS)
myargv = [OSCAP, "xccdf", "generate"]
myargv.append("report")
if opt_s:
myargv.extend(["--show", opt_s])
myargv.extend(["--output", rfile, xccdf])
if opt_verbose:
printexecargs(sys.stderr, myargv)
status = os.spawnv(os.P_WAIT, myargv[0], myargv)
return os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0
FORMATS = ( # (option, filename, function)
("html", REPORT, do_html),
("log", "log", bad_assessment),
("xccdf", RESULTS, bad_assessment))
if not opt_f:
opt_f = "html"
if opt_s and opt_f != "html":
usage(_("Use of -s option only permitted with html format"))
try:
_fname, ffile, ffunc = [f for f in FORMATS if f[0] == opt_f][0]
except Exception as _e:
fnames = [f[0] for f in FORMATS]
usage(_("Unrecognized format request '%(o)s', valid choices: %(c)s") %
{"o": opt_f, "c": strjoin(", ", fnames)})
if len(pargs) > 0:
usage(_("Unrecognized parameters specified"))
if not opt_a:
# scan assessments, find newest log
newesttime = None
try:
assessments = os.listdir(ASSESSMENTS)
except:
assessments = []
for assessment in assessments:
try:
logpath = path.join(ASSESSMENTS, assessment, "log")
logtime = path.getctime(logpath)
except:
continue
if not newesttime or logtime > newesttime:
opt_a = assessment
newesttime = logtime
if not opt_a:
fatal(1, _("No assessments available"))
if re.search("/", opt_a):
fatal(1, _("Invalid assessment name: '%s'") % opt_a)
assessmentrep = path.join(ASSESSMENTS, opt_a)
if not path.isdir(assessmentrep):
fatal(1, _("Assessment repository does not exist: %s") % assessmentrep)
if opt_s:
exact = len(opt_s) and opt_s[0] == '='
sl = opt_s[exact:].split(",")
sl.sort()
opt_s = (exact and "=" or "") + reduce(lambda a, b: a + "," + b, sl)
ffile = "report." + opt_s + ".html"
reportfile = path.join(assessmentrep, ffile)
log = path.join(assessmentrep, "log")
logf = None
try:
if opt_o or path.isfile(reportfile):
logf = open(log, 'r')
fcntl.lockf(logf.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB, 1, 0, 0)
else:
logf = open(log, 'r+')
fcntl.lockf(logf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB, 1, 1, 0)
statbuf = os.fstat(logf.fileno())
if not statbuf or statbuf.st_size == 0:
raise EnvironmentError
except:
fatal(1, _("Assessment '%s' is not finished") % opt_a)
if opt_o:
logf.close()
ruid = os.getuid()
if os.geteuid() != ruid:
# drop privileges before accessing user-specified pathname
os.setreuid(ruid, ruid)
reportfile = opt_o
if not opt_o and path.isfile(reportfile) or ffunc(reportfile):
print reportfile
exit(0)
else:
print _("Unable to create report file: %s") % reportfile
exit(1)
#
# compliance delete assessment
#
def do_delete():
"""
process delete command
"""
_opts, pargs = getcmdopt("")
if len(pargs) < 1:
usage(_("Missing argument: assessment"))
if len(pargs) > 1:
usage(_("More than one assessment specified"))
user = realuser()
if not haveauth(AUTH_ASSESS, user):
fatal(1, _("User %(u)s lacks authorization %(a)s") %
{"u": user, "a": AUTH_ASSESS})
assessment = pargs[0]
if re.search("/", assessment) or len(assessment) == 0:
fatal(1, _("Invalid assessment name: '%s'") % assessment)
assessmentrep = path.join(ASSESSMENTS, assessment)
shutil.rmtree(assessmentrep, True)
def do_get_policy():
(benchmark, profile, tailoring) = smf_util.get_policy()
print _("Benchmark:\t%s") % benchmark
print _("Profile:\t%s") % profile
print _("Tailoring:\t%s") % tailoring
def do_set_policy():
opts, pargs = getcmdopt("b:p:t:")
opt_b = ""
opt_p = ""
opt_t = ""
for opt, arg in opts:
if (opt == '-b'):
opt_b = arg
if (opt == '-p'):
opt_p = arg
if (opt == '-t'):
opt_t = arg
if pargs:
usage(_("Extra arguments for set-policy"))
if opt_t:
if opt_p:
usage(_("Option -t cannot be used with -p"))
if opt_b:
tailoring = "%s/%s" % (opt_b, opt_t)
else:
tailoring = opt_t
# compute benchmark and profile from tailoring
(benchmark, profile, _tpath, msgs) = tailor.getassessopts(tailoring)
if not benchmark or not profile:
fatal(1, _("Tailoring not found '%(t)s': %(m)s") %
{"t": tailoring, "m": msgs})
elif opt_b:
xccdf = xccdf_util.bench_path(opt_b)
if not path.isfile(xccdf):
fatal(1, _("Benchmark tests file not found: %s") % xccdf)
if opt_p:
proflist = xccdf_util.benchmark_profiles(xccdf)
if opt_p not in proflist:
fatal(1, _("Benchmark %(b)s has no '%(p)s' profile") %
{"b": opt_b, "p": opt_p})
elif opt_p:
usage(_("Option -p cannot be used without -b"))
if not any((opt_b, opt_p, opt_t)):
usage(_("No policy parameters specified"))
smf_util.set_policy(opt_b, opt_p, opt_t)
#
# main program
#
try:
locale.setlocale(locale.LC_ALL, "")
except locale.Error:
pass
gettext.textdomain("compliance")
if (len(sys.argv) == 1):
usage(_("No command specified"))
#
# main program dispatch
#
if (command == "smf-service"):
if not os.environ.get("SMF_FMRI"):
fatal(1, _("Service functionality invoked outside service context"))
if len(sys.argv) < 2:
fatal(1, _("Service method not specified"))
service = sys.argv[2]
service_set(service) # propagate to common namespace
if service not in ("start", "refresh"):
fatal(1, _("Invalid service method: %s") % service)
do_guide()
elif (command == "list"):
do_list()
elif (command == "guide"):
do_guide()
elif (command == "assess"):
do_assess()
elif (command == "report"):
do_report()
elif (command == "delete"):
do_delete()
elif (command == "tailor"):
tailor.do_tailor()
elif (command == "get-policy"):
do_get_policy()
elif (command == "set-policy"):
do_set_policy()
elif re.search("help", command, re.IGNORECASE):
usage(None)
else:
usage(_("Command '%s' not recognized") % command)