From 0a473397a8f9b7ddf14b45ea4f84160d4c292111 Mon Sep 17 00:00:00 2001
From: Jan Lieskovsky <jlieskov@redhat.com>
Date: Tue, 17 Sep 2013 16:39:43 +0200
Subject: [PATCH 7/8] Add FEDORA/utils content (README plus two verification
 scripts).


Signed-off-by: Jan Lieskovsky <jlieskov@redhat.com>
---
 FEDORA/utils/README                 |  36 +++++++
 FEDORA/utils/verify-input-sanity.py | 151 +++++++++++++++++++++++++++++
 FEDORA/utils/verify-references.py   | 185 ++++++++++++++++++++++++++++++++++++
 3 files changed, 372 insertions(+)
 create mode 100644 FEDORA/utils/README
 create mode 100755 FEDORA/utils/verify-input-sanity.py
 create mode 100755 FEDORA/utils/verify-references.py

diff --git a/FEDORA/utils/README b/FEDORA/utils/README
new file mode 100644
index 0000000..89c6ded
--- /dev/null
+++ b/FEDORA/utils/README
@@ -0,0 +1,36 @@
+This file is meant to give a quick overview to some of the 
+scripts located in this directory.
+
+verify-input-sanity.py
+	Purpose:
+		This script can be invoked without arguments to examine the 
+		files within src/input to make sure that they are in good 
+		shape *prior to* building the XCCDF and OVAL content with the 
+		various make commands.
+	Intent:
+		Help XCCDF and OVAL developers spot common mistakes *before*
+		utilizing the Makefile to build the XCCDF and OVAL content.
+	Usage:
+		./verify-input-sanity.py
+
+verify-references.py
+	Purpose:
+		This script can be used to perform various checks on the XCCDF
+		and OVAL that is generated by the Makefile. Unlike the
+		verify-input-sanity.py script, this script limits its focus to
+		the files in the src/output directory. This script is to be
+		used as a development tool to aid in the creation of concise
+		and structurally correct XCCDF and OVAL. 
+	Intent:
+		Help XCCDF and OVAL developers spot potential mistakes in the
+		XCCDF and OVAL content that is generated by the Makefile.
+	Usage:
+		The script assumes that your current working directory is
+		src/output so if you are currently in the transforms directory:
+
+		cd ../output
+		../transforms/verify-references.py --all-checks ./rhel6-xccdf.xml
+
+		You may find this informative as well:
+
+		./verify-references.py -h
diff --git a/FEDORA/utils/verify-input-sanity.py b/FEDORA/utils/verify-input-sanity.py
new file mode 100755
index 0000000..1e0ec2c
--- /dev/null
+++ b/FEDORA/utils/verify-input-sanity.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+
+#
+# verify-input-sanity.py
+#   perform sanity checks on the individual OVAL checks that exist within the src/input/checks directory
+#
+
+# the python modules that we need
+import os, re
+import lxml.etree as ET
+
+# the "oval_header" variable must be prepended to the body of the check to form valid XML
+oval_header = '''<?xml version="1.0" encoding="UTF-8"?>
+<oval_definitions
+    xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
+    xmlns:unix="http://oval.mitre.org/XMLSchema/oval-definitions-5#unix"
+    xmlns:ind="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent"
+    xmlns:linux="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"
+    xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd
+        http://oval.mitre.org/XMLSchema/oval-definitions-5#independent independent-definitions-schema.xsd
+        http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd
+        http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd
+        http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd">
+       <generator>
+        <oval:product_name>testcheck.py</oval:product_name>
+        <oval:product_version>0.0.1</oval:product_version>
+        <oval:schema_version>5.10</oval:schema_version>
+        <oval:timestamp>2011-09-23T13:44:00</oval:timestamp>
+    </generator>'''
+
+# the "oval_footer" variable must be appended to the body of the check to form valid XML
+oval_footer = '</oval_definitions>'
+
+# the namespace we are working in
+oval_namespace = "{http://oval.mitre.org/XMLSchema/oval-definitions-5}"
+
+xccdf_header = '<?xml version="1.0" encoding="UTF-8"?><xccdf>'
+xccdf_footer = '</xccdf>'
+
+# print a blank line to keep things pretty
+print
+
+#
+##################################################################################
+#
+# The directory src/input/checks contains all of the OVAL checks that are implemented within scap-security-guide.
+# In order to build the XCCDF and OVAL properly, several helper scripts expect that the ID assigned to each OVAL
+# check matches its file name.
+#
+# Assume we have an OVAL check called "mount_option_var_tmp_bind.xml" in the src/input/checks directory. The ID
+# of this check *must* be "mount_option_var_tmp_bind" like so:
+#
+# <def-group>
+#   <definition class="compliance" id="mount_option_var_tmp_bind" version="1">
+#     <metadata>
+#
+# This piece of Python will step through each OVAL check in src/input/checks and verify that the ID assigned to the
+# check matches the name of the file. If a mismatch is found, then a warning is printed to stdout.
+#
+##################################################################################
+#
+
+# make the checks directory our working directory
+os.chdir("../input/checks")
+
+# generate a list of all the OVAL checks
+oval_file_list = []
+for root, dirs, files in os.walk("."):
+    if (root == "."):
+        for name in files:
+            if (name.find(".xml") > -1):
+                oval_file_list.append(root + "/" + name)
+
+# step through each file and open it for reading
+for oval_check in oval_file_list:
+    with open(oval_check, 'r') as infile:
+        # form valid XML so that we can parse it
+        oval_xml_contents = oval_header + infile.read() + oval_footer
+        # parse the XML at this point
+        tree = ET.fromstring(oval_xml_contents)
+        # extract the ID of the check
+        definition_node = tree.findall("./" + oval_namespace + "def-group/*")
+        oval_id = "Error"
+        for node in definition_node:
+            if (node.tag == (oval_namespace + "definition")):
+                oval_id = node.get("id")
+        # at this point the variable oval_id should contain the id of the oval check
+        # now we make sure that the name of the file matches the oval id
+        if (oval_check.find(oval_id + ".xml") < 0):
+            print "  WARNING: OVAL check " + oval_check.replace("./", "src/input/checks/") + " has ID \"" + oval_id + "\""
+            print "           the ID should match the file name without the .xml\n"
+
+#
+##################################################################################
+#
+# This section of Python looks through all of the XCCDF files for references to OVAL checks. If a reference is found we
+# want to make sure that a corresponding OVAL check exists in the src/input/checks directory. We print a relatively polite
+# warning if a XCCDF Rule references an OVAL check that does not exist.
+#
+# Again, lots of regex-fu here. In their raw state the XCCDF files are not valid XML. The next iteration of this file may
+# add all of the XML complexity so that we can perform these validations by parsing XML.  That would form a more complete
+# solution.
+#
+##################################################################################
+#
+
+# make the input directory our working directory
+# remember that we are currently at "../input/checks"
+os.chdir("..")
+
+# exclude these directories in the search for XCCDF files
+exclude_subdirs = ['.', './checks', './checks/templates', './checks/templates/output']
+
+# generate a list of all the XML files that are used to generate the XCCDF
+xccdf_xml_files = []
+for root, dirs, files in os.walk("."):
+    if not (root in exclude_subdirs):
+        for name in files:
+            if (name.find(".xml") > -1):
+                xccdf_xml_files.append(root + "/" + name)
+
+# step through each file and open it for reading
+for xccdf_file in xccdf_xml_files:
+    with open(xccdf_file, 'r') as infile:
+        # form valid XML so that we can parse it
+        xccdf_xml_contents = xccdf_header + infile.read() + xccdf_footer
+        # parse the XML at this point
+        try:
+            tree = ET.fromstring(xccdf_xml_contents)
+        except ET.XMLSyntaxError:
+            print "  XML syntax error in file: %s" % xccdf_file.replace("./", "src/input/")
+        # extract all of the rules that are defined within the XCCDF
+        xccdf_rules = tree.findall(".//Rule")
+        for xccdf_rule in xccdf_rules:
+            # extract any reference to an OVAL check
+            oval_check_refs = xccdf_rule.findall(".//oval")
+            # make sure the OVAL references point to an actual check
+            for oval_ref in oval_check_refs:
+                # build a path to look for
+                if (oval_ref.get("id")):
+                    file_name = "./checks/" + oval_ref.get("id") + ".xml"
+                    if (not os.access(file_name, os.F_OK)):
+                        print "  WARNING: XCCDF Rule \"" + xccdf_rule.get("id") + "\" references OVAL check \"" + oval_ref.get("id") + "\" which does not exist"
+                        print "           problem occurs in file: " + xccdf_file.replace("./", "src/input/") + "\n"
+                else:
+                    print "  WARNING: XCCDF Rule \"" + xccdf_rule.get("id") + "\" in file " + xccdf_file.replace("./", "src/input/") + " contains a null OVAL check\n"
+
+# we are done
+exit()
diff --git a/FEDORA/utils/verify-references.py b/FEDORA/utils/verify-references.py
new file mode 100755
index 0000000..bac9723
--- /dev/null
+++ b/FEDORA/utils/verify-references.py
@@ -0,0 +1,185 @@
+#!/usr/bin/python
+
+import sys, os, optparse
+import lxml.etree as ET
+
+#
+# This script can verify consistency of references (linkage) between XCCDF and
+# OVAL, and also search based on other criteria such as existence of policy
+# references in XCCDF.
+#
+# It must be run from the same directory as the XCCDF and OVAL content
+# it references.
+
+xccdf_ns = "http://checklists.nist.gov/xccdf/1.1" 
+oval_ns = "http://oval.mitre.org/XMLSchema/oval-definitions-5"
+
+# we use these strings to look for references within the XCCDF rules
+nist_ref_href = "http://csrc.nist.gov/publications/nistpubs/800-53-Rev3/sp800-53-rev3-final.pdf"
+disa_ref_href = "http://iase.disa.mil/cci/index.html"
+
+def parse_options():
+	usage = "usage: %prog [options] xccdf_file"
+	parser = optparse.OptionParser(usage=usage, version="%prog ")
+	# only some options are on by default
+	parser.add_option("-p", "--profile", default=False, action="store", dest="profile_name",
+					  help="act on Rules from this XCCDF Profile only")
+	parser.add_option("--rules-with-invalid-checks", default=False, action="store_true", dest="rules_with_invalid_checks",
+					  help="print XCCDF Rules that reference an invalid/nonexistent check")
+	parser.add_option("--rules-without-checks", default=False, action="store_true", dest="rules_without_checks",
+					  help="print XCCDF Rules that do not include a check")
+	parser.add_option("--rules-without-severity", default=False, action="store_true", dest="rules_without_severity",
+					  help="print XCCDF Rules that do not include a severity")
+	parser.add_option("--rules-without-nistrefs", default=False, action="store_true", dest="rules_without_nistrefs",
+					  help="print XCCDF Rules which do not include any NIST 800-53 references")
+	parser.add_option("--rules-without-disarefs", default=False, action="store_true", dest="rules_without_disarefs",
+					  help="print XCCDF Rules which do not include any DISA CCI references")
+	parser.add_option("--rules-with-nistrefs-outside-profile", default=False, action="store_true", dest="nistrefs_not_in_profile",
+					  help="print XCCDF Rules which have a NIST reference, but are not part of the Profile specified")
+	parser.add_option("--rules-with-disarefs-outside-profile", default=False, action="store_true", dest="disarefs_not_in_profile",
+					  help="print XCCDF Rules which have a DISA CCI reference, but are not part of the Profile specified")
+	parser.add_option("--ovaldefs-unused", default=False, action="store_true", dest="ovaldefs_unused",
+					  help="print OVAL definitions which are not used by any XCCDF Rule")
+	parser.add_option("--all-checks", default=False, action="store_true", dest="all_checks",
+					  help="perform all checks on the given XCCDF file")
+	(options, args) = parser.parse_args()
+	if len(args) < 1:
+		parser.print_help()
+		sys.exit(1)
+	return (options, args)
+
+def get_ovalfiles(checks):
+	# iterate over all checks, grab the OVAL files referenced within
+	ovalfiles = set()
+	for check in checks:
+		if check.get("system") == oval_ns:
+			checkcontentref = check.find("./{%s}check-content-ref" % xccdf_ns)
+			ovalfiles.add(checkcontentref.get("href"))
+		elif check.get("system") != "ocil-transitional":
+			print "Non-OVAL checking system found: " + check.get("system")
+	return ovalfiles
+
+
+def get_profileruleids(xccdftree, profile_name):
+	ruleids = []
+
+	while profile_name:
+		profile = xccdftree.find(".//{%s}Profile[@id='%s']" % (xccdf_ns, profile_name))
+		if profile is None:
+			sys.exit("Specified XCCDF Profile %s was not found.")
+		for select in profile.findall(".//{%s}select" % xccdf_ns):
+			ruleids.append(select.get("idref"))
+		profile_name = profile.get("extends")
+
+	return ruleids
+
+def main():
+	(options, args) = parse_options()
+	xccdffilename = args[0]
+
+	# extract all of the rules within the xccdf
+	xccdftree = ET.parse(xccdffilename)
+	rules = xccdftree.findall(".//{%s}Rule" % xccdf_ns)
+
+	# if a profile was specified, get rid of any Rules that aren't in it
+	if options.profile_name:
+		profile_ruleids = get_profileruleids(xccdftree, options.profile_name)
+		prunedrules = rules[:]
+		for rule in rules:
+			if rule.get("id") not in profile_ruleids:
+				prunedrules.remove(rule)	
+		rules = prunedrules
+
+	# step over xccdf file, and find referenced oval files
+	checks = xccdftree.findall(".//{%s}check" % xccdf_ns)
+	ovalfiles = get_ovalfiles(checks)
+
+	# this script only supports the inclusion of one OVAL file
+	if len(ovalfiles) > 1:
+		sys.exit("Referencing more than one OVAL file is not yet supported by this script.")
+
+	# find important elements within the XCCDF and the OVAL
+	ovalfile = ovalfiles.pop()
+	ovaltree = ET.parse(ovalfile) 
+	# collect all compliance checks (not inventory checks, which are needed by CPE)
+	ovaldefs = ovaltree.findall(".//{%s}definition[@class='compliance']" % oval_ns)
+	ovaldef_ids = [ovaldef.get("id") for ovaldef in ovaldefs] 
+
+	oval_extenddefs = ovaltree.findall(".//{%s}extend_definition" % oval_ns)
+	ovaldef_ids_extended = [oval_extenddef.get("definition_ref") for oval_extenddef in oval_extenddefs]
+	ovaldef_ids_extended = list(set(ovaldef_ids_extended))
+
+	check_content_refs = xccdftree.findall(".//{%s}check-content-ref" % xccdf_ns)
+
+	# now we can actually do the verification work here
+	if options.rules_with_invalid_checks or options.all_checks:
+		for check_content_ref in check_content_refs:
+			refname = check_content_ref.get("name")
+			if refname not in ovaldef_ids:
+				rule = check_content_ref.getparent().getparent() 
+				print "Invalid OVAL definition referenced by XCCDF Rule: " + rule.get("id")
+
+	if options.rules_without_checks or options.all_checks:
+		for rule in rules:
+			check = rule.find("./{%s}check" % xccdf_ns) 
+			if check is None:
+				print "No reference to OVAL definition in XCCDF Rule: " + rule.get("id")
+
+	if options.rules_without_severity or options.all_checks:
+		for rule in rules:
+			if rule.get("severity") is None:
+				print "No severity assigned to XCCDF Rule: " + rule.get("id")
+
+	if options.rules_without_nistrefs or options.rules_without_disarefs or options.all_checks:
+		for rule in rules:
+			# find all references in the current rule
+			refs = rule.findall(".//{%s}reference" % xccdf_ns)
+			if refs is None:
+				print "No reference assigned to XCCDF Rule: " + rule.get("id")
+			else:
+				# loop through the Rule's references and put their hrefs in a list
+				ref_href_list = [ref.get("href") for ref in refs]
+				# print warning if rule does not have a NIST reference
+				if (not nist_ref_href in ref_href_list) and options.rules_without_nistrefs:
+					print "No valid NIST reference in XCCDF Rule: " + rule.get("id")
+				# print warning if rule does not have a DISA reference
+				if (not disa_ref_href in ref_href_list) and options.rules_without_disarefs:
+					print "No valid DISA CCI reference in XCCDF Rule: " + rule.get("id")
+
+	if options.disarefs_not_in_profile or options.nistrefs_not_in_profile:
+		if options.profile_name is None:
+			sys.exit("The options for finding Rules with a reference, but which are not in a Profile, requires specifying a Profile.")
+		allrules = xccdftree.findall(".//{%s}Rule" % xccdf_ns)
+		for rule in allrules:
+			# find all references in the current rule
+			refs = rule.findall(".//{%s}reference" % xccdf_ns)
+			ref_href_list = [ref.get("href") for ref in refs]
+			# print warning if Rule is outside Profile and has a NIST reference
+			if options.nistrefs_not_in_profile:
+				if (nist_ref_href in ref_href_list) and (rule.get("id") not in profile_ruleids):
+					print "XCCDF Rule found with NIST reference outside Profile %s: " % options.profile_name + rule.get("id")
+			# print warning if Rule is outside Profile and has a DISA reference
+			if options.disarefs_not_in_profile:
+				if (disa_ref_href in ref_href_list) and (rule.get("id") not in profile_ruleids):
+					print "XCCDF Rule found with DISA CCI reference outside Profile %s: " % options.profile_name + rule.get("id")
+
+	if options.ovaldefs_unused or options.all_checks:
+		# create a list of all of the OVAL compliance check ids that are defined in the oval file
+		oval_checks_list = [ovaldef.get("id") for ovaldef in ovaldefs]
+		# now loop through the xccdf rules; if a rule references an oval check we remove the oval check from our list
+		for check_content in check_content_refs:
+			# remove from the list
+			if (check_content.get("name") in oval_checks_list):
+				oval_checks_list.remove(check_content.get("name"))
+		# the list should now contain the OVAL checks that are not referenced by any XCCDF rule
+		oval_checks_list.sort()
+		for oval_id in oval_checks_list:
+			# don't print out the OVAL defs that are extended by others, as they're not unused
+			if oval_id not in ovaldef_ids_extended:
+				print "OVAL Check is not referenced by XCCDF: %s" % oval_id
+
+	sys.exit(0)
+
+if __name__ == "__main__":
+	main()
+
-- 
1.7.11.7

