File: build_derivatives.py

package info (click to toggle)
scap-security-guide 0.1.76-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 110,644 kB
  • sloc: xml: 241,883; sh: 73,777; python: 32,527; makefile: 27
file content (203 lines) | stat: -rw-r--r-- 7,283 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""
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)