From 37dda7bd5d512ebf6224c6908e7084458fdd88e6 Mon Sep 17 00:00:00 2001
From: Jan Lieskovsky <[email protected]>
Date: Thu, 12 Sep 2013 17:26:42 +0200
Subject: [PATCH 7/8] Add verify-references.py testing script (required for
 validation of generated XML files), and corresponding
 README file for it.


Signed-off-by: Jan Lieskovsky <[email protected]>
---
 FEDORA/utils/README               |  24 +++++
 FEDORA/utils/verify-references.py | 185 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 209 insertions(+)
 create mode 100644 FEDORA/utils/README
 create mode 100755 FEDORA/utils/verify-references.py

diff --git a/FEDORA/utils/README b/FEDORA/utils/README
new file mode 100644
index 0000000..0949648
--- /dev/null
+++ b/FEDORA/utils/README
@@ -0,0 +1,24 @@
+This file is meant to give a quick overview to some of the
+scripts located in this directory.
+
+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-references.py b/FEDORA/utils/verify-references.py
new file mode 100755
index 0000000..d70d744
--- /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

_______________________________________________
scap-security-guide mailing list
[email protected]
https://lists.fedorahosted.org/mailman/listinfo/scap-security-guide

Reply via email to