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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
#!/usr/bin/python3
import argparse
import collections
import lxml.etree as ET
import os
from ssg.constants import OSCAP_PROFILE, PREFIX_TO_NS
import ssg.build_guides
BenchmarkData = collections.namedtuple(
"BenchmarkData", ["title", "profiles", "product"])
OPENSCAP_POSSIBLE_ROOT_DIRS = [
os.getenv("OPENSCAP_ROOT_DIR"),
os.getenv("ProgramFiles"),
os.getenv("XSLT_PREFIX"),
"/usr",
"/usr/bin",
"/usr/sbin",
"/usr/local",
"/usr/share/",
"/usr/local/share",
"/opt",
"/opt/local",
]
for root_dir in OPENSCAP_POSSIBLE_ROOT_DIRS:
if root_dir is None:
continue
for subpath in [os.path.join("share", "openscap", "xsl"), "xsl"]:
file_path = os.path.join(root_dir, subpath, "xccdf-guide.xsl")
if os.path.exists(file_path):
XCCDF_GUIDE_XSL = file_path
break
else:
continue
break
else:
XCCDF_GUIDE_XSL = None
def get_benchmarks(ds, product):
benchmarks = {}
benchmark_xpath = "./ds:component/xccdf-1.2:Benchmark"
for benchmark_el in ds.xpath(benchmark_xpath, namespaces=PREFIX_TO_NS):
benchmark_id = benchmark_el.get("id")
title = benchmark_el.xpath(
"./xccdf-1.2:title", namespaces=PREFIX_TO_NS)[0].text
profiles = get_profiles(benchmark_el)
benchmarks[benchmark_id] = BenchmarkData(title, profiles, product)
return benchmarks
def get_profiles(benchmark_el):
profiles = {}
for profile_el in benchmark_el.xpath(
"./xccdf-1.2:Profile", namespaces=PREFIX_TO_NS):
profile_id = profile_el.get("id")
profile_title = profile_el.xpath(
"./xccdf-1.2:title", namespaces=PREFIX_TO_NS)[0].text
profiles[profile_id] = profile_title
return profiles
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--data-stream", required=True,
help="Path to a SCAP source data stream, eg. 'ssg-rhel9-ds.xml'")
parser.add_argument(
"--oscap-version", required=True,
help=f"Version of OpenSCAP that owns {XCCDF_GUIDE_XSL}, eg. 1.3.8")
parser.add_argument(
"--product", required=True,
help="Product ID, eg. rhel9")
parser.add_argument(
"--output-dir", required=True,
help="Path to the directory where to generate the output files"
", eg. 'build/guides'")
args = parser.parse_args()
return args
def make_params(oscap_version, benchmark_id, profile_id):
params = {
"oscap-version": ET.XSLT.strparam(oscap_version),
"benchmark_id": ET.XSLT.strparam(benchmark_id),
"profile_id": ET.XSLT.strparam(profile_id)
}
return params
def make_output_file_name(profile_id, product):
short_profile_id = profile_id.replace(OSCAP_PROFILE, "")
output_file_name = "ssg-%s-guide-%s.html" % (product, short_profile_id)
return output_file_name
def make_output_file_path(profile_id, product, output_dir):
output_file_name = make_output_file_name(profile_id, product)
output_file_path = os.path.join(output_dir, output_file_name)
return output_file_path
def generate_html_guide(ds, transform, params, output_file_path):
html = transform(ds, **params)
html.write_output(output_file_path)
def make_index_options(benchmarks):
index_options = {}
for benchmark_id, benchmark_data in benchmarks.items():
options = []
for profile_id, profile_title in benchmark_data.profiles.items():
guide_file_name = make_output_file_name(
profile_id, benchmark_data.product)
data_benchmark_id = "" if len(benchmarks) == 1 else benchmark_id
option = (
f"<option value=\"{guide_file_name}\" data-benchmark-id=\""
f"{data_benchmark_id}\" data-profile-id=\"{profile_id}\">"
f"{profile_title}</option>")
options.append(option)
index_options[benchmark_id] = options
return index_options
def make_index_links(benchmarks):
index_links = []
for benchmark_id, benchmark_data in benchmarks.items():
for profile_id, profile_title in benchmark_data.profiles.items():
guide_file_name = make_output_file_name(
profile_id, benchmark_data.product)
a_target = (
f"<a target=\"guide\" href=\"{guide_file_name}\">"
f"{profile_title} in {benchmark_id}</a>")
index_links.append(a_target)
return index_links
def make_index_initial_src(benchmarks):
for benchmark_data in benchmarks.values():
for profile_id in benchmark_data.profiles:
return make_output_file_name(profile_id, benchmark_data.product)
return None
def generate_html_index(benchmarks, data_stream, output_dir):
benchmark_titles = {id_: data.title for id_, data in benchmarks.items()}
product = list(benchmarks.values())[0].product
input_basename = os.path.basename(data_stream)
index_links = make_index_links(benchmarks)
index_options = make_index_options(benchmarks)
index_initial_src = make_index_initial_src(benchmarks)
index_source = ssg.build_guides.build_index(
benchmark_titles, input_basename, index_links, index_options,
index_initial_src)
output_path = make_output_file_path("index", product, output_dir)
with open(output_path, "wb") as f:
f.write(index_source.encode("utf-8"))
def generate_html_guides(ds, benchmarks, oscap_version, output_dir):
xslt = ET.parse(XCCDF_GUIDE_XSL)
transform = ET.XSLT(xslt)
for benchmark_id, benchmark_data in benchmarks.items():
for profile_id in benchmark_data.profiles:
params = make_params(oscap_version, benchmark_id, profile_id)
output_file_path = make_output_file_path(
profile_id, benchmark_data.product, output_dir)
generate_html_guide(ds, transform, params, output_file_path)
def main():
args = parse_args()
ds = ET.parse(args.data_stream)
benchmarks = get_benchmarks(ds, args.product)
if not os.path.exists(args.output_dir):
os.mkdir(args.output_dir)
generate_html_guides(ds, benchmarks, args.oscap_version, args.output_dir)
generate_html_index(benchmarks, args.data_stream, args.output_dir)
if __name__ == "__main__":
main()
|