ComplianceAsCode/content

View on GitHub
build-scripts/generate_guides.py

Summary

Maintainability
A
0 mins
Test Coverage
#!/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()