Source code for ssg.build_guides

from __future__ import absolute_import
from __future__ import print_function

import os
import sys
from collections import namedtuple

from .shims import subprocess_check_output, Queue
from .xccdf import get_profile_choices_for_input, get_profile_short_id
from .xccdf import PROFILE_ID_SKIPLIST
from .constants import OSCAP_DS_STRING, OSCAP_PATH


[docs] def get_path_args(args): """ Return a namedtuple of (input_path, input_basename, path_base, output_dir) from an argparse containing args.input and args.output. """ paths = namedtuple('paths', ['input_path', 'input_basename', 'path_base', 'output_dir']) input_path = os.path.abspath(args.input) output_dir = os.path.abspath(args.output) input_basename = os.path.basename(input_path) path_base, _ = os.path.splitext(input_basename) # avoid -ds and -xccdf suffices in guide filenames if path_base.endswith("-ds"): path_base = path_base[:-3] elif path_base.endswith("-xccdf"): path_base = path_base[:-6] return paths(input_path, input_basename, path_base, output_dir)
[docs] def generate_for_input_content(input_content, benchmark_id, profile_id): """ Returns HTML guide for given input_content and profile_id combination. This function assumes only one Benchmark exists in given input_content! """ args = [OSCAP_PATH, "xccdf", "generate", "guide"] if benchmark_id != "": args.extend(["--benchmark-id", benchmark_id]) if profile_id != "": args.extend(["--profile", profile_id]) args.append(input_content) return subprocess_check_output(args).decode("utf-8")
[docs] def builder(queue): """ Fetch from a queue of tasks, process tasks until the queue is empty. Each task is processed with generate_for_input_content, and the guide is written as output. Raises: when an error occurred when processing a task. """ while True: try: benchmark_id, profile_id, input_path, guide_path = \ queue.get(False) guide_html = generate_for_input_content( input_path, benchmark_id, profile_id ) with open(guide_path, "wb") as guide_file: guide_file.write(guide_html.encode("utf-8")) queue.task_done() except Queue.Empty: break except Exception as error: sys.stderr.write( "Fatal error encountered when generating guide '%s'. " "Error details:\n%s\n\n" % (guide_path, error) ) with queue.mutex: queue.queue.clear() raise error
def _benchmark_profile_pair_sort_key(benchmark_id, profile_id, profile_title): # The "base" benchmarks come first if (benchmark_id.endswith("_RHEL-7") or benchmark_id.endswith("_RHEL-6") or benchmark_id.endswith("_RHEL-5")): benchmark_id = "AAA" + benchmark_id # The default profile comes last if not profile_id: profile_title = "zzz(default)" return (benchmark_id, profile_title)
[docs] def get_benchmark_profile_pairs(input_tree, benchmarks): benchmark_profile_pairs = [] for benchmark_id in benchmarks.keys(): profiles = get_profile_choices_for_input(input_tree, benchmark_id, None) for profile_id in profiles: pair = (benchmark_id, profile_id, profiles[profile_id]) benchmark_profile_pairs.append(pair) return sorted(benchmark_profile_pairs, key=lambda x: _benchmark_profile_pair_sort_key(x[0], x[1], x[2]))
def _is_skipped_profile(profile_id): for skipped_id in PROFILE_ID_SKIPLIST: if profile_id.endswith(skipped_id): return True return False def _get_guide_filename(path_base, profile_id, benchmark_id, benchmarks): profile_id_for_path = "default" if not profile_id else profile_id benchmark_id_for_path = benchmark_id if benchmark_id_for_path.startswith(OSCAP_DS_STRING): benchmark_id_for_path = \ benchmark_id_for_path[len(OSCAP_DS_STRING):] if len(benchmarks) == 1 or len(benchmark_id_for_path) == len("RHEL-X"): # treat the base RHEL benchmark as a special case to preserve # old guide paths and old URLs that people may be relying on return "%s-guide-%s.html" % (path_base, get_profile_short_id(profile_id_for_path)) return "%s-%s-guide-%s.html" % \ (path_base, benchmark_id_for_path, get_profile_short_id(profile_id_for_path))
[docs] def get_output_guide_paths(benchmarks, benchmark_profile_pairs, path_base, output_dir): """ Return a list of guide paths containing guides for each non-skipped profile_id in a benchmark. """ guide_paths = [] for benchmark_id, profile_id, _ in benchmark_profile_pairs: if _is_skipped_profile(profile_id): continue guide_filename = _get_guide_filename(path_base, profile_id, benchmark_id, benchmarks) guide_path = os.path.join(output_dir, guide_filename) guide_paths.append(guide_path) return guide_paths
[docs] def fill_queue(benchmarks, benchmark_profile_pairs, input_path, path_base, output_dir): """ For each benchmark and profile in the benchmark, create a queue of tasks for later processing. A task is a named tuple (benchmark_id, profile_id, input_path, guide_path). Returns: queue of tasks. """ index_links = [] index_options = {} index_initial_src = None queue = Queue.Queue() task = namedtuple('task', ['benchmark_id', 'profile_id', 'input_path', 'guide_path']) for benchmark_id, profile_id, profile_title in benchmark_profile_pairs: if _is_skipped_profile(profile_id): continue guide_filename = _get_guide_filename(path_base, profile_id, benchmark_id, benchmarks) guide_path = os.path.join(output_dir, guide_filename) index_links.append( "<a target=\"guide\" href=\"%s\">%s</a>" % (guide_filename, "%s in %s" % (profile_title, benchmark_id)) ) if benchmark_id not in index_options: index_options[benchmark_id] = [] index_options[benchmark_id].append( "<option value=\"%s\" data-benchmark-id=\"%s\" data-profile-id=\"%s\">%s</option>" % (guide_filename, "" if len(benchmarks) == 1 else benchmark_id, profile_id, profile_title) ) if index_initial_src is None: index_initial_src = guide_filename queue.put(task(benchmark_id, profile_id, input_path, guide_path)) return index_links, index_options, index_initial_src, queue
[docs] def build_index(benchmarks, input_basename, index_links, index_options, index_initial_src): index_select_options = "" if len(index_options.keys()) > 1: # we sort by length of the benchmark_id to make sure the "default" # comes up first in the list for benchmark_id in sorted(index_options.keys(), key=lambda val: (len(val), val)): index_select_options += "<optgroup label=\"benchmark: %s\">\n" \ % (benchmark_id) index_select_options += "\n".join(index_options[benchmark_id]) index_select_options += "</optgroup>\n" else: index_select_options += "\n".join(list(index_options.values())[0]) return "".join([ "<!DOCTYPE html>\n", "<html lang=\"en\">\n", "\t<head>\n", "\t\t<meta charset=\"utf-8\">\n", "\t\t<title>%s</title>\n" % (list(benchmarks.values())[0]), "\t\t<script>\n", "\t\t\tfunction change_profile(option_element)\n", "\t\t\t{\n", "\t\t\t\tvar benchmark_id=option_element.getAttribute('data-benchmark-id');\n", "\t\t\t\tvar profile_id=option_element.getAttribute('data-profile-id');\n", "\t\t\t\tvar eval_snippet=document.getElementById('eval_snippet');\n", "\t\t\t\tvar input_path='/usr/share/xml/scap/ssg/content/%s';\n" % (input_basename), "\t\t\t\tif (profile_id == '')\n", "\t\t\t\t{\n", "\t\t\t\t\tif (benchmark_id == '')\n", "\t\t\t\t\t\teval_snippet.innerHTML='# oscap xccdf eval ' + input_path;\n", "\t\t\t\t\telse\n", "\t\t\t\t\t\teval_snippet.innerHTML='# oscap xccdf eval --benchmark-id ' + benchmark_id + ' &#92;<br/>' + input_path;\n", "\t\t\t\t}\n", "\t\t\t\telse\n", "\t\t\t\t{\n", "\t\t\t\t\tif (benchmark_id == '')\n", "\t\t\t\t\t\teval_snippet.innerHTML='# oscap xccdf eval --profile ' + profile_id + ' &#92;<br/>' + input_path;\n", "\t\t\t\t\telse\n", "\t\t\t\t\t\teval_snippet.innerHTML='# oscap xccdf eval --benchmark-id ' + benchmark_id + ' &#92;<br/>--profile ' + profile_id + ' &#92;<br/>' + input_path;\n", "\t\t\t\t}\n", "\t\t\t\twindow.open(option_element.value, 'guide');\n", "\t\t\t}\n", "\t\t</script>\n", "\t\t<style>\n", "\t\t\thtml, body { margin: 0; height: 100% }\n", "\t\t\t#js_switcher { position: fixed; right: 30px; top: 10px; padding: 2px; background: #ddd; border: 1px solid #999 }\n", "\t\t\t#guide_div { margin: auto; width: 99%; height: 99% }\n", "\t\t</style>\n", "\t</head>\n", "\t<body onload=\"document.getElementById('js_switcher').style.display = 'block'\">\n", "\t\t<noscript>\n", "Profiles: ", ", ".join(index_links) + "\n", "\t\t</noscript>\n", "\t\t<div id=\"js_switcher\" style=\"display: none\">\n", "\t\t\tProfile: \n", "\t\t\t<select style=\"margin-bottom: 5px\" ", "onchange=\"change_profile(this.options[this.selectedIndex]);\"", ">\n", "\n", index_select_options, "\n", "\t\t\t</select>\n", "\t\t\t<div id='eval_snippet' style='background: #eee; padding: 3px; border: 1px solid #000'>", "select a profile to display its guide and a command line snippet needed to use it", "</div>\n", "\t\t</div>\n", "\t\t<div id=\"guide_div\">\n", "\t\t\t<iframe src=\"%s\" name=\"guide\" " % (index_initial_src), "width=\"100%\" height=\"100%\">\n", "\t\t\t</iframe>\n", "\t\t</div>\n", "\t</body>\n", "</html>\n" ])