Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select rules on the command line #1832

Merged
merged 10 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/bash_completion.d/oscap
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function _oscap {
opts[oscap:oval:analyse]="--variables --directives --verbose --verbose-log-file --skip-valid --skip-validation"
opts[oscap:oval:collect]="--id --syschar --skip-valid --skip-validation --variables --verbose --verbose-log-file"
opts[oscap:oval:generate:report]="-o --output"
opts[oscap:xccdf:eval]="--benchmark-id --check-engine-results --cpe --datastream-id --enforce-signature --export-variables --fetch-remote-resources --local-files --oval-results --profile --progress --progress-full --remediate --report --results --results-arf --rule --skip-valid --skip-validation --skip-signature-validation --stig-viewer --tailoring-file --tailoring-id --thin-results --verbose --verbose-log-file --without-syschar --xccdf-id"
opts[oscap:xccdf:eval]="--benchmark-id --check-engine-results --cpe --datastream-id --enforce-signature --export-variables --fetch-remote-resources --local-files --oval-results --profile --progress --progress-full --remediate --report --results --results-arf --rule --skip-rule --skip-valid --skip-validation --skip-signature-validation --stig-viewer --tailoring-file --tailoring-id --thin-results --verbose --verbose-log-file --without-syschar --xccdf-id"
opts[oscap:xccdf:validate]="--skip-schematron"
opts[oscap:xccdf:export-oval-variables]="--datastream-id --xccdf-id --profile --skip-valid --skip-validation --fetch-remote-resources --local-files --cpe"
opts[oscap:xccdf:remediate]="--result-id --skip-valid --skip-validation --fetch-remote-resources --local-files --results --results-arf --report --oval-results --export-variables --cpe --check-engine-results --progress --progress-full"
Expand Down
4 changes: 4 additions & 0 deletions docs/manual/manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,10 @@ $ oscap xccdf eval --profile Desktop --rule ensure_gpgcheck_globally_activated
Where `ensure_gpgcheck_globally_activated` is the only rule from the `Desktop`
profile which will be evaluated.

The `--rule` option can be used multiple times to evaluate multiple rules at once.

* You can skip some rules by using the `--skip-rule` option.

In the examples above we are generating XCCDF result files using the `--results`
command-line argument. You can use `--results-arf` to generate an SCAP result
data stream (also called ARF - Asset Reporting Format) XML instead.
Expand Down
19 changes: 18 additions & 1 deletion src/XCCDF/public/xccdf_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,24 @@ OSCAP_API bool xccdf_session_is_sds(const struct xccdf_session *session);
* @param session XCCDF Session
* @param rule If not NULL, session will use only this rule
*/
OSCAP_API void xccdf_session_set_rule(struct xccdf_session *session, const char *rule);
OSCAP_DEPRECATED(OSCAP_API void xccdf_session_set_rule(struct xccdf_session *session, const char *rule));

/**
* Add specific rule for session - if at least one rule is added to the session,
* only the added rules will be evaluated
* @memberof xccdf_session
* @param session XCCDF Session
* @param rule rule ID
*/
OSCAP_API void xccdf_session_add_rule(struct xccdf_session *session, const char *rule);

/**
* Skip rule during evaluation of the session
* @memberof xccdf_session
* @param session XCCDF Session
* @param rule rule ID
*/
OSCAP_API void xccdf_session_skip_rule(struct xccdf_session *session, const char *rule);

/**
* Set XSD validation level to one of three possibilities:
Expand Down
33 changes: 30 additions & 3 deletions src/XCCDF/xccdf_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ struct oval_content_resource {

struct xccdf_session {
const char *filename; ///< File name of SCAP (SDS or XCCDF) file for this session.
const char *rule; ///< Single-rule feature: if not NULL, the session will work only with this one rule.
struct oscap_list *rules;
struct oscap_list *skip_rules;
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 @@ -163,6 +164,8 @@ struct xccdf_session *xccdf_session_new_from_source(struct oscap_source *source)
session->oval.progress = download_progress_empty_calllback;
session->check_engine_plugins = oscap_list_new();
session->loading_flags = XCCDF_SESSION_LOAD_ALL;
session->rules = oscap_list_new();
session->skip_rules = oscap_list_new();

// We now have to switch up the oscap_sources in case we were given XCCDF tailoring

Expand Down Expand Up @@ -354,6 +357,7 @@ void xccdf_session_free(struct xccdf_session *session)
oscap_htable_free(session->oval.results_mapping, (oscap_destruct_func) free);
oscap_htable_free(session->oval.arf_report_mapping, (oscap_destruct_func) free);
oscap_signature_ctx_free(session->signature_ctx);
oscap_list_free(session->rules, (oscap_destruct_func) free);
free(session);
}

Expand All @@ -369,7 +373,19 @@ bool xccdf_session_is_sds(const struct xccdf_session *session)

void xccdf_session_set_rule(struct xccdf_session *session, const char *rule)
{
session->rule = rule;
while (oscap_list_pop(session->rules, free))
;
oscap_list_add(session->rules, strdup(rule));
}

void xccdf_session_add_rule(struct xccdf_session *session, const char *rule)
{
oscap_list_add(session->rules, strdup(rule));
}

void xccdf_session_skip_rule(struct xccdf_session *session, const char *rule)
{
oscap_list_add(session->skip_rules, strdup(rule));
}

void xccdf_session_set_validation(struct xccdf_session *session, bool validate, bool full_validation)
Expand Down Expand Up @@ -1318,7 +1334,18 @@ int xccdf_session_evaluate(struct xccdf_session *session)
oscap_seterr(OSCAP_EFAMILY_OSCAP, "Cannot build xccdf_policy.");
return 1;
}
policy->rule = session->rule;
struct oscap_iterator *it = oscap_iterator_new(session->rules);
while (oscap_iterator_has_more(it)) {
const char *rule_id = oscap_iterator_next(it);
oscap_htable_add(policy->rules, rule_id, (void *)true);
}
oscap_iterator_free(it);
struct oscap_iterator *sit = oscap_iterator_new(session->skip_rules);
while (oscap_iterator_has_more(sit)) {
const char *rule_id = oscap_iterator_next(sit);
oscap_htable_add(policy->skip_rules, rule_id, (void *)true);
}
oscap_iterator_free(sit);

session->xccdf.result = xccdf_policy_evaluate(policy);
if (session->xccdf.result == NULL)
Expand Down
104 changes: 76 additions & 28 deletions src/XCCDF_POLICY/xccdf_policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ static xccdf_test_result_type_t _resolve_negate(xccdf_test_result_type_t value,
return value;
}


static void _xccdf_policy_modify_selected_final(struct xccdf_policy *policy, const char *id, bool selected)
{
static bool TRUE0 = true;
static bool FALSE0 = false;
oscap_htable_detach(policy->selected_final, id);
oscap_htable_add(policy->selected_final, id, selected ? &TRUE0 : &FALSE0);
}

/**
* Resolve the xccdf item. Parameter selected indicates parents selection attribute
* It is used to decide the final selection attribute of item
Expand All @@ -337,15 +346,12 @@ static void xccdf_policy_resolve_item(struct xccdf_policy * policy, struct xccdf
__attribute__nonnull__(policy);
__attribute__nonnull__(item);

static bool TRUE0= true;
static bool FALSE0 = false;
bool result;

oscap_htable_detach(policy->selected_final, xccdf_item_get_id(item));
const char *id = xccdf_item_get_id(item);
if (!selected)
result = false;
else {
struct xccdf_select *sel = xccdf_policy_get_select_by_id(policy, xccdf_item_get_id(item));
struct xccdf_select *sel = xccdf_policy_get_select_by_id(policy, id);
result = (sel != NULL) ? xccdf_select_get_selected(sel) : xccdf_item_get_selected(item);
}

Expand All @@ -355,8 +361,7 @@ static void xccdf_policy_resolve_item(struct xccdf_policy * policy, struct xccdf
xccdf_policy_resolve_item(policy, xccdf_item_iterator_next(child_it), selected);
xccdf_item_iterator_free(child_it);
}

oscap_htable_add(policy->selected_final, xccdf_item_get_id(item), result ? &TRUE0 : &FALSE0);
_xccdf_policy_modify_selected_final(policy, id, result);
}

/**
Expand Down Expand Up @@ -695,6 +700,10 @@ static struct xccdf_rule_result * _xccdf_rule_result_new_from_rule(const struct
return rule_ritem;
}

static bool _user_specified_rule_mode(struct xccdf_policy *policy) {
return oscap_htable_itemcount(policy->rules) > 0;
}

static int _xccdf_policy_report_rule_result(struct xccdf_policy *policy,
struct xccdf_result *result,
const struct xccdf_rule *rule,
Expand All @@ -716,11 +725,12 @@ static int _xccdf_policy_report_rule_result(struct xccdf_policy *policy,
} else
xccdf_check_free(check);

/* If policy selects only one rule, skip reporting for the other
* unselected rules - only the selected rule will be reported. */
if (policy->rule != NULL) {
/* If at least one --rule option has been provided by the user on the
* command line skip reporting for the other rules - only the selected
* rule(s) will be reported. */
if (_user_specified_rule_mode(policy) > 0) {
const char* rule_id = xccdf_rule_get_id(rule);
if (strcmp(policy->rule, rule_id) != 0)
if (oscap_htable_get(policy->rules, rule_id) == NULL)
return ret;
}
ret = xccdf_policy_report_cb(policy, XCCDF_POLICY_OUTCB_END, (void *) rule_result);
Expand Down Expand Up @@ -1004,6 +1014,23 @@ static bool _xccdf_policy_item_has_all_requirements(struct xccdf_policy *policy,
return has_all_requirements;
}

static void _warn_about_required_rules(const struct xccdf_policy *policy, const struct xccdf_rule *rule)
{
struct oscap_stringlist_iterator *requires_it = xccdf_item_get_requires((struct xccdf_item *) rule);
while (oscap_stringlist_iterator_has_more(requires_it)) {
struct oscap_stringlist *requires_ids_list = oscap_stringlist_iterator_next(requires_it);
struct oscap_string_iterator *rule_requires_ids_it = oscap_stringlist_get_strings(requires_ids_list);
while (oscap_string_iterator_has_more(rule_requires_ids_it)) {
const char *requires_id = oscap_string_iterator_next(rule_requires_ids_it);
if (oscap_htable_get(policy->rules, requires_id) == NULL) {
dW("Rule '%s' requires rule '%s', but it hasn't been specified using the '--rule' option.", xccdf_rule_get_id(rule), requires_id);
}
}
oscap_string_iterator_free(rule_requires_ids_it);
}
oscap_stringlist_iterator_free(requires_it);
}

/**
* Evaluate given check which is immediate child of the rule.
* A possibe child checks will be evaluated by xccdf_policy_check_evaluate.
Expand All @@ -1018,23 +1045,33 @@ _xccdf_policy_rule_evaluate(struct xccdf_policy * policy, const struct xccdf_rul
const char *message = NULL;
int report = 0;

/* If policy selects only one rule and the rule currently being
* evaluated is not equal to the selected rule, do not evaluate it and
* mark it as notselected. */
if (policy->rule != NULL) {
if (strcmp(policy->rule, rule_id) != 0) {
/* If the rule is requested to be skipped by the user using --skip-rule on
* the command line we will skip the evaluation of this rule. */
if (oscap_htable_get(policy->skip_rules, rule_id) != NULL) {
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
}

/* If user wants to evaluate only specific rules and the rule currently
* being evaluated is not among these rules, do not evaluate it and mark it
* as notselected. */
if (_user_specified_rule_mode(policy) > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use oscap_htable_itemcount(policy->rules) > 0 here and get rid of that function.

if (oscap_htable_get(policy->rules, rule_id) == NULL) {
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
}
policy->rule_found = 1;
}
oscap_htable_add(policy->rules_found, rule_id, (void *)true);
_xccdf_policy_modify_selected_final(policy, rule_id, true);
_warn_about_required_rules(policy, rule);

if (!is_selected || !parent_selected)
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
} else {
/* solve selects only when in --rule mode */
if (!is_selected || !parent_selected)
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);

// See section 7.2.3.3.2 (<xccdf:requires> and <xccdf:conflicts> Elements) of the XCCDF specification.
if (_xccdf_policy_item_is_in_conflict(policy, XITEM(rule)) || !_xccdf_policy_item_has_all_requirements(policy, XITEM(rule))) {
xccdf_policy_resolve_item(policy, XITEM(rule), false);
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
// See section 7.2.3.3.2 (<xccdf:requires> and <xccdf:conflicts> Elements) of the XCCDF specification.
if (_xccdf_policy_item_is_in_conflict(policy, XITEM(rule)) || !_xccdf_policy_item_has_all_requirements(policy, XITEM(rule))) {
xccdf_policy_resolve_item(policy, XITEM(rule), false);
return _xccdf_policy_report_rule_result(policy, result, rule, NULL, XCCDF_RESULT_NOT_SELECTED, NULL);
}
}

/* Otherwise start reporting */
Expand Down Expand Up @@ -1833,6 +1870,9 @@ struct xccdf_policy * xccdf_policy_new(struct xccdf_policy_model * model, struct
memset(policy, 0, sizeof(struct xccdf_policy));

policy->profile = profile;
policy->rules = oscap_htable_new();
policy->rules_found = oscap_htable_new();
policy->skip_rules = oscap_htable_new();
policy->selects = oscap_list_new();
policy->values = oscap_list_new();
policy->results = oscap_list_new();
Expand Down Expand Up @@ -2101,11 +2141,16 @@ struct xccdf_result * xccdf_policy_evaluate(struct xccdf_policy * policy)
}
xccdf_item_iterator_free(item_it);

if (policy->rule != NULL && !policy->rule_found) {
oscap_seterr(OSCAP_EFAMILY_XCCDF,
"Rule '%s' not found in selected profile.", policy->rule);
return NULL;
struct oscap_htable_iterator *rit = oscap_htable_iterator_new(policy->rules);
while (oscap_htable_iterator_has_more(rit)) {
const char *rule_id = oscap_htable_iterator_next_key(rit);
if (oscap_htable_get(policy->rules_found, rule_id) == NULL) {
oscap_seterr(OSCAP_EFAMILY_XCCDF,
"Rule '%s' not found in selected profile.", rule_id);
return NULL;
}
}
oscap_htable_iterator_free(rit);

xccdf_policy_add_final_setvalues(policy, xccdf_benchmark_to_item(benchmark), result);

Expand Down Expand Up @@ -2223,6 +2268,9 @@ void xccdf_policy_free(struct xccdf_policy * policy) {
*/
xccdf_profile_free((struct xccdf_item *) policy->profile);

oscap_htable_free0(policy->rules);
oscap_htable_free0(policy->skip_rules);
oscap_htable_free0(policy->rules_found);
oscap_list_free(policy->selects, (oscap_destruct_func) xccdf_select_free);
oscap_list_free(policy->values, (oscap_destruct_func) xccdf_value_binding_free);
oscap_list_free(policy->results, (oscap_destruct_func) xccdf_result_free);
Expand Down
5 changes: 3 additions & 2 deletions src/XCCDF_POLICY/xccdf_policy_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ struct xccdf_policy {
struct xccdf_policy_model * model; ///< XCCDF Policy model
struct xccdf_profile * profile; ///< Profile structure (from benchmark)
/** A list of all selects. Either from profile or later added through API. */
const char *rule; ///< Single-rule feature: if not NULL, only this one rule will be selected.
int rule_found; ///< Single-rule feature: flag for rule - if rule is found it is set to 1 otherwise 0.
struct oscap_htable *rules;
struct oscap_htable *rules_found;
struct oscap_htable *skip_rules;
struct oscap_list * selects;
struct oscap_list * values; ///< Bound values of profile
struct oscap_list * results; ///< List of XCCDF results
Expand Down
5 changes: 5 additions & 0 deletions src/common/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,11 @@ void *oscap_htable_get(struct oscap_htable *htable, const char *key)
return htitem ? htitem->value : NULL;
}

size_t oscap_htable_itemcount(struct oscap_htable *htable)
{
return htable->itemcount;
}

void oscap_print_depth(int);

void oscap_htable_dump(struct oscap_htable *htable, oscap_dump_func dumper, int depth)
Expand Down
7 changes: 7 additions & 0 deletions src/common/list.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ bool oscap_htable_add(struct oscap_htable *htable, const char *key, void *item);
*/
void *oscap_htable_get(struct oscap_htable *htable, const char *key);

/**
* Get count of items in the hash table
* @param htable Hash table
* @return Count of items stored in the given hash table
*/
size_t oscap_htable_itemcount(struct oscap_htable *htable);

void *oscap_htable_detach(struct oscap_htable *htable, const char *key);

void oscap_htable_dump(struct oscap_htable *htable, oscap_dump_func dumper, int depth);
Expand Down
1 change: 1 addition & 0 deletions tests/API/XCCDF/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ add_oscap_test("test_fix_resultid_by_suffix.sh")
add_oscap_test("test_generate_fix_ansible_vars.sh")
add_oscap_test("test_xccdf_requires_conflicts.sh")
add_oscap_test("test_results_hostname.sh")
add_oscap_test("test_skip_rule.sh")
26 changes: 26 additions & 0 deletions tests/API/XCCDF/unittests/test_single_rule.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ name=$(basename $0 .sh)
prof1="xccdf_com.example.www_profile_test_single_rule"
prof2="xccdf_com.example.www_profile_test_single_rule_2"
rule_pass="xccdf_com.example.www_rule_test-pass"
# in profile $prof1 the $rule_fail passes because val1 is set to bar2
rule_fail="xccdf_com.example.www_rule_test-fail"
result=$(mktemp -t ${name}.out.XXXXXX)
stderr=$(mktemp -t ${name}.out.XXXXXX)
Expand Down Expand Up @@ -42,6 +43,19 @@ assert_exists 1 "//rule-result[@idref=\"$rule_pass\"]/result[text()=\"pass\"]"
assert_exists 1 "//rule-result[@idref=\"$rule_fail\"]/result[text()=\"notselected\"]"
:> $result

# Tests that multiple rules are evaluated when multiple --rule options are
# provided
$OSCAP xccdf eval --results $result --profile $prof1 \
--rule $rule_pass --rule $rule_fail \
$srcdir/${name}.xccdf.xml 2> $stderr
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr

$OSCAP xccdf validate --skip-schematron $result

assert_exists 1 "//rule-result[@idref=\"$rule_pass\"]/result[text()=\"pass\"]"
assert_exists 1 "//rule-result[@idref=\"$rule_fail\"]/result[text()=\"pass\"]"
:> $result

# Tests that only selected rule from XCCDF document is evaluated without
# specifying a profile name -- this is possible as both rules in the XCCDF
# document are selected by default.
Expand Down Expand Up @@ -108,6 +122,18 @@ assert_exists 1 "//rule-result[@idref=\"$rule_pass\"]/result[text()=\"pass\"]"
assert_exists 1 "//rule-result[@idref=\"$rule_fail\"]/result[text()=\"notselected\"]"
:> $result

# Tests that multiple rules are evaluated when multiple --rule options are
# provided
$OSCAP xccdf eval --results $result --profile $prof1 \
--rule $rule_pass --rule $rule_fail $srcdir/${name}.ds.xml 2> $stderr
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr

$OSCAP xccdf validate --skip-schematron $result

assert_exists 1 "//rule-result[@idref=\"$rule_pass\"]/result[text()=\"pass\"]"
assert_exists 1 "//rule-result[@idref=\"$rule_fail\"]/result[text()=\"pass\"]"
:> $result

# Tests that only selected passing rule from profile ${prof1}_customized from
# the tailoring file is evaluated (tailoring file was created using SCAP
# Workbench.
Expand Down
Loading