1 | #!/usr/bin/python3 |
||
2 | from __future__ import print_function |
||
3 | |||
4 | import argparse |
||
5 | import subprocess |
||
6 | import os |
||
7 | import xml.etree.ElementTree as ET |
||
8 | import sys |
||
9 | |||
10 | scapval_results_ns = "http://csrc.nist.gov/ns/decima/results/1.0" |
||
11 | oval_unix_ns = "http://oval.mitre.org/XMLSchema/oval-definitions-5#unix" |
||
12 | xccdf_ns = "http://checklists.nist.gov/xccdf/1.2" |
||
13 | |||
14 | XML_SCHEMA_REQUIREMENT = "SRC-329" |
||
15 | |||
16 | K8S_PRODUCTS = set(( |
||
17 | "ocp4", |
||
18 | "eks", |
||
19 | )) |
||
20 | |||
21 | |||
22 | def parse_args(): |
||
23 | parser = argparse.ArgumentParser( |
||
24 | description="Runs SCAP Validation of our data streams using SCAP" |
||
25 | "Validation Tool (SCAPVal)") |
||
26 | parser.add_argument( |
||
27 | "--scap-version", |
||
28 | help="SCAP Version (Only 1.2 and 1.3 supported)", |
||
29 | choices=["1.2", "1.3"], required=True) |
||
30 | parser.add_argument( |
||
31 | "--scapval-path", |
||
32 | help="Full path to the SCAPVal JAR archive", required=True) |
||
33 | input_group = parser.add_mutually_exclusive_group(required=True) |
||
34 | input_group.add_argument( |
||
35 | "--build-dir", |
||
36 | help="Full path to the ComplianceAsCode build directory") |
||
37 | input_group.add_argument("--datastream", help="Full path to the ComplianceAsCode data stream") |
||
38 | return parser.parse_args() |
||
39 | |||
40 | |||
41 | def ds_is_k8s_related(result_path): |
||
42 | for prodname in K8S_PRODUCTS: |
||
43 | ds_basename_stem = "ssg-{prodname}-ds".format(prodname=prodname) |
||
44 | if ds_basename_stem in result_path: |
||
45 | return True |
||
46 | return False |
||
47 | |||
48 | |||
49 | def print_requirement_feedback(req_id, message): |
||
50 | print(" %s: %s" % (req_id, message)) |
||
51 | |||
52 | |||
53 | def process_results(result_path): |
||
54 | ret_val = True |
||
55 | tree = ET.parse(result_path) |
||
56 | root = tree.getroot() |
||
57 | results = root.find("./{%s}results" % scapval_results_ns) |
||
58 | for base_req in results.findall( |
||
59 | "./{%s}base-requirement" % scapval_results_ns): |
||
60 | scapval_requirement_id = base_req.get("id") |
||
61 | status = base_req.find("./{%s}status" % scapval_results_ns).text |
||
62 | if status == "FAIL": |
||
63 | if ds_is_k8s_related(result_path) and scapval_requirement_id == XML_SCHEMA_REQUIREMENT: |
||
64 | warning = "WARNING (Contains non-standardized yamlfilecontent_test)" |
||
65 | print_requirement_feedback(scapval_requirement_id, warning) |
||
66 | else: |
||
67 | print_requirement_feedback(scapval_requirement_id, status) |
||
68 | ret_val = False |
||
69 | if status == "PASS": |
||
70 | if ds_is_k8s_related(result_path) and scapval_requirement_id == XML_SCHEMA_REQUIREMENT: |
||
71 | msg = ("FAIL (yamlfilecontent_test is probably standardized by now." |
||
72 | "You should update the waiver.)") |
||
73 | print_requirement_feedback(scapval_requirement_id, msg) |
||
74 | return ret_val |
||
75 | |||
76 | |||
77 | def test_datastream(datastream_path, scapval_path, scap_version): |
||
78 | result_path = datastream_path + ".result.xml" |
||
79 | report_path = datastream_path + ".report.html" |
||
80 | scapval_command = [ |
||
81 | "java", |
||
82 | "-Xmx1024m", |
||
83 | "-Djava.protocol.handler.pkgs=sun.net.www.protocol", |
||
84 | "-jar", scapval_path, |
||
85 | "-scapversion", scap_version, |
||
86 | "-file", datastream_path, |
||
87 | "-valresultfile", result_path, |
||
88 | "-valreportfile", report_path |
||
89 | ] |
||
90 | try: |
||
91 | subprocess.check_output(scapval_command, stderr=subprocess.STDOUT) |
||
92 | except subprocess.CalledProcessError as e: |
||
93 | scapval_output = e.output.decode("utf-8") |
||
94 | # Workaround: SCAPVal 1.3.2 can't generate HTML report because |
||
95 | # it throws a NullPointerException, but we don't need the HTML |
||
96 | # report for this test, so we can ignore this error. |
||
97 | # TODO: Remove this when this is fixed in SCAPVal |
||
98 | last_line = scapval_output.splitlines()[-1] |
||
99 | if not last_line.endswith( |
||
100 | "ERROR SCAPVal has encountered a problem and cannot continue " |
||
101 | "with this validation. - java.lang.NullPointerException: " |
||
102 | "XSLTemplateExtension cannot be null"): |
||
103 | sys.stderr.write("Command '{0}' returned {1}:\n{2}\n".format( |
||
104 | " ".join(e.cmd), e.returncode, scapval_output)) |
||
105 | sys.exit(1) |
||
106 | return process_results(result_path) |
||
107 | |||
108 | |||
109 | def main(): |
||
110 | overall_result = True |
||
111 | args = parse_args() |
||
112 | if args.datastream is not None: |
||
113 | build_dir = os.path.dirname(args.datastream) |
||
114 | files = [os.path.basename(args.datastream), ] |
||
115 | else: |
||
116 | build_dir = args.build_dir |
||
117 | files = os.listdir(build_dir) |
||
118 | print("Build dir: %s" % build_dir) |
||
119 | if args.scap_version == "1.2": |
||
120 | ds_suffix = "-ds-1.2.xml" |
||
121 | elif args.scap_version == "1.3": |
||
122 | ds_suffix = "-ds.xml" |
||
123 | |||
124 | for filename in files: |
||
125 | if filename.endswith(ds_suffix): |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
126 | print("Testing %s ..." % filename) |
||
127 | datastream_path = os.path.join(build_dir, filename) |
||
128 | datastream_result = test_datastream( |
||
129 | datastream_path, args.scapval_path, args.scap_version) |
||
130 | if datastream_result: |
||
131 | print("%s: PASS" % filename) |
||
132 | else: |
||
133 | print("%s: FAIL" % filename) |
||
134 | overall_result = False |
||
135 | if overall_result: |
||
136 | sys.exit(0) |
||
137 | else: |
||
138 | sys.exit(1) |
||
139 | |||
140 | |||
141 | if __name__ == "__main__": |
||
142 | main() |
||
143 |