|
| 1 | +#!/usr/bin/python3 |
| 2 | + |
| 3 | +import argparse |
| 4 | +import collections |
| 5 | +import lxml.etree as ET |
| 6 | +import os |
| 7 | + |
| 8 | +from ssg.constants import OSCAP_PROFILE, PREFIX_TO_NS |
| 9 | +import ssg.build_guides |
| 10 | + |
| 11 | +BenchmarkData = collections.namedtuple( |
| 12 | + "BenchmarkData", ["title", "profiles", "product"]) |
| 13 | +XSLT_PATH = "/usr/share/openscap/xsl/xccdf-guide.xsl" |
| 14 | + |
| 15 | + |
| 16 | +def get_benchmarks(ds, product): |
| 17 | + benchmarks = {} |
| 18 | + benchmark_xpath = "./ds:component/xccdf-1.2:Benchmark" |
| 19 | + for benchmark_el in ds.xpath(benchmark_xpath, namespaces=PREFIX_TO_NS): |
| 20 | + benchmark_id = benchmark_el.get("id") |
| 21 | + title = benchmark_el.xpath( |
| 22 | + "./xccdf-1.2:title", namespaces=PREFIX_TO_NS)[0].text |
| 23 | + profiles = get_profiles(benchmark_el) |
| 24 | + benchmarks[benchmark_id] = BenchmarkData(title, profiles, product) |
| 25 | + return benchmarks |
| 26 | + |
| 27 | + |
| 28 | +def get_profiles(benchmark_el): |
| 29 | + profiles = {} |
| 30 | + for profile_el in benchmark_el.xpath( |
| 31 | + "./xccdf-1.2:Profile", namespaces=PREFIX_TO_NS): |
| 32 | + profile_id = profile_el.get("id") |
| 33 | + profile_title = profile_el.xpath( |
| 34 | + "./xccdf-1.2:title", namespaces=PREFIX_TO_NS)[0].text |
| 35 | + profiles[profile_id] = profile_title |
| 36 | + return profiles |
| 37 | + |
| 38 | + |
| 39 | +def parse_args(): |
| 40 | + parser = argparse.ArgumentParser() |
| 41 | + parser.add_argument( |
| 42 | + "--data-stream", required=True, |
| 43 | + help="Path to a SCAP source data stream, eg. 'ssg-rhel9-ds.xml'") |
| 44 | + parser.add_argument( |
| 45 | + "--oscap-version", required=True, |
| 46 | + help=f"Version of OpenSCAP that owns {XSLT_PATH}, eg. 1.3.8") |
| 47 | + parser.add_argument( |
| 48 | + "--product", required=True, |
| 49 | + help="Product ID, eg. rhel9") |
| 50 | + parser.add_argument( |
| 51 | + "--output-dir", required=True, |
| 52 | + help="Path to the directory where to generate the output files" |
| 53 | + ", eg. 'build/guides'") |
| 54 | + args = parser.parse_args() |
| 55 | + return args |
| 56 | + |
| 57 | + |
| 58 | +def make_params(oscap_version, benchmark_id, profile_id): |
| 59 | + params = { |
| 60 | + "oscap-version": ET.XSLT.strparam(oscap_version), |
| 61 | + "benchmark_id": ET.XSLT.strparam(benchmark_id), |
| 62 | + "profile_id": ET.XSLT.strparam(profile_id) |
| 63 | + } |
| 64 | + return params |
| 65 | + |
| 66 | + |
| 67 | +def make_output_file_name(profile_id, product): |
| 68 | + short_profile_id = profile_id.replace(OSCAP_PROFILE, "") |
| 69 | + output_file_name = "ssg-%s-guide-%s.html" % (product, short_profile_id) |
| 70 | + return output_file_name |
| 71 | + |
| 72 | + |
| 73 | +def make_output_file_path(profile_id, product, output_dir): |
| 74 | + output_file_name = make_output_file_name(profile_id, product) |
| 75 | + output_file_path = os.path.join(output_dir, output_file_name) |
| 76 | + return output_file_path |
| 77 | + |
| 78 | + |
| 79 | +def generate_html_guide(ds, transform, params, output_file_path): |
| 80 | + html = transform(ds, **params) |
| 81 | + html.write_output(output_file_path) |
| 82 | + |
| 83 | + |
| 84 | +def make_index_options(benchmarks): |
| 85 | + index_options = {} |
| 86 | + for benchmark_id, benchmark_data in benchmarks.items(): |
| 87 | + options = [] |
| 88 | + for profile_id, profile_title in benchmark_data.profiles.items(): |
| 89 | + guide_file_name = make_output_file_name( |
| 90 | + profile_id, benchmark_data.product) |
| 91 | + data_benchmark_id = "" if len(benchmarks) == 1 else benchmark_id |
| 92 | + option = ( |
| 93 | + f"<option value=\"{guide_file_name}\" data-benchmark-id=\"" |
| 94 | + f"{data_benchmark_id}\" data-profile-id=\"{profile_id}\">" |
| 95 | + f"{profile_title}</option>") |
| 96 | + options.append(option) |
| 97 | + index_options[benchmark_id] = options |
| 98 | + return index_options |
| 99 | + |
| 100 | + |
| 101 | +def make_index_links(benchmarks): |
| 102 | + index_links = [] |
| 103 | + for benchmark_id, benchmark_data in benchmarks.items(): |
| 104 | + for profile_id, profile_title in benchmark_data.profiles.items(): |
| 105 | + guide_file_name = make_output_file_name( |
| 106 | + profile_id, benchmark_data.product) |
| 107 | + a_target = ( |
| 108 | + f"<a target=\"guide\" href=\"{guide_file_name}\">" |
| 109 | + f"{profile_title} in {benchmark_id}</a>") |
| 110 | + index_links.append(a_target) |
| 111 | + return index_links |
| 112 | + |
| 113 | + |
| 114 | +def make_index_initial_src(benchmarks): |
| 115 | + for benchmark_data in benchmarks.values(): |
| 116 | + for profile_id in benchmark_data.profiles: |
| 117 | + return make_output_file_name(profile_id, benchmark_data.product) |
| 118 | + return None |
| 119 | + |
| 120 | + |
| 121 | +def generate_html_index(benchmarks, data_stream, output_dir): |
| 122 | + benchmark_titles = {id_: data.title for id_, data in benchmarks.items()} |
| 123 | + product = list(benchmarks.values())[0].product |
| 124 | + input_basename = os.path.basename(data_stream) |
| 125 | + index_links = make_index_links(benchmarks) |
| 126 | + index_options = make_index_options(benchmarks) |
| 127 | + index_initial_src = make_index_initial_src(benchmarks) |
| 128 | + index_source = ssg.build_guides.build_index( |
| 129 | + benchmark_titles, input_basename, index_links, index_options, |
| 130 | + index_initial_src) |
| 131 | + output_path = make_output_file_path("index", product, output_dir) |
| 132 | + with open(output_path, "wb") as f: |
| 133 | + f.write(index_source.encode("utf-8")) |
| 134 | + |
| 135 | + |
| 136 | +def generate_html_guides(ds, benchmarks, oscap_version, output_dir): |
| 137 | + xslt = ET.parse(XSLT_PATH) |
| 138 | + transform = ET.XSLT(xslt) |
| 139 | + for benchmark_id, benchmark_data in benchmarks.items(): |
| 140 | + for profile_id in benchmark_data.profiles: |
| 141 | + params = make_params(oscap_version, benchmark_id, profile_id) |
| 142 | + output_file_path = make_output_file_path( |
| 143 | + profile_id, benchmark_data.product, output_dir) |
| 144 | + generate_html_guide(ds, transform, params, output_file_path) |
| 145 | + |
| 146 | + |
| 147 | +def main(): |
| 148 | + args = parse_args() |
| 149 | + ds = ET.parse(args.data_stream) |
| 150 | + benchmarks = get_benchmarks(ds, args.product) |
| 151 | + if not os.path.exists(args.output_dir): |
| 152 | + os.mkdir(args.output_dir) |
| 153 | + generate_html_guides(ds, benchmarks, args.oscap_version, args.output_dir) |
| 154 | + generate_html_index(benchmarks, args.data_stream, args.output_dir) |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + main() |
0 commit comments