Skip to content

Commit

Permalink
Merge pull request OpenSCAP#2070 from jan-cerny/references
Browse files Browse the repository at this point in the history
Select rules based on reference
  • Loading branch information
Mab879 authored Jan 10, 2024
2 parents c09211d + 3b9508d commit 3222734
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 6 deletions.
28 changes: 28 additions & 0 deletions docs/manual/manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions src/XCCDF/public/xccdf_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions src/XCCDF/xccdf_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
72 changes: 72 additions & 0 deletions src/XCCDF_POLICY/xccdf_policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}

Expand Down
6 changes: 6 additions & 0 deletions src/XCCDF_POLICY/xccdf_policy_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};


Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions tests/API/XCCDF/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
104 changes: 104 additions & 0 deletions tests/API/XCCDF/unittests/test_reference.sh
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 3222734

Please sign in to comment.