File: oval.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 (173 lines) | stat: -rw-r--r-- 7,200 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
"""
Common functions for processing OVAL in SSG
"""

from __future__ import absolute_import
from __future__ import print_function

import sys
import os

from .constants import oval_footer as footer
from .constants import oval_namespace as ovalns
from .xml import ElementTree as ET
from .xml import oval_generated_header

from .jinja import process_file_with_macros
from .products import _get_implied_properties


ASSUMED_OVAL_VERSION_STRING = "5.11"
# globals, to make recursion easier in case we encounter extend_definition
try:
    ET.register_namespace("oval", ovalns)
except AttributeError:
    # Legacy Python 2.6 fix, see e.g.
    # https://www.programcreek.com/python/example/57552/xml.etree.ElementTree._namespace_map
    from xml.etree import ElementTree as ET
    ET._namespace_map[ovalns] = "oval"


def applicable_platforms(oval_file, oval_version_string=None):
    """
    Returns the applicable platforms for a given OVAL file.

    This function processes an OVAL file to extract the platforms it applies to. It uses a
    specified OVAL version string or a default version if none is provided. The function
    constructs an XML tree from the OVAL file and extracts platform information from it.

    Args:
        oval_file (str): The path to the OVAL file to be processed.
        oval_version_string (str, optional): The OVAL version string to be used.
                                             If not provided, a default version is used.

    Returns:
        list: A list of platforms that the OVAL file applies to.

    Raises:
        Exception: If there is an error while parsing the OVAL file.
    """
    platforms = []

    if not oval_version_string:
        oval_version_string = ASSUMED_OVAL_VERSION_STRING
    header = oval_generated_header("applicable_platforms", oval_version_string, "0.0.1")

    oval_version_list = [int(num) for num in oval_version_string.split(".")]
    subst_dict = dict(target_oval_version=oval_version_list)

    oval_filename_components = oval_file.split(os.path.sep)
    if len(oval_filename_components) > 3:
        subst_dict["rule_id"] = oval_filename_components[-3]
    else:
        msg = "Unable to get rule ID from OVAL path '{path}'".format(path=oval_file)
        print(msg, file=sys.stderr)

    subst_dict = _get_implied_properties(subst_dict)
    subst_dict['target_oval_version'] = [999, 999.999]

    body = process_file_with_macros(oval_file, subst_dict)

    try:
        oval_tree = ET.fromstring(header + body + footer)
    except Exception as e:
        msg = "Error while loading " + oval_file
        print(msg, file=sys.stderr)
        raise e

    element_path = "./{%s}def-group/{%s}definition/{%s}metadata/{%s}affected/{%s}platform"
    element_ns_path = element_path % (ovalns, ovalns, ovalns, ovalns, ovalns)
    for node in oval_tree.findall(element_ns_path):
        platforms.append(node.text)

    return platforms


def parse_affected(oval_contents):
    """
    Returns the tuple (start_affected, end_affected, platform_indents) for the passed OVAL file contents.

    Args:
    oval_contents (list of str): The contents of the OVAL file, where each element is a line from
                                 the file.

    Returns:
        tuple: A tuple containing:
            - start_affected (int): The line number of the starting <affected> tag.
            - end_affected (int): The line number of the closing </affected> tag.
            - platform_indents (str): The indenting characters before the contents of the
              <affected> element.

    Raises:
        ValueError: If the OVAL file does not contain a single <affected> element, if the start
                    tag is after the end tag, or if the tags contain other elements.
    """
    start_affected = list(filter(lambda x: "<affected" in oval_contents[x],
                                 range(0, len(oval_contents))))
    if len(start_affected) != 1:
        raise ValueError("OVAL file does not contain a single <affected> "
                         "element; counted %d in:\n%s\n\n" %
                         (len(start_affected), "\n".join(oval_contents)))

    start_affected = start_affected[0]

    end_affected = list(filter(lambda x: "</affected" in oval_contents[x],
                               range(0, len(oval_contents))))
    if len(end_affected) != 1:
        raise ValueError("Malformed OVAL file does not contain a single "
                         "closing </affected>; counted %d in:\n%s\n\n" %
                         (len(start_affected), "\n".join(oval_contents)))
    end_affected = end_affected[0]

    if start_affected >= end_affected:
        raise ValueError("Malformed OVAL file: start affected tag begins "
                         "on the same line or after ending affected tag: "
                         "start:%d vs end:%d:\n%s\n\n" %
                         (start_affected, end_affected, oval_contents))

    # Validate that start_affected contains only a starting <affected> tag;
    # otherwise, using this information to update the <platform> subelements
    # would fail.
    start_line = oval_contents[start_affected]
    start_line = start_line.strip()

    if not start_line.startswith('<affected'):
        raise ValueError("Malformed OVAL file: line with starting affected "
                         "tag contains other elements: line:%s\n%s\n\n" %
                         (start_line, oval_contents))
    if '<' in start_line[1:]:
        raise ValueError("Malformed OVAL file: line with starting affected "
                         "tag contains other elements: line:%s\n%s\n\n" %
                         (start_line, oval_contents))

    # Validate that end_affected contains only an ending </affected> tag;
    # otherwise, using this information to update the <platform> subelements
    # would fail.
    end_line = oval_contents[end_affected]
    end_line = end_line.strip()

    if not end_line.startswith('</affected>'):
        raise ValueError("Malformed OVAL file: line with ending affected "
                         "tag contains other elements: line:%s\n%s\n\n" %
                         (end_line, oval_contents))
    if '<' in end_line[1:]:
        raise ValueError("Malformed OVAL file: line with ending affected "
                         "tag contains other elements: line:%s\n%s\n\n" %
                         (end_line, oval_contents))

    indents = ""
    if start_affected+1 == end_affected:
        # Since the affected element is present but empty, the indents should
        # be two more spaces than that of the starting <affected> element.
        start_index = oval_contents[start_affected].index('<')
        indents = oval_contents[start_affected][0:start_index]
        indents += "  "
    else:
        # Otherwise, grab the indents off the next line unmodified, as this is
        # likely a platform element tag. We don't validate here that this is
        # indeed the case, as other parts of the build infrastructure will
        # validate this for us.
        start_index = oval_contents[start_affected+1].index('<')
        indents = oval_contents[start_affected+1][0:start_index]

    return start_affected, end_affected, indents