diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc
index ab069a3708..a461482268 100644
--- a/docs/manual/manual.adoc
+++ b/docs/manual/manual.adoc
@@ -696,6 +696,34 @@ STIG by DISA. When evaluating a STIG provided by DISA using `oscap`, use the
`scap-security-guide` content in STIG Viewer and evaluating
`scap-security-guide` by oscap, use `--results` instead of `--stig-viewer`.
+=== Checking for compliance with a particular requirement coverage
+
+A common theme is to check system status based on requirements of a particular policy.
+OpenSCAP can select rules that are related to a specific requirement based on the references in the rules.
+
+1) List references that are supported in your scap content using the `oscap info --references` command.
+This will list of available reference names and their URIs.
+For example:
+
+----
+$ oscap info --references /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
+... snip ...
+ References:
+ anssi: http://www.ssi.gouv.fr/administration/bonnes-pratiques/
+ cis: https://www.cisecurity.org/benchmark/red_hat_linux/
+ disa: https://public.cyber.mil/stigs/cci/
+... snip ...
+----
+
+2) Run the evaluation with the `--reference` option, using the name obtained in the previous step and the requirement ID, separated by a colon.
+That will filter the list of rules so that only rules that have the given reference ID assigned would be evaluated.
+For example:
+
+----
+$ oscap xccdf eval --profile cis --reference cis:3.3.2 /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
+----
+
+NOTE: If the `oscap info --references` command doesn't list any reference names in the `References` section of its output, it means that the provided SCAP content doesn't support this feature.
== Remediating system
diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h
index 8efa1d16d7..fbadeccd2e 100644
--- a/src/XCCDF/public/xccdf_session.h
+++ b/src/XCCDF/public/xccdf_session.h
@@ -644,6 +644,13 @@ OSCAP_API int xccdf_session_generate_guide(struct xccdf_session *session, const
*/
OSCAP_API int xccdf_session_export_all(struct xccdf_session *session);
+/**
+ * Set reference filter to the XCCDF session. If this filter is set,
+ * the XCCDF session will evaluate only rules that conform to the filter.
+ * @param session XCCDF session
+ * @param reference_filter a string in a form "key:identifier"
+ */
+OSCAP_API void xccdf_session_set_reference_filter(struct xccdf_session *session, const char *reference_filter);
/// @}
/// @}
#endif
diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c
index c5bc6d6947..5ec127be68 100644
--- a/src/XCCDF/xccdf_session.c
+++ b/src/XCCDF/xccdf_session.c
@@ -70,6 +70,7 @@ struct xccdf_session {
const char *filename; ///< File name of SCAP (SDS or XCCDF) file for this session.
struct oscap_list *rules;
struct oscap_list *skip_rules;
+ const char *reference_parameter;
struct oscap_source *source; ///< Main source assigned with the main file (SDS or XCCDF)
char *temp_dir; ///< Temp directory used for decomposed component files.
struct {
@@ -1384,6 +1385,9 @@ int xccdf_session_evaluate(struct xccdf_session *session)
}
oscap_iterator_free(sit);
+ if (session->reference_parameter) {
+ xccdf_policy_set_reference_filter(policy, session->reference_parameter);
+ }
session->xccdf.result = xccdf_policy_evaluate(policy);
if (session->xccdf.result == NULL)
return 1;
@@ -2059,3 +2063,8 @@ int xccdf_session_export_all(struct xccdf_session *session)
oscap_source_free(arf_source);
return ret;
}
+
+void xccdf_session_set_reference_filter(struct xccdf_session *session, const char *reference_filter)
+{
+ session->reference_parameter = reference_filter;
+}
diff --git a/src/XCCDF_POLICY/xccdf_policy.c b/src/XCCDF_POLICY/xccdf_policy.c
index c37be64b17..f12b046baf 100644
--- a/src/XCCDF_POLICY/xccdf_policy.c
+++ b/src/XCCDF_POLICY/xccdf_policy.c
@@ -36,6 +36,7 @@
#include "xccdf_policy_engine_priv.h"
#include "reporter_priv.h"
#include "public/xccdf_policy.h"
+#include "public/xccdf_session.h"
#include "public/xccdf_benchmark.h"
#include "public/oscap_text.h"
@@ -1031,6 +1032,25 @@ static void _warn_about_required_rules(const struct xccdf_policy *policy, const
oscap_stringlist_iterator_free(requires_it);
}
+static bool _matches_references(struct xccdf_policy *policy, const struct xccdf_rule *rule)
+{
+ if (!policy->reference_filter.active) {
+ return true;
+ }
+ bool matched = false;
+ struct oscap_reference_iterator *references = xccdf_item_get_references((struct xccdf_item *)rule);
+ while (oscap_reference_iterator_has_more(references) && !matched) {
+ struct oscap_reference *ref = oscap_reference_iterator_next(references);
+ const char *href = oscap_reference_get_href(ref);
+ const char *title = oscap_reference_get_title(ref);
+ if (!strcmp(href, policy->reference_filter.href) && !strcmp(title, policy->reference_filter.title)) {
+ matched = true;
+ }
+ }
+ oscap_reference_iterator_free(references);
+ return matched;
+}
+
/**
* Evaluate given check which is immediate child of the rule.
* A possibe child checks will be evaluated by xccdf_policy_check_evaluate.
@@ -1074,6 +1094,10 @@ _xccdf_policy_rule_evaluate(struct xccdf_policy * policy, const struct xccdf_rul
}
}
+ if (!_matches_references(policy, rule)) {
+ return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
+ }
+
/* Otherwise start reporting */
report = xccdf_policy_report_cb(policy, XCCDF_POLICY_OUTCB_START, (void *) rule);
if (report)
@@ -1882,6 +1906,10 @@ struct xccdf_policy * xccdf_policy_new(struct xccdf_policy_model * model, struct
policy->refine_rules_internal = oscap_htable_new();
policy->model = model;
+ policy->reference_filter.active = false;
+ policy->reference_filter.href = NULL;
+ policy->reference_filter.title = NULL;
+
benchmark = xccdf_policy_model_get_benchmark(model);
if (profile) {
@@ -2253,6 +2281,48 @@ void xccdf_policy_model_free(struct xccdf_policy_model * model) {
free(model);
}
+static const char *_find_reference_uri_by_key(struct xccdf_benchmark *benchmark, const char *key)
+{
+ const char *uri = NULL;
+ struct oscap_reference_iterator *benchmark_references = xccdf_item_get_references((struct xccdf_item *)benchmark);
+ while (oscap_reference_iterator_has_more(benchmark_references)) {
+ struct oscap_reference *ref = oscap_reference_iterator_next(benchmark_references);
+ const char *title = oscap_reference_get_title(ref);
+ if (!strcmp(key, title)) {
+ uri = oscap_reference_get_href(ref);
+ break;
+ }
+ }
+ oscap_reference_iterator_free(benchmark_references);
+ return uri;
+}
+
+void xccdf_policy_set_reference_filter(struct xccdf_policy *policy, const char *reference_parameter)
+{
+ if (!reference_parameter) {
+ return;
+ }
+ char *reference_parameter_dup = strdup(reference_parameter);
+ char **split = oscap_split(reference_parameter_dup, ":");
+ struct xccdf_benchmark *benchmark = policy->model->benchmark;
+ char *key = split[0];
+ const char *uri = _find_reference_uri_by_key(benchmark, key);
+ if (!uri) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Reference type '%s' isn't available in this benchmark", key);
+ goto cleanup;
+ }
+ char *title = split[1];
+ if (!title) {
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Reference identifier hasn't been provided");
+ goto cleanup;
+ }
+ policy->reference_filter.active = true;
+ policy->reference_filter.href = strdup(uri);
+ policy->reference_filter.title = strdup(title);
+cleanup:
+ free(split);
+ free(reference_parameter_dup);
+}
void xccdf_policy_free(struct xccdf_policy * policy) {
@@ -2278,6 +2348,8 @@ void xccdf_policy_free(struct xccdf_policy * policy) {
oscap_htable_free0(policy->selected_internal);
oscap_htable_free0(policy->selected_final);
oscap_htable_free(policy->refine_rules_internal, (oscap_destruct_func) xccdf_refine_rule_internal_free);
+ free(policy->reference_filter.href);
+ free(policy->reference_filter.title);
free(policy);
}
diff --git a/src/XCCDF_POLICY/xccdf_policy_priv.h b/src/XCCDF_POLICY/xccdf_policy_priv.h
index e5eb9ebd12..edfbe0c68f 100644
--- a/src/XCCDF_POLICY/xccdf_policy_priv.h
+++ b/src/XCCDF_POLICY/xccdf_policy_priv.h
@@ -72,6 +72,11 @@ struct xccdf_policy {
struct oscap_htable *selected_final;
/* The hash-table contains the latest refine-rule for specified item-id. */
struct oscap_htable *refine_rules_internal;
+ struct {
+ bool active;
+ char *href;
+ char *title;
+ } reference_filter;
};
@@ -136,5 +141,6 @@ int xccdf_policy_report_cb(struct xccdf_policy *policy, const char *sysname, voi
*/
struct xccdf_benchmark *xccdf_policy_get_benchmark(const struct xccdf_policy *policy);
+void xccdf_policy_set_reference_filter(struct xccdf_policy *policy, const char *reference_parameter);
#endif
diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt
index 95441b40e1..7a9b3b452c 100644
--- a/tests/API/XCCDF/unittests/CMakeLists.txt
+++ b/tests/API/XCCDF/unittests/CMakeLists.txt
@@ -109,3 +109,4 @@ add_oscap_test("test_results_hostname.sh")
add_oscap_test("test_skip_rule.sh")
add_oscap_test("test_no_newline_between_select_elements.sh")
add_oscap_test("test_single_line_tailoring.sh")
+add_oscap_test("test_reference.sh")
diff --git a/tests/API/XCCDF/unittests/test_reference.sh b/tests/API/XCCDF/unittests/test_reference.sh
new file mode 100755
index 0000000000..4c5a76ea03
--- /dev/null
+++ b/tests/API/XCCDF/unittests/test_reference.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+. $builddir/tests/test_common.sh
+
+set -e
+set -o pipefail
+
+result=$(mktemp -t ${name}.out.XXXXXX)
+stderr=$(mktemp -t ${name}.out.XXXXXX)
+stdout=$(mktemp -t ${name}.out.XXXXXX)
+
+ds="$srcdir/test_reference_ds.xml"
+p1="xccdf_com.example.www_profile_P1"
+r1="xccdf_com.example.www_rule_R1"
+r2="xccdf_com.example.www_rule_R2"
+r3="xccdf_com.example.www_rule_R3"
+r4="xccdf_com.example.www_rule_R4"
+
+# Tests if references are correctly shown in oscap info output
+$OSCAP info --references $ds > $stdout 2> $stderr
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+grep -q "References:" $stdout
+grep -q "animals: https://www.animals.com" $stdout
+grep -q "fruit: https://www.fruit.com" $stdout
+:> $stdout
+
+# Tests that all rules from profile P1 (profile contains only 4 rules) are
+# evaluated when '--reference' option is not specified.
+$OSCAP xccdf eval --results $result --profile $p1 $ds > $stdout 2> $stderr
+
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"pass\"]"
+:> $stdout
+:> $result
+
+# Tests that rule R1 from profile P1 is evaluated when '--reference' option
+# matches the rule R1.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:3.14" $ds > $stdout 2> $stderr
+
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
+:> $stdout
+:> $result
+
+# Tests that rule R1 from profile P1 is evaluated when '--reference' option
+# matches the rule R1. This test uses a different reference key than the
+# previous test.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "fruit:42.42" $ds > $stdout 2> $stderr
+
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
+:> $stdout
+:> $result
+
+# Tests that only rules R2 and R3 from profile P1 are evaluated when
+# '--reference' option matches the rule R2 and R3, both rules have
+# the same reference item.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:17.71.777" $ds > $stdout 2> $stderr
+
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
+:> $stdout
+:> $result
+
+# Tests that no rule from profile P1 is evaluated when '--reference' option
+# doesn't match any reference in any rule.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:99.66.33" $ds > $stdout 2> $stderr
+
+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
+:> $stdout
+:> $result
+
+# Tests that when a wrong '--reference' option is provided OpenSCAP ignores it,
+# evaluates all rules and prints a nice error messsage.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "aliens:XXX" $ds > $stdout 2> $stderr
+grep -q "OpenSCAP Error: Reference type 'aliens' isn't available in this benchmark" $stderr
+assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
+assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"pass\"]"
+:> $stdout
+:> $result
+
+# Tests that when a wrong '--reference' option with a valid name but missing
+# identifier is provided OpenSCAP prints an errror message.
+$OSCAP xccdf eval --results $result --profile $p1 --reference "animals" $ds > $stdout 2> $stderr || [[ $? -eq 1 ]]
+grep -q "The --reference argument needs to be in form NAME:IDENTIFIER, using a colon as a separator." $stderr
+:> $stdout
+:> $result
diff --git a/tests/API/XCCDF/unittests/test_reference_ds.xml b/tests/API/XCCDF/unittests/test_reference_ds.xml
new file mode 100644
index 0000000000..c2bacf0704
--- /dev/null
+++ b/tests/API/XCCDF/unittests/test_reference_ds.xml
@@ -0,0 +1,107 @@
+
+