Passed
Pull Request — master (#3663)
by Matěj
02:24
created

stable_profile_ids.check_build_dir()   B

Complexity

Conditions 7

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nop 1
dl 0
loc 20
rs 8
c 0
b 0
f 0
1
#!/usr/bin/env python3
2
3
from __future__ import print_function
4
5
import os
6
import ssg.xml
7
import ssg.xccdf
8
import ssg.build_guides
9
import argparse
10
import glob
11
from collections import defaultdict
12
13
14
# Maps shortened benchmark IDs to list of profiles that need to be contained
15
# in said benchmarks. Delete the predictable prefix of each benchmark ID to get
16
# the shortened ID. For example xccdf_org.ssgproject.content_benchmark_RHEL-7
17
# becomes just RHEL-7. Apply the same to profile IDs:
18
# xccdf_org.ssgproject.content_profile_ospp42 becomes ospp42
19
20
STABLE_PROFILE_IDS = {
21
    "FEDORA": ["standard", "ospp", "pci-dss"],
22
    "RHEL-6": ["C2S", "CS2", "CSCF-RHEL6-MLS", "fisma-medium-rhel6-server",
23
               "pci-dss", "rht-ccp", "stig-rhel6-disa", "usgcb-rhel6-server"],
24
    "RHEL-7": ["C2S", "cjis", "hipaa", "nist-800-171-cui", "rht-ccp",
25
               "ospp", "ospp42", "pci-dss", "stig-rhel7-disa"],
26
    "RHEL-8": ["ospp", "pci-dss"],
27
}
28
29
30
BENCHMARK_TO_FILE_STEM = {
31
    "FEDORA": "fedora",
32
    "RHEL-6": "rhel6",
33
    "RHEL-7": "rhel7",
34
    "RHEL-8": "rhel8",
35
}
36
37
38
BENCHMARK_ID_PREFIX = "xccdf_org.ssgproject.content_benchmark_"
39
PROFILE_ID_PREFIX = "xccdf_org.ssgproject.content_profile_"
40
41
42
def parse_args():
43
    p = argparse.ArgumentParser()
44
45
    p.add_argument("build_dir", type=str,
46
                   help="Directory with the datastreams that will be checked "
47
                        "for stable profile IDs. All files matching "
48
                        "$BUILD_DIR/ssg-*-ds.xml checked.")
49
50
    return p.parse_args()
51
52
53
def gather_profiles_from_datastream(path, build_dir, profiles_per_benchmark):
54
    input_tree = ssg.xml.ElementTree.parse(path)
55
    benchmarks = ssg.xccdf.get_benchmark_id_title_map(input_tree)
56
    if len(benchmarks) == 0:
57
        raise RuntimeError(
58
            "Expected input file '%s' to contain at least 1 xccdf:Benchmark. "
59
            "No Benchmarks were found!" % (path)
60
        )
61
62
    benchmark_profile_pairs = ssg.build_guides.get_benchmark_profile_pairs(
63
        input_tree, benchmarks)
64
65
    for bench_id, profile_id, title in benchmark_profile_pairs:
66
        bench_short_id = bench_id[len(BENCHMARK_ID_PREFIX):]
67
        if respective_datastream_absent(bench_short_id, build_dir):
68
            continue
69
70
        if not bench_id.startswith(BENCHMARK_ID_PREFIX):
71
            raise RuntimeError("Expected benchmark ID '%s' from '%s' to be "
72
                               "prefixed with '%s'."
73
                               % (bench_id, path, BENCHMARK_ID_PREFIX))
74
75
        if profile_id == "":
76
            # default profile can be skipped, we know for sure that
77
            # it will be present in all benchmarks
78
            continue
79
80
        if not profile_id.startswith(PROFILE_ID_PREFIX):
81
            raise RuntimeError("Expected profile ID '%s' from '%s' to be "
82
                               "prefixed with '%s'."
83
                               % (profile_id, path, PROFILE_ID_PREFIX))
84
85
        profile_id = profile_id[len(PROFILE_ID_PREFIX):]
86
87
        profiles_per_benchmark[bench_short_id].append(profile_id)
88
89
90
def respective_datastream_absent(bench_id, build_dir):
91
    if bench_id not in BENCHMARK_TO_FILE_STEM:
92
        return True
93
94
    datastream_filename = "ssg-{stem}-ds.xml".format(stem=BENCHMARK_TO_FILE_STEM[bench_id])
95
    datastream_path = os.path.join(build_dir, datastream_filename)
96
    if not os.path.isfile(datastream_path):
97
        return True
98
    else:
99
        return False
100
101
102
def check_build_dir(build_dir):
103
    profiles_per_benchmark = defaultdict(list)
104
    for path in glob.glob(os.path.join(build_dir, "ssg-*-ds.xml")):
105
        gather_profiles_from_datastream(path, build_dir, profiles_per_benchmark)
106
107
    for bench_short_id in STABLE_PROFILE_IDS.keys():
108
        if respective_datastream_absent(bench_short_id, build_dir):
109
            continue
110
111
        if bench_short_id not in profiles_per_benchmark:
112
            raise RuntimeError("Expected benchmark ID '%s' has to be "
113
                               "prefixed with '%s'."
114
                               % (bench_short_id, BENCHMARK_ID_PREFIX))
115
116
        for profile_id in STABLE_PROFILE_IDS[bench_short_id]:
117
            if profile_id not in profiles_per_benchmark[bench_short_id]:
118
                raise RuntimeError("Profile '%s' is required to be in the "
119
                                   "'%s' benchmark. It is a stable profile "
120
                                   "that can't be renamed or removed!"
121
                                   % (profile_id, bench_short_id))
122
123
124
def main():
125
    args = parse_args()
126
127
    check_build_dir(args.build_dir)
128
129
130
if __name__ == "__main__":
131
    main()
132