Skip to content

Commit 3222734

Browse files
authored
Merge pull request #2070 from jan-cerny/references
Select rules based on reference
2 parents c09211d + 3b9508d commit 3222734

File tree

12 files changed

+390
-6
lines changed

12 files changed

+390
-6
lines changed

docs/manual/manual.adoc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,34 @@ STIG by DISA. When evaluating a STIG provided by DISA using `oscap`, use the
696696
`scap-security-guide` content in STIG Viewer and evaluating
697697
`scap-security-guide` by oscap, use `--results` instead of `--stig-viewer`.
698698

699+
=== Checking for compliance with a particular requirement coverage
700+
701+
A common theme is to check system status based on requirements of a particular policy.
702+
OpenSCAP can select rules that are related to a specific requirement based on the references in the rules.
703+
704+
1) List references that are supported in your scap content using the `oscap info --references` command.
705+
This will list of available reference names and their URIs.
706+
For example:
707+
708+
----
709+
$ oscap info --references /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
710+
... snip ...
711+
References:
712+
anssi: http://www.ssi.gouv.fr/administration/bonnes-pratiques/
713+
cis: https://www.cisecurity.org/benchmark/red_hat_linux/
714+
disa: https://public.cyber.mil/stigs/cci/
715+
... snip ...
716+
----
717+
718+
2) Run the evaluation with the `--reference` option, using the name obtained in the previous step and the requirement ID, separated by a colon.
719+
That will filter the list of rules so that only rules that have the given reference ID assigned would be evaluated.
720+
For example:
721+
722+
----
723+
$ oscap xccdf eval --profile cis --reference cis:3.3.2 /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
724+
----
725+
726+
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.
699727

700728
== Remediating system
701729

src/XCCDF/public/xccdf_session.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,13 @@ OSCAP_API int xccdf_session_generate_guide(struct xccdf_session *session, const
644644
*/
645645
OSCAP_API int xccdf_session_export_all(struct xccdf_session *session);
646646

647+
/**
648+
* Set reference filter to the XCCDF session. If this filter is set,
649+
* the XCCDF session will evaluate only rules that conform to the filter.
650+
* @param session XCCDF session
651+
* @param reference_filter a string in a form "key:identifier"
652+
*/
653+
OSCAP_API void xccdf_session_set_reference_filter(struct xccdf_session *session, const char *reference_filter);
647654
/// @}
648655
/// @}
649656
#endif

src/XCCDF/xccdf_session.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ struct xccdf_session {
7070
const char *filename; ///< File name of SCAP (SDS or XCCDF) file for this session.
7171
struct oscap_list *rules;
7272
struct oscap_list *skip_rules;
73+
const char *reference_parameter;
7374
struct oscap_source *source; ///< Main source assigned with the main file (SDS or XCCDF)
7475
char *temp_dir; ///< Temp directory used for decomposed component files.
7576
struct {
@@ -1384,6 +1385,9 @@ int xccdf_session_evaluate(struct xccdf_session *session)
13841385
}
13851386
oscap_iterator_free(sit);
13861387

1388+
if (session->reference_parameter) {
1389+
xccdf_policy_set_reference_filter(policy, session->reference_parameter);
1390+
}
13871391
session->xccdf.result = xccdf_policy_evaluate(policy);
13881392
if (session->xccdf.result == NULL)
13891393
return 1;
@@ -2059,3 +2063,8 @@ int xccdf_session_export_all(struct xccdf_session *session)
20592063
oscap_source_free(arf_source);
20602064
return ret;
20612065
}
2066+
2067+
void xccdf_session_set_reference_filter(struct xccdf_session *session, const char *reference_filter)
2068+
{
2069+
session->reference_parameter = reference_filter;
2070+
}

src/XCCDF_POLICY/xccdf_policy.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "xccdf_policy_engine_priv.h"
3737
#include "reporter_priv.h"
3838
#include "public/xccdf_policy.h"
39+
#include "public/xccdf_session.h"
3940
#include "public/xccdf_benchmark.h"
4041
#include "public/oscap_text.h"
4142

@@ -1031,6 +1032,25 @@ static void _warn_about_required_rules(const struct xccdf_policy *policy, const
10311032
oscap_stringlist_iterator_free(requires_it);
10321033
}
10331034

1035+
static bool _matches_references(struct xccdf_policy *policy, const struct xccdf_rule *rule)
1036+
{
1037+
if (!policy->reference_filter.active) {
1038+
return true;
1039+
}
1040+
bool matched = false;
1041+
struct oscap_reference_iterator *references = xccdf_item_get_references((struct xccdf_item *)rule);
1042+
while (oscap_reference_iterator_has_more(references) && !matched) {
1043+
struct oscap_reference *ref = oscap_reference_iterator_next(references);
1044+
const char *href = oscap_reference_get_href(ref);
1045+
const char *title = oscap_reference_get_title(ref);
1046+
if (!strcmp(href, policy->reference_filter.href) && !strcmp(title, policy->reference_filter.title)) {
1047+
matched = true;
1048+
}
1049+
}
1050+
oscap_reference_iterator_free(references);
1051+
return matched;
1052+
}
1053+
10341054
/**
10351055
* Evaluate given check which is immediate child of the rule.
10361056
* 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
10741094
}
10751095
}
10761096

1097+
if (!_matches_references(policy, rule)) {
1098+
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
1099+
}
1100+
10771101
/* Otherwise start reporting */
10781102
report = xccdf_policy_report_cb(policy, XCCDF_POLICY_OUTCB_START, (void *) rule);
10791103
if (report)
@@ -1882,6 +1906,10 @@ struct xccdf_policy * xccdf_policy_new(struct xccdf_policy_model * model, struct
18821906
policy->refine_rules_internal = oscap_htable_new();
18831907
policy->model = model;
18841908

1909+
policy->reference_filter.active = false;
1910+
policy->reference_filter.href = NULL;
1911+
policy->reference_filter.title = NULL;
1912+
18851913
benchmark = xccdf_policy_model_get_benchmark(model);
18861914

18871915
if (profile) {
@@ -2253,6 +2281,48 @@ void xccdf_policy_model_free(struct xccdf_policy_model * model) {
22532281
free(model);
22542282
}
22552283

2284+
static const char *_find_reference_uri_by_key(struct xccdf_benchmark *benchmark, const char *key)
2285+
{
2286+
const char *uri = NULL;
2287+
struct oscap_reference_iterator *benchmark_references = xccdf_item_get_references((struct xccdf_item *)benchmark);
2288+
while (oscap_reference_iterator_has_more(benchmark_references)) {
2289+
struct oscap_reference *ref = oscap_reference_iterator_next(benchmark_references);
2290+
const char *title = oscap_reference_get_title(ref);
2291+
if (!strcmp(key, title)) {
2292+
uri = oscap_reference_get_href(ref);
2293+
break;
2294+
}
2295+
}
2296+
oscap_reference_iterator_free(benchmark_references);
2297+
return uri;
2298+
}
2299+
2300+
void xccdf_policy_set_reference_filter(struct xccdf_policy *policy, const char *reference_parameter)
2301+
{
2302+
if (!reference_parameter) {
2303+
return;
2304+
}
2305+
char *reference_parameter_dup = strdup(reference_parameter);
2306+
char **split = oscap_split(reference_parameter_dup, ":");
2307+
struct xccdf_benchmark *benchmark = policy->model->benchmark;
2308+
char *key = split[0];
2309+
const char *uri = _find_reference_uri_by_key(benchmark, key);
2310+
if (!uri) {
2311+
oscap_seterr(OSCAP_EFAMILY_OSCAP, "Reference type '%s' isn't available in this benchmark", key);
2312+
goto cleanup;
2313+
}
2314+
char *title = split[1];
2315+
if (!title) {
2316+
oscap_seterr(OSCAP_EFAMILY_OSCAP, "Reference identifier hasn't been provided");
2317+
goto cleanup;
2318+
}
2319+
policy->reference_filter.active = true;
2320+
policy->reference_filter.href = strdup(uri);
2321+
policy->reference_filter.title = strdup(title);
2322+
cleanup:
2323+
free(split);
2324+
free(reference_parameter_dup);
2325+
}
22562326

22572327

22582328
void xccdf_policy_free(struct xccdf_policy * policy) {
@@ -2278,6 +2348,8 @@ void xccdf_policy_free(struct xccdf_policy * policy) {
22782348
oscap_htable_free0(policy->selected_internal);
22792349
oscap_htable_free0(policy->selected_final);
22802350
oscap_htable_free(policy->refine_rules_internal, (oscap_destruct_func) xccdf_refine_rule_internal_free);
2351+
free(policy->reference_filter.href);
2352+
free(policy->reference_filter.title);
22812353
free(policy);
22822354
}
22832355

src/XCCDF_POLICY/xccdf_policy_priv.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ struct xccdf_policy {
7272
struct oscap_htable *selected_final;
7373
/* The hash-table contains the latest refine-rule for specified item-id. */
7474
struct oscap_htable *refine_rules_internal;
75+
struct {
76+
bool active;
77+
char *href;
78+
char *title;
79+
} reference_filter;
7580
};
7681

7782

@@ -136,5 +141,6 @@ int xccdf_policy_report_cb(struct xccdf_policy *policy, const char *sysname, voi
136141
*/
137142
struct xccdf_benchmark *xccdf_policy_get_benchmark(const struct xccdf_policy *policy);
138143

144+
void xccdf_policy_set_reference_filter(struct xccdf_policy *policy, const char *reference_parameter);
139145

140146
#endif

tests/API/XCCDF/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,4 @@ add_oscap_test("test_results_hostname.sh")
109109
add_oscap_test("test_skip_rule.sh")
110110
add_oscap_test("test_no_newline_between_select_elements.sh")
111111
add_oscap_test("test_single_line_tailoring.sh")
112+
add_oscap_test("test_reference.sh")
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env bash
2+
. $builddir/tests/test_common.sh
3+
4+
set -e
5+
set -o pipefail
6+
7+
result=$(mktemp -t ${name}.out.XXXXXX)
8+
stderr=$(mktemp -t ${name}.out.XXXXXX)
9+
stdout=$(mktemp -t ${name}.out.XXXXXX)
10+
11+
ds="$srcdir/test_reference_ds.xml"
12+
p1="xccdf_com.example.www_profile_P1"
13+
r1="xccdf_com.example.www_rule_R1"
14+
r2="xccdf_com.example.www_rule_R2"
15+
r3="xccdf_com.example.www_rule_R3"
16+
r4="xccdf_com.example.www_rule_R4"
17+
18+
# Tests if references are correctly shown in oscap info output
19+
$OSCAP info --references $ds > $stdout 2> $stderr
20+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
21+
grep -q "References:" $stdout
22+
grep -q "animals: https://www.animals.com" $stdout
23+
grep -q "fruit: https://www.fruit.com" $stdout
24+
:> $stdout
25+
26+
# Tests that all rules from profile P1 (profile contains only 4 rules) are
27+
# evaluated when '--reference' option is not specified.
28+
$OSCAP xccdf eval --results $result --profile $p1 $ds > $stdout 2> $stderr
29+
30+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
31+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
32+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
33+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
34+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"pass\"]"
35+
:> $stdout
36+
:> $result
37+
38+
# Tests that rule R1 from profile P1 is evaluated when '--reference' option
39+
# matches the rule R1.
40+
$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:3.14" $ds > $stdout 2> $stderr
41+
42+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
43+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
44+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
45+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
46+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
47+
:> $stdout
48+
:> $result
49+
50+
# Tests that rule R1 from profile P1 is evaluated when '--reference' option
51+
# matches the rule R1. This test uses a different reference key than the
52+
# previous test.
53+
$OSCAP xccdf eval --results $result --profile $p1 --reference "fruit:42.42" $ds > $stdout 2> $stderr
54+
55+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
56+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
57+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
58+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
59+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
60+
:> $stdout
61+
:> $result
62+
63+
# Tests that only rules R2 and R3 from profile P1 are evaluated when
64+
# '--reference' option matches the rule R2 and R3, both rules have
65+
# the same reference item.
66+
$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:17.71.777" $ds > $stdout 2> $stderr
67+
68+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
69+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"notselected\"]"
70+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
71+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
72+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
73+
:> $stdout
74+
:> $result
75+
76+
# Tests that no rule from profile P1 is evaluated when '--reference' option
77+
# doesn't match any reference in any rule.
78+
$OSCAP xccdf eval --results $result --profile $p1 --reference "animals:99.66.33" $ds > $stdout 2> $stderr
79+
80+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
81+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"notselected\"]"
82+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"notselected\"]"
83+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"notselected\"]"
84+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"notselected\"]"
85+
:> $stdout
86+
:> $result
87+
88+
# Tests that when a wrong '--reference' option is provided OpenSCAP ignores it,
89+
# evaluates all rules and prints a nice error messsage.
90+
$OSCAP xccdf eval --results $result --profile $p1 --reference "aliens:XXX" $ds > $stdout 2> $stderr
91+
grep -q "OpenSCAP Error: Reference type 'aliens' isn't available in this benchmark" $stderr
92+
assert_exists 1 "//rule-result[@idref=\"$r1\"]/result[text()=\"pass\"]"
93+
assert_exists 1 "//rule-result[@idref=\"$r2\"]/result[text()=\"pass\"]"
94+
assert_exists 1 "//rule-result[@idref=\"$r3\"]/result[text()=\"pass\"]"
95+
assert_exists 1 "//rule-result[@idref=\"$r4\"]/result[text()=\"pass\"]"
96+
:> $stdout
97+
:> $result
98+
99+
# Tests that when a wrong '--reference' option with a valid name but missing
100+
# identifier is provided OpenSCAP prints an errror message.
101+
$OSCAP xccdf eval --results $result --profile $p1 --reference "animals" $ds > $stdout 2> $stderr || [[ $? -eq 1 ]]
102+
grep -q "The --reference argument needs to be in form NAME:IDENTIFIER, using a colon as a separator." $stderr
103+
:> $stdout
104+
:> $result

0 commit comments

Comments
 (0)