Passed
Pull Request — master (#3193)
by Alexander
02:07
created

ssg.oval.append()   A

Complexity

Conditions 3

Size

Total Lines 11
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 8.6667

Importance

Changes 0
Metric Value
cc 3
eloc 10
nop 2
dl 0
loc 11
ccs 1
cts 7
cp 0.1429
crap 8.6667
rs 9.9
c 0
b 0
f 0
1 1
from __future__ import absolute_import
0 ignored issues
show
Coding Style introduced by
This module should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
2 1
from __future__ import print_function
3
4 1
import sys
5 1
import os
6 1
import re
7 1
import argparse
8 1
import tempfile
9 1
import subprocess
10
11 1
from .constants import oval_footer as footer
12 1
from .constants import oval_namespace as ovalns
13 1
from .rules import get_rule_dir_id, get_rule_dir_ovals, find_rule_dirs
14 1
from .xml import ElementTree as ET
15 1
from .xml import oval_generated_header
16 1
from .id_translate import IDTranslator
17
18 1
SHARED_OVAL = re.sub(r'ssg/.*', 'shared', __file__) + '/checks/oval/'
19 1
LINUX_OS_GUIDE = re.sub(r'ssg/.*', 'linux_os', __file__) + '/guide/'
20
21
22
# globals, to make recursion easier in case we encounter extend_definition
23 1
ET.register_namespace("oval", ovalns)
24 1
definitions = ET.Element("oval:definitions")
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
25 1
tests = ET.Element("oval:tests")
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
26 1
objects = ET.Element("oval:objects")
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
27 1
states = ET.Element("oval:states")
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
28 1
variables = ET.Element("oval:variables")
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
29 1
silent_mode = False
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
30
31
32
# append new child ONLY if it's not a duplicate
33 1
def append(element, newchild):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
34
    global silent_mode
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
35
    newid = newchild.get("id")
36
    existing = element.find(".//*[@id='" + newid + "']")
37
    if existing is not None:
38
        if not silent_mode:
39
            sys.stderr.write("Notification: this ID is used more than once " +
40
                             "and should represent equivalent elements: " +
41
                             newid + "\n")
42
    else:
43
        element.append(newchild)
44
45
46 1
def _add_elements(body, header):
47
    """Add oval elements to the global Elements defined above"""
48
    global definitions
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
49
    global tests
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
50
    global objects
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
51
    global states
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
52
    global variables
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
53
54
    tree = ET.fromstring(header + body + footer)
55
    tree = replace_external_vars(tree)
56
    defname = None
57
    # parse new file(string) as an etree, so we can arrange elements
58
    # appropriately
59
    for childnode in tree.findall("./{%s}def-group/*" % ovalns):
60
        # print "childnode.tag is " + childnode.tag
61
        if childnode.tag is ET.Comment:
62
            continue
63
        if childnode.tag == ("{%s}definition" % ovalns):
64
            append(definitions, childnode)
65
            defname = childnode.get("id")
66
            # extend_definition is a special case:  must include a whole other
67
            # definition
68
            for defchild in childnode.findall(".//{%s}extend_definition"
69
                                              % ovalns):
70
                defid = defchild.get("definition_ref")
71
                extend_ref = find_testfile_or_exit(defid)
72
                includedbody = read_ovaldefgroup_file(extend_ref)
73
                # recursively add the elements in the other file
74
                _add_elements(includedbody, header)
75
        if childnode.tag.endswith("_test"):
76
            append(tests, childnode)
77
        if childnode.tag.endswith("_object"):
78
            append(objects, childnode)
79
        if childnode.tag.endswith("_state"):
80
            append(states, childnode)
81
        if childnode.tag.endswith("_variable"):
82
            append(variables, childnode)
83
    return defname
84
85
86 1
def applicable_platforms(oval_file):
87
    """
88
    Returns the applicable platforms for a given oval file
89
    """
90
91 1
    platforms = []
92 1
    header = oval_generated_header("applicable_platforms", "5.11", "0.0.1")
93 1
    body = read_ovaldefgroup_file(oval_file)
94 1
    oval_tree = ET.fromstring(header + body + footer)
95
96 1
    element_path = "./{%s}def-group/{%s}definition/{%s}metadata/{%s}affected/{%s}platform"
97 1
    element_ns_path = element_path % (ovalns, ovalns, ovalns, ovalns, ovalns)
98 1
    for node in oval_tree.findall(element_ns_path):
99 1
        platforms.append(node.text)
100
101 1
    return platforms
102
103
104 1
def replace_external_vars(tree):
105
    """Replace external_variables with local_variables, so the definition can be
106
       tested independently of an XCCDF file"""
107
108
    # external_variable is a special case: we turn it into a local_variable so
109
    # we can test
110
    for node in tree.findall(".//{%s}external_variable" % ovalns):
111
        print("External_variable with id : " + node.get("id"))
112
        extvar_id = node.get("id")
113
        # for envkey, envval in os.environ.iteritems():
114
        #     print envkey + " = " + envval
115
        # sys.exit()
116
        if extvar_id not in os.environ.keys():
117
            print("External_variable specified, but no value provided via "
118
                  "environment variable", file=sys.stderr)
119
            sys.exit(2)
120
        # replace tag name: external -> local
121
        node.tag = "{%s}local_variable" % ovalns
122
        literal = ET.Element("literal_component")
123
        literal.text = os.environ[extvar_id]
124
        node.append(literal)
125
        # TODO: assignment of external_variable via environment vars, for
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
126
        # testing
127
    return tree
128
129
130 1
def find_testfile_or_exit(testfile):
131
    """Find OVAL files in CWD or shared/oval and calls sys.exit if the file is not found"""
132
    _testfile = find_testfile(testfile)
133
    if _testfile is None:
134
        print("ERROR: %s does not exist! Please specify a valid OVAL file." % testfile,
135
              file=sys.stderr)
136
        sys.exit(1)
137
    else:
138
        return _testfile
139
140
141 1
def find_testfile(oval_id):
142
    """
143
    Find OVAL file by id in CWD, SHARED_OVAL, or LINUX_OS_GUIDE. Understands rule
144
    directories and defaults to returning shared.xml over {{{ product }}}.xml.
145
146
    Returns path to OVAL file or None if not found.
147
    """
148 1
    if oval_id.endswith(".xml"):
149 1
        oval_id, _ = os.path.splitext(oval_id)
150 1
        oval_id = os.path.basename(oval_id)
151
152 1
    candidates = [oval_id, "%s.xml" % oval_id]
153
154 1
    found_file = None
155 1
    for path in ['.', SHARED_OVAL, LINUX_OS_GUIDE]:
156 1
        for root, _, _ in os.walk(path):
157 1
            for candidate in candidates:
158 1
                search_file = os.path.join(root, candidate).strip()
159 1
                if os.path.isfile(search_file):
160
                    found_file = search_file
161
                    break
162
163 1
        for rule_dir in find_rule_dirs(path):
164 1
            rule_id = get_rule_dir_id(rule_dir)
165 1
            if rule_id == oval_id:
166 1
                ovals = get_rule_dir_ovals(rule_dir, product="shared")
167 1
                if ovals:
168 1
                    found_file = ovals[0]
169 1
                    break
170
171 1
    return found_file
172
173
174 1
def read_ovaldefgroup_file(testfile):
175
    """Read oval files"""
176 1
    with open(testfile, 'r') as test_file:
177 1
        body = test_file.read()
178 1
    return body
179
180
181 1
def get_openscap_supported_oval_version():
0 ignored issues
show
Coding Style Naming introduced by
The name get_openscap_supported_oval_version does not conform to the function naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
182
    try:
183
        from openscap import oscap_get_version
184
        if [int(x) for x in str(oscap_get_version()).split(".")] >= [1, 2, 0]:
185
            return "5.11"
186
    except ImportError:
187
        pass
188
189
    return "5.10"
190
191
192 1
def parse_options():
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
193
    usage = "usage: %(prog)s [options] definition_file.xml"
194
    parser = argparse.ArgumentParser(usage=usage)
195
    # only some options are on by default
196
197
    oscap_oval_version = get_openscap_supported_oval_version()
198
199
    parser.add_argument("--oval_version",
200
                        default=oscap_oval_version,
201
                        dest="oval_version", action="store",
202
                        help="OVAL version to use. Example: 5.11, 5.10, ... "
203
                             "If not supplied the highest version supported by "
204
                             "openscap will be used: %s" % (oscap_oval_version))
205
    parser.add_argument("-q", "--quiet", "--silent", default=False,
206
                        action="store_true", dest="silent_mode",
207
                        help="Don't show any output when testing OVAL files")
208
    parser.add_argument("xmlfile", metavar="XMLFILE", help="OVAL XML file")
209
    args = parser.parse_args()
210
211
    return args
212
213
214 1
def main():
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
215
    global definitions
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
216
    global tests
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
217
    global objects
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
218
    global states
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
219
    global variables
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
220
    global silent_mode
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
221
222
    args = parse_options()
223
    silent_mode = args.silent_mode
224
    oval_version = args.oval_version
225
226
    testfile = args.xmlfile
227
    header = oval_generated_header("testoval.py", oval_version, "0.0.1")
228
    testfile = find_testfile_or_exit(testfile)
229
    body = read_ovaldefgroup_file(testfile)
230
231
    defname = _add_elements(body, header)
232
    if defname is None:
233
        print("Error while evaluating oval: defname not set; missing "
234
              "definitions section?")
235
        sys.exit(1)
236
237
    ovaltree = ET.fromstring(header + footer)
238
239
    # append each major element type, if it has subelements
240
    for element in [definitions, tests, objects, states, variables]:
241
        if list(element):
242
            ovaltree.append(element)
243
244
    # re-map all the element ids from meaningful names to meaningless
245
    # numbers
246
    testtranslator = IDTranslator("scap-security-guide.testing")
247
    ovaltree = testtranslator.translate(ovaltree)
248
    (ovalfile, fname) = tempfile.mkstemp(prefix=defname, suffix=".xml")
249
    os.write(ovalfile, ET.tostring(ovaltree))
250
    os.close(ovalfile)
251
252
    cmd = ['oscap', 'oval', 'eval', '--results', fname + '-results', fname]
253
    if not silent_mode:
254
        print("Evaluating with OVAL tempfile: " + fname)
255
        print("OVAL Schema Version: %s" % oval_version)
256
        print("Writing results to: " + fname + "-results")
257
        print("Running command: %s\n" % " ".join(cmd))
258
259
    oscap_child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
260
    cmd_out = oscap_child.communicate()[0]
261
262
    if isinstance(cmd_out, bytes):
263
        cmd_out = cmd_out.decode('utf-8')
264
265
    if not silent_mode:
266
        print(cmd_out, file=sys.stderr)
267
268
    if oscap_child.returncode != 0:
269
        if not silent_mode:
270
            print("Error launching 'oscap' command: return code %d" % oscap_child.returncode)
271
        sys.exit(2)
272
273
    if 'false' in cmd_out or 'error' in cmd_out:
274
        # at least one from the evaluated OVAL definitions evaluated to
275
        # 'false' result, exit with '1' to indicate OVAL scan FAIL result
276
        sys.exit(1)
277
278
    # perhaps delete tempfile?
279
    definitions = ET.Element("oval:definitions")
280
    tests = ET.Element("oval:tests")
281
    objects = ET.Element("oval:objects")
282
    states = ET.Element("oval:states")
283
    variables = ET.Element("oval:variables")
284
285
    # 'false' keyword wasn't found in oscap's command output
286
    # exit with '0' to indicate OVAL scan TRUE result
287
    sys.exit(0)
288