diff --git a/src/XCCDF/benchmark.c b/src/XCCDF/benchmark.c
index f561736d4..d6560b7c9 100644
--- a/src/XCCDF/benchmark.c
+++ b/src/XCCDF/benchmark.c
@@ -866,6 +866,19 @@ xccdf_benchmark_find_target_htable(const struct xccdf_benchmark *benchmark, xccd
return XITEM(benchmark)->sub.benchmark.items_dict;
}
+int xccdf_benchmark_include_tailored_profiles(struct xccdf_benchmark *benchmark)
+{
+ struct oscap_list *profiles = XITEM(benchmark)->sub.benchmark.profiles;
+ struct oscap_htable_iterator *it = oscap_htable_iterator_new(XITEM(benchmark)->sub.benchmark.profiles_dict);
+ while(oscap_htable_iterator_has_more(it)) {
+ struct xccdf_profile *profile = oscap_htable_iterator_next_value(it);
+ if (xccdf_profile_get_tailoring(profile)) {
+ oscap_list_add(profiles, xccdf_profile_clone(profile));
+ }
+ }
+ oscap_htable_iterator_free(it);
+ return 0;
+}
struct xccdf_plain_text *xccdf_plain_text_new(void)
{
diff --git a/src/XCCDF/item.h b/src/XCCDF/item.h
index 35204744f..da76f854d 100644
--- a/src/XCCDF/item.h
+++ b/src/XCCDF/item.h
@@ -437,6 +437,7 @@ void xccdf_item_dump(struct xccdf_item *item, int depth);
struct xccdf_item* xccdf_item_get_benchmark_internal(struct xccdf_item* item);
bool xccdf_benchmark_parse(struct xccdf_item *benchmark, xmlTextReaderPtr reader);
void xccdf_benchmark_dump(struct xccdf_benchmark *benchmark);
+int xccdf_benchmark_include_tailored_profiles(struct xccdf_benchmark *benchmark);
struct oscap_htable_iterator *xccdf_benchmark_get_cluster_items(struct xccdf_benchmark *benchmark, const char *cluster_id);
bool xccdf_benchmark_register_item(struct xccdf_benchmark *benchmark, struct xccdf_item *item);
bool xccdf_benchmark_unregister_item(struct xccdf_item *item);
diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h
index ac96e2036..026432693 100644
--- a/src/XCCDF/public/xccdf_session.h
+++ b/src/XCCDF/public/xccdf_session.h
@@ -558,6 +558,14 @@ OSCAP_API int xccdf_session_build_policy_from_testresult(struct xccdf_session *s
*/
OSCAP_API int xccdf_session_add_report_from_source(struct xccdf_session *session, struct oscap_source *report_source);
+/**
+ * Generate HTML guide form a loaded XCCDF session
+ * @param session XCCDF Session
+ * @param outfile path to the output file
+ * @returns zero on success
+ */
+OSCAP_API int xccdf_session_generate_guide(struct xccdf_session *session, const char *outfile);
+
/// @}
/// @}
#endif
diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c
index 1005a030a..d38905f77 100644
--- a/src/XCCDF/xccdf_session.c
+++ b/src/XCCDF/xccdf_session.c
@@ -1746,3 +1746,44 @@ int xccdf_session_add_report_from_source(struct xccdf_session *session, struct o
session->xccdf.result = result;
return 0;
}
+
+int xccdf_session_generate_guide(struct xccdf_session *session, const char *outfile)
+{
+ struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session);
+ if (policy == NULL) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF policy.");
+ return 1;
+ }
+ struct xccdf_policy_model *model = xccdf_policy_get_model(policy);
+ if (model == NULL) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF policy model.");
+ return 1;
+ }
+ struct xccdf_benchmark *benchmark = xccdf_policy_model_get_benchmark(model);
+ if (benchmark == NULL) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF benchmark.");
+ return 1;
+ }
+ xccdf_benchmark_include_tailored_profiles(benchmark);
+ const char *params[] = {
+ "oscap-version", oscap_get_version(),
+ "benchmark_id", xccdf_benchmark_get_id(benchmark),
+ "profile_id", xccdf_session_get_profile_id(session),
+ NULL
+ };
+ struct oscap_source *benchmark_source = xccdf_benchmark_export_source(benchmark, NULL);
+ if (benchmark_source == NULL) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP,
+ "Failed to export source from XCCDF benchmark %s.",
+ xccdf_benchmark_get_id(benchmark));
+ return 1;
+ }
+ int ret = oscap_source_apply_xslt_path(benchmark_source,
+ "xccdf-guide.xsl", outfile, params, oscap_path_to_xslt());
+ oscap_source_free(benchmark_source);
+ if (ret < 0) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not apply XSLT.");
+ return 1;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/API/XCCDF/tailoring/all.sh b/tests/API/XCCDF/tailoring/all.sh
index dbed97a87..fc2ccd743 100755
--- a/tests/API/XCCDF/tailoring/all.sh
+++ b/tests/API/XCCDF/tailoring/all.sh
@@ -134,6 +134,21 @@ function test_api_xccdf_tailoring_profile_generate_fix {
rm -f $tailoring_result $fix_result
}
+function test_api_xccdf_tailoring_profile_generate_guide {
+ local INPUT=$srcdir/$1
+ local TAILORING=$srcdir/$2
+
+ guide=`mktemp`
+ # tailoring profile only with "always fail" rule and generate HTML guide
+ $OSCAP xccdf generate guide --tailoring-file $TAILORING --profile "xccdf_com.example.www_profile_customized" --output $guide $INPUT
+
+ grep -q "Baseline Testing Profile 1 \[CUSTOMIZED\]" $guide
+ # profile 'customized' selects first rule and deselects the second
+ grep -q "xccdf_com.example.www_rule_first" $guide
+ grep -v "xccdf_com.example.www_rule_second" $guide
+ rm -f $guide
+}
+
# Testing.
test_init "test_api_xccdf_tailoring.log"
@@ -154,6 +169,7 @@ test_run "test_api_xccdf_tailoring_autonegotiation" test_api_xccdf_tailoring_aut
test_run "test_api_xccdf_tailoring_simple_include_in_arf" test_api_xccdf_tailoring_simple_include_in_arf simple-xccdf.xml simple-tailoring.xml
test_run "test_api_xccdf_tailoring_profile_include_in_arf" test_api_xccdf_tailoring_profile_include_in_arf baseline.xccdf.xml baseline.tailoring.xml
test_run "test_api_xccdf_tailoring_profile_generate_fix" test_api_xccdf_tailoring_profile_generate_fix baseline.xccdf.xml baseline.tailoring.xml
+test_run "test_api_xccdf_tailoring_profile_generate_guide" test_api_xccdf_tailoring_profile_generate_guide baseline.xccdf.xml baseline.tailoring.xml
test_exit
diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c
index 472a2cc6f..87a0c7ad1 100644
--- a/utils/oscap-xccdf.c
+++ b/utils/oscap-xccdf.c
@@ -77,6 +77,7 @@ static bool getopt_xccdf(int argc, char **argv, struct oscap_action *action);
static bool getopt_generate(int argc, char **argv, struct oscap_action *action);
static int app_xccdf_xslt(const struct oscap_action *action);
static int app_generate_fix(const struct oscap_action *action);
+static int app_generate_guide(const struct oscap_action *action);
#define XCCDF_SUBMODULES_NUM 7
#define XCCDF_GEN_SUBMODULES_NUM 5 /* See actual arrays
@@ -244,12 +245,18 @@ static struct oscap_module XCCDF_GEN_GUIDE = {
.help = GEN_OPTS
"\nGuide Options:\n"
" --output <file> - Write the document into file.\n"
- " --hide-profile-info - Do not output additional information about selected profile.\n"
+ " --hide-profile-info - This option has no effect.\n"
" --benchmark-id <id> - ID of XCCDF Benchmark in some component in the datastream that should be used.\n"
+ " (only applicable for source datastreams)\n"
+ " --xccdf-id <id> - ID of component-ref with XCCDF in the datastream that should be evaluated.\n"
+ " (only applicable for source datastreams)\n"
+ " --tailoring-file <file> - Use given XCCDF Tailoring file.\n"
+ " (only applicable for source datastreams)\n"
+ " --tailoring-id <component-id> - Use given DS component as XCCDF Tailoring file.\n"
" (only applicable for source datastreams)\n",
.opt_parser = getopt_xccdf,
- .user = "xccdf-guide.xsl",
- .func = app_xccdf_xslt
+ .user = NULL,
+ .func = app_generate_guide
};
static struct oscap_module XCCDF_GEN_FIX = {
@@ -973,6 +980,50 @@ int app_generate_fix(const struct oscap_action *action)
return ret;
}
+int app_generate_guide(const struct oscap_action *action)
+{
+ int ret = OSCAP_ERROR;
+
+ struct oscap_source *source = oscap_source_new_from_file(action->f_xccdf);
+ struct xccdf_session *session = xccdf_session_new_from_source(source);
+ if (session == NULL) {
+ goto cleanup;
+ }
+
+ xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL);
+ xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback);
+ xccdf_session_set_user_tailoring_file(session, action->tailoring_file);
+ xccdf_session_set_user_tailoring_cid(session, action->tailoring_id);
+ if (xccdf_session_is_sds(session)) {
+ xccdf_session_set_component_id(session, action->f_xccdf_id);
+ xccdf_session_set_benchmark_id(session, action->f_benchmark_id);
+ }
+ xccdf_session_set_loading_flags(session, XCCDF_SESSION_LOAD_XCCDF);
+
+ if (xccdf_session_load(session) != 0) {
+ goto cleanup;
+ }
+
+ if (!xccdf_session_set_profile_id(session, action->profile)) {
+ if (action->profile != NULL) {
+ if (xccdf_set_profile_or_report_bad_id(session, action->profile, action->f_xccdf) == OSCAP_ERROR)
+ goto cleanup;
+ } else {
+ fprintf(stderr, "No Policy was found for default profile.\n");
+ goto cleanup;
+ }
+ }
+
+ if (xccdf_session_generate_guide(session, action->f_results) == 0) {
+ ret = OSCAP_OK;
+ }
+
+cleanup:
+ xccdf_session_free(session);
+ oscap_print_error();
+ return ret;
+}
+
int app_xccdf_xslt(const struct oscap_action *action)
{
const char *oval_template = action->oval_template;
diff --git a/utils/oscap.8 b/utils/oscap.8
index 1b05f8c9e..3a50d1df5 100644
--- a/utils/oscap.8
+++ b/utils/oscap.8
@@ -331,10 +331,19 @@ Generate a HTML document containing a security guide from an XCCDF Benchmark. Un
Write the guide to this file instead of standard output.
.TP
\fB\-\-hide-profile-info\fR
-Information on chosen profile (e.g. rules selected by the profile) will be excluded from the document.
+This option has no effect and is kept only for backward compatibility purposes.
.TP
\fB\-\-benchmark-id ID\fR
Selects a component ref from any datastream that references a component with XCCDF Benchmark such that its @id attribute matches given string exactly.
+.TP
+\fB\-\-xccdf-id ID\fR
+Takes component ref with given ID from checklists. This allows to select a particular XCCDF component even in cases where there are 2 XCCDFs in one datastream. If none is given, the first component from the checklists element is used.
+.TP
+\fB\-\-tailoring-file TAILORING_FILE\fR
+Use given file for XCCDF tailoring. Select profile from tailoring file to apply using --profile. If both --tailoring-file and --tailoring-id are specified, --tailoring-file takes priority.
+.TP
+\fB\-\-tailoring-id COMPONENT_REF_ID\fR
+Use tailoring component in input source datastream for XCCDF tailoring. The tailoring component must be specified by its Ref-ID (value of component-ref/@id attribute in input source datastream). Select profile from tailoring component to apply using --profile. If both --tailoring-file and --tailoring-id are specified, --tailoring-file takes priority.
.RE
.TP
.B \fBreport\fR [\fIoptions\fR] xccdf-file