From 0a473397a8f9b7ddf14b45ea4f84160d4c292111 Mon Sep 17 00:00:00 2001 From: Jan Lieskovsky <[email protected]> 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 <[email protected]> --- 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
_______________________________________________ scap-security-guide mailing list [email protected] https://lists.fedorahosted.org/mailman/listinfo/scap-security-guide
