ssg/build_derivatives.py
"""
Common functions for enabling derivative products
"""
from __future__ import absolute_import
from __future__ import print_function
import re
from .xml import ElementTree
from .constants import (
standard_profiles,
OSCAP_VENDOR,
cpe_dictionary_namespace,
oval_namespace,
datastream_namespace,
)
from .build_cpe import ProductCPEs, get_linked_cpe_oval_document
from .products import load_product_yaml
def add_cpes(elem, namespace, mapping):
"""
Adds derivative CPEs next to RHEL ones, checks XCCDF elements of given
namespace.
"""
affected = False
for child in list(elem):
affected = affected or add_cpes(child, namespace, mapping)
# precompute this so that we can affect the tree while iterating
children = list(elem.findall(".//{%s}platform" % (namespace)))
for child in children:
idref = child.get("idref")
if idref in mapping:
new_platform = ElementTree.Element("{%s}platform" % (namespace))
new_platform.set("idref", mapping[idref])
# this is done for the newline and indentation
new_platform.tail = child.tail
index = list(elem).index(child)
# insert it right after the respective RHEL CPE
elem.insert(index + 1, new_platform)
affected = True
return affected
def get_cpe_item(product_yaml, cpe_ref, cpe_items_dir):
product_cpes = ProductCPEs()
product_cpes.load_cpes_from_directory_tree(cpe_items_dir, product_yaml)
return product_cpes.get_cpe(cpe_ref)
def add_cpe_item_to_dictionary(
tree_root, product_yaml_path, cpe_ref, id_name, cpe_items_dir
):
cpe_list = tree_root.find(".//{%s}cpe-list" % cpe_dictionary_namespace)
if cpe_list is not None:
product_yaml = load_product_yaml(product_yaml_path)
cpe_item = get_cpe_item(product_yaml, cpe_ref, cpe_items_dir)
cpe_item.content_id = id_name
cpe_item.set_cpe_oval_def_id()
cpe_list.append(
cpe_item.to_xml_element("ssg-%s-cpe-oval.xml" % product_yaml.get("product"))
)
return cpe_item.cpe_oval_short_def_id
return None
def add_element_to(oval_root, tag_name, component_element):
xml_el = oval_root.find(".//{%s}%s" % (oval_namespace, tag_name))
if xml_el is None:
xml_el = ElementTree.Element("{%s}%s" % (oval_namespace, tag_name))
oval_root.append(xml_el)
if xml_el.find("%s[@id='%s']" % (component_element.tag, component_element.get("id"))) is None:
xml_el.append(component_element)
def add_oval_components_to_oval_xml(oval_root, tag_name, component_dict):
for component in component_dict.values():
add_element_to(oval_root, tag_name, component.get_xml_element())
def get_cpe_oval_root(root):
for component_el in root.findall("./{%s}component" % datastream_namespace):
if "cpe-oval" in component_el.get("id", ""):
return component_el
return None
def add_oval_definition_to_cpe_oval(root, unlinked_oval_file_path, oval_def_id):
oval_cpe_root = get_cpe_oval_root(root)
if oval_cpe_root is None:
raise Exception("CPE OVAL is missing in base DS!")
oval_document = get_linked_cpe_oval_document(unlinked_oval_file_path)
references_to_keep = oval_document.get_all_references_of_definition(oval_def_id)
oval_document.keep_referenced_components(references_to_keep)
add_oval_components_to_oval_xml(
oval_cpe_root, "definitions", oval_document.definitions
)
add_oval_components_to_oval_xml(oval_cpe_root, "tests", oval_document.tests)
add_oval_components_to_oval_xml(oval_cpe_root, "objects", oval_document.objects)
add_oval_components_to_oval_xml(oval_cpe_root, "states", oval_document.states)
add_oval_components_to_oval_xml(oval_cpe_root, "variables", oval_document.variables)
def add_notice(benchmark, namespace, notice, warning):
"""
Adds derivative notice as the first notice to given benchmark.
"""
index = -1
prev_element = None
existing_notices = list(benchmark.findall("./{%s}notice" % (namespace)))
if existing_notices:
prev_element = existing_notices[0]
# insert before the first notice
index = list(benchmark).index(prev_element)
else:
existing_descriptions = list(
benchmark.findall("./{%s}description" % (namespace))
)
prev_element = existing_descriptions[-1]
# insert after the last description
index = list(benchmark).index(prev_element) + 1
if index == -1:
raise RuntimeError(
"Can't find existing notices or description in benchmark '%s'." %
(benchmark)
)
elem = ElementTree.Element("{%s}notice" % (namespace))
elem.set("id", warning)
elem.append(notice)
# this is done for the newline and indentation
elem.tail = prev_element.tail
benchmark.insert(index, elem)
return True
def remove_idents(tree_root, namespace, prod="RHEL"):
"""
Remove product identifiers from rules in XML tree
"""
ident_exp = '.*' + prod + '-*'
ref_exp = prod + '-*'
for rule in tree_root.findall(".//{%s}Rule" % (namespace)):
for ident in rule.findall(".//{%s}ident" % (namespace)):
if ident is not None:
if (re.search(r'CCE-*', ident.text) or
re.search(ident_exp, ident.text)):
rule.remove(ident)
for ref in rule.findall(".//{%s}reference" % (namespace)):
if ref.text is not None:
if re.search(ref_exp, ref.text):
rule.remove(ref)
for fix in rule.findall(".//{%s}fix" % (namespace)):
sub_elems = fix.findall(".//{%s}sub" % (namespace))
for sub_elem in sub_elems:
sub_elem.tail = re.sub(r"[\s]+- CCE-.*", "", sub_elem.tail)
sub_elem.tail = re.sub(r"CCE-[0-9]*-[0-9]*", "", sub_elem.tail)
if fix.text is not None:
fix.text = re.sub(r"[\s]+- CCE-.*", "", fix.text)
fix.text = re.sub(r"CCE-[0-9]*-[0-9]*", "", fix.text)
def remove_cce_reference(tree_root, namespace):
"""
Remove CCE identifiers from OVAL checks in XML tree
"""
for definition in tree_root.findall(".//{%s}definition" % (namespace)):
for metadata in definition.findall(".//{%s}metadata" % (namespace)):
for ref in metadata.findall(".//{%s}reference" % (namespace)):
if (re.search(r'CCE-*', ref.get("ref_id"))):
metadata.remove(ref)
def profile_handling(tree_root, namespace):
ns_profiles = []
for i in standard_profiles:
ns_profiles.append("xccdf_%s.content_profile_%s" % (OSCAP_VENDOR, i))
all_profiles = standard_profiles + ns_profiles
for profile in tree_root.findall(".//{%s}Profile" % (namespace)):
if profile.get("id") not in all_profiles:
tree_root.remove(profile)
def replace_platform(tree_root, namespace, product):
for oval in tree_root.findall(".//{%s}oval_definitions" % (namespace)):
for platform in oval.findall(".//{%s}platform" % (namespace)):
platform.text = (platform.text).replace("Red Hat Enterprise Linux", product)