File: stable_profile_ids.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 (126 lines) | stat: -rwxr-xr-x 4,281 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
#!/usr/bin/python3

from __future__ import print_function

import os
import ssg.xml
import ssg.xccdf
import ssg.build_guides
import argparse
import glob
from collections import defaultdict


# Maps shortened benchmark IDs to list of profiles that need to be contained
# in said benchmarks. Delete the predictable prefix of each benchmark ID to get
# the shortened ID. For example xccdf_org.ssgproject.content_benchmark_RHEL-7
# becomes just RHEL-7. Apply the same to profile IDs:
# xccdf_org.ssgproject.content_profile_ospp42 becomes ospp42

STABLE_PROFILE_IDS = {
    "FEDORA": ["standard", "ospp", "pci-dss"],
    "RHEL-8": ["ospp", "pci-dss"],
}


BENCHMARK_TO_FILE_STEM = {
    "FEDORA": "fedora",
    "RHEL-8": "rhel8",
    "RHEL-9": "rhel9"
}


BENCHMARK_ID_PREFIX = "xccdf_org.ssgproject.content_benchmark_"
PROFILE_ID_PREFIX = "xccdf_org.ssgproject.content_profile_"


def parse_args():
    p = argparse.ArgumentParser()

    p.add_argument("build_dir", type=str,
                   help="Directory with the datastreams that will be checked "
                        "for stable profile IDs. All files matching "
                        "$BUILD_DIR/ssg-*-ds.xml checked.")

    return p.parse_args()


def gather_profiles_from_datastream(path, build_dir, profiles_per_benchmark):
    input_tree = ssg.xml.ElementTree.parse(path)
    benchmarks = ssg.xccdf.get_benchmark_id_title_map(input_tree)
    if len(benchmarks) == 0:
        raise RuntimeError(
            "Expected input file '%s' to contain at least 1 xccdf:Benchmark. "
            "No Benchmarks were found!" % (path)
        )

    benchmark_profile_pairs = ssg.build_guides.get_benchmark_profile_pairs(
        input_tree, benchmarks)

    for bench_id, profile_id, title in benchmark_profile_pairs:
        bench_short_id = bench_id[len(BENCHMARK_ID_PREFIX):]
        if respective_datastream_absent(bench_short_id, build_dir):
            continue

        if not bench_id.startswith(BENCHMARK_ID_PREFIX):
            raise RuntimeError("Expected benchmark ID '%s' from '%s' to be "
                               "prefixed with '%s'."
                               % (bench_id, path, BENCHMARK_ID_PREFIX))

        if not profile_id:
            # default profile can be skipped, we know for sure that
            # it will be present in all benchmarks
            continue

        if not profile_id.startswith(PROFILE_ID_PREFIX):
            raise RuntimeError("Expected profile ID '%s' from '%s' to be "
                               "prefixed with '%s'."
                               % (profile_id, path, PROFILE_ID_PREFIX))

        profile_id = profile_id[len(PROFILE_ID_PREFIX):]

        profiles_per_benchmark[bench_short_id].append(profile_id)


def respective_datastream_absent(bench_id, build_dir):
    if bench_id not in BENCHMARK_TO_FILE_STEM:
        return True

    datastream_filename = "ssg-{stem}-ds.xml".format(stem=BENCHMARK_TO_FILE_STEM[bench_id])
    datastream_path = os.path.join(build_dir, datastream_filename)
    if not os.path.isfile(datastream_path):
        return True
    else:
        return False


def check_build_dir(build_dir):
    profiles_per_benchmark = defaultdict(list)
    for path in glob.glob(os.path.join(build_dir, "ssg-*-ds.xml")):
        gather_profiles_from_datastream(path, build_dir, profiles_per_benchmark)

    for bench_short_id in STABLE_PROFILE_IDS.keys():
        if respective_datastream_absent(bench_short_id, build_dir):
            continue

        if bench_short_id not in profiles_per_benchmark:
            raise RuntimeError("Expected benchmark ID '%s' has to be "
                               "prefixed with '%s'."
                               % (bench_short_id, BENCHMARK_ID_PREFIX))

        for profile_id in STABLE_PROFILE_IDS[bench_short_id]:
            if profile_id not in profiles_per_benchmark[bench_short_id]:
                raise RuntimeError("Profile '%s' is required to be in the "
                                   "'%s' benchmark. It is a stable profile "
                                   "that can't be renamed or removed!"
                                   % (profile_id, bench_short_id))


def main():
    args = parse_args()

    check_build_dir(args.build_dir)


if __name__ == "__main__":
    main()