Skip to content

Commit 4342ab7

Browse files
committed
Add --list-rules option to oscap info
Add a new --list-rules option to the oscap info module that, when combined with --profile, prints the IDs of all XCCDF rules selected by the given profile. The output is machine-readable (one rule ID per line with no decoration), making it suitable for CI/CD automation, auditing, and tailoring validation workflows. Resolves: https://issues.redhat.com/browse/RHEL-143569
1 parent 5a8c7f1 commit 4342ab7

6 files changed

Lines changed: 110 additions & 9 deletions

File tree

docs/manual/manual.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,23 @@ description, use the `--profile` option followed by the profile ID.
217217
$ oscap info --profile xccdf_org.ssgproject.content_profile_ospp /usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml
218218
----
219219

220+
=== Listing rules selected by a profile
221+
222+
To list the IDs of all XCCDF rules that are selected by a given profile, use
223+
the `--list-rules` option together with `--profile`. The output contains one
224+
rule ID per line and is machine-readable, which makes it suitable for scripting,
225+
CI/CD pipelines, and tailoring validation workflows.
226+
227+
----
228+
$ oscap info --profile ospp --list-rules /usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml
229+
xccdf_org.ssgproject.content_rule_partition_for_tmp
230+
xccdf_org.ssgproject.content_rule_partition_for_var
231+
...
232+
----
233+
234+
The `--list-rules` option requires `--profile`. Running `--list-rules` without
235+
`--profile` will produce an error.
236+
220237
=== Displaying information about SCAP result data streams
221238

222239
The `oscap info` command is also helpful with other SCAP file types such as

tests/API/XCCDF/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,6 @@ add_oscap_test("test_skip_rule.sh")
113113
add_oscap_test("test_no_newline_between_select_elements.sh")
114114
add_oscap_test("test_single_line_tailoring.sh")
115115
add_oscap_test("test_reference.sh")
116+
add_oscap_test("test_list_rules.sh")
116117
add_oscap_test("test_remediation_bootc.sh")
117118
add_oscap_test("openscap_2289_regression.sh")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
. $builddir/tests/test_common.sh
3+
4+
set -e
5+
set -o pipefail
6+
7+
stderr=$(mktemp -t ${name}.err.XXXXXX)
8+
stdout=$(mktemp -t ${name}.out.XXXXXX)
9+
10+
ds="$srcdir/test_reference_ds.xml"
11+
p1="xccdf_com.example.www_profile_P1"
12+
13+
# Test 1: --list-rules with --profile prints selected rule IDs
14+
$OSCAP info --profile $p1 --list-rules $ds > $stdout 2> $stderr
15+
[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr
16+
grep -q "xccdf_com.example.www_rule_R1" $stdout
17+
grep -q "xccdf_com.example.www_rule_R2" $stdout
18+
grep -q "xccdf_com.example.www_rule_R3" $stdout
19+
grep -q "xccdf_com.example.www_rule_R4" $stdout
20+
# Verify output contains only rule IDs, one per line (4 rules = 4 lines)
21+
[ "$(wc -l < $stdout)" -eq 4 ]
22+
:> $stdout
23+
24+
# Test 2: --list-rules without --profile produces an error
25+
$OSCAP info --list-rules $ds > $stdout 2> $stderr && exit 1 || true
26+
grep -q "\-\-list-rules option requires \-\-profile" $stderr
27+
:> $stdout
28+
:> $stderr
29+
30+
rm -f $stdout $stderr

utils/oscap-info.c

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct oscap_module OSCAP_INFO_MODULE = {
6363
.usage = "some-file.xml",
6464
.help = "Options:\n"
6565
" --fetch-remote-resources - Download remote content referenced by data stream.\n"
66+
" --list-rules - Print selected rule IDs for the given profile (requires --profile).\n"
6667
" --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n"
6768
" --profile <id> - Show info of the profile with the given ID.\n"
6869
" --profiles - Show profiles from the input file in the <id>:<title> format, one line per profile.\n"
@@ -400,15 +401,36 @@ static const char *benchmark_get_profile_or_report_id_issues(struct xccdf_benchm
400401
return result;
401402
}
402403

403-
static int app_info_single_ds_one_profile(struct ds_stream_index_iterator* sds_it, struct ds_sds_session *session, const char *profile_suffix, const char *filename)
404+
static void _print_rules_for_profile(struct xccdf_benchmark *bench, const char *profile_id)
404405
{
406+
struct xccdf_policy_model *policy_model = xccdf_policy_model_new(bench);
407+
struct xccdf_policy *policy = xccdf_policy_model_get_policy_by_id(policy_model, profile_id);
408+
if (policy == NULL) {
409+
xccdf_policy_model_free(policy_model);
410+
return;
411+
}
412+
struct xccdf_select_iterator *sel_it = xccdf_policy_get_selected_rules(policy);
413+
while (xccdf_select_iterator_has_more(sel_it)) {
414+
struct xccdf_select *sel = xccdf_select_iterator_next(sel_it);
415+
printf("%s\n", xccdf_select_get_item(sel));
416+
}
417+
xccdf_select_iterator_free(sel_it);
418+
xccdf_policy_model_free(policy_model);
419+
}
420+
421+
static int app_info_single_ds_one_profile(struct ds_stream_index_iterator* sds_it, struct ds_sds_session *session, const struct oscap_action *action)
422+
{
423+
const char *profile_suffix = action->profile;
424+
const char *filename = action->file;
405425
const char *prefix = "";
406426
struct ds_stream_index * stream = ds_stream_index_iterator_next(sds_it);
407427
struct oscap_string_iterator* checklist_it = ds_stream_index_get_checklists(stream);
408428

409-
printf("\nStream: %s\n", ds_stream_index_get_id(stream));
410-
printf("Generated: %s\n", ds_stream_index_get_timestamp(stream));
411-
printf("Version: %s\n", ds_stream_index_get_version(stream));
429+
if (!action->list_rules) {
430+
printf("\nStream: %s\n", ds_stream_index_get_id(stream));
431+
printf("Generated: %s\n", ds_stream_index_get_timestamp(stream));
432+
printf("Version: %s\n", ds_stream_index_get_version(stream));
433+
}
412434
bool profile_not_found = true;
413435

414436
while (oscap_string_iterator_has_more(checklist_it) && profile_not_found) {
@@ -433,10 +455,17 @@ static int app_info_single_ds_one_profile(struct ds_stream_index_iterator* sds_i
433455
}
434456
const char *profile_id = benchmark_get_profile_or_report_multiple_ids(bench, profile_suffix, filename);
435457
if (profile_id != NULL) {
436-
_print_single_benchmark_one_profile(bench, profile_id);
458+
if (action->list_rules) {
459+
_print_rules_for_profile(bench, profile_id);
460+
// bench is freed by policy_model inside _print_rules_for_profile
461+
} else {
462+
_print_single_benchmark_one_profile(bench, profile_id);
463+
xccdf_benchmark_free(bench);
464+
}
437465
profile_not_found = false;
466+
} else {
467+
xccdf_benchmark_free(bench);
438468
}
439-
xccdf_benchmark_free(bench);
440469
} else if (oscap_source_get_scap_type(xccdf_source) == OSCAP_DOCUMENT_XCCDF_TAILORING) {
441470
struct xccdf_tailoring *tailoring = xccdf_tailoring_import_source(xccdf_source, NULL);
442471

@@ -509,9 +538,16 @@ static void app_info_single_benchmark(struct xccdf_benchmark *bench, const struc
509538
} else if (action->profile) {
510539
const char *profile_id = benchmark_get_profile_or_report_id_issues(bench, action->profile, action->file);
511540
if (profile_id != NULL) {
512-
_print_single_benchmark_one_profile(bench, profile_id);
541+
if (action->list_rules) {
542+
_print_rules_for_profile(bench, profile_id);
543+
// bench is freed by policy_model inside _print_rules_for_profile
544+
} else {
545+
_print_single_benchmark_one_profile(bench, profile_id);
546+
xccdf_benchmark_free(bench);
547+
}
548+
} else {
549+
xccdf_benchmark_free(bench);
513550
}
514-
xccdf_benchmark_free(bench);
515551
} else {
516552
printf("Checklist version: %s\n", oscap_source_get_schema_version(source));
517553
print_time(action->file);
@@ -527,7 +563,7 @@ static int app_info_single_ds(struct ds_stream_index_iterator* sds_it, struct ds
527563
if (action->show_profiles_only) {
528564
return_value = app_info_single_ds_profiles_only(sds_it, session, action);
529565
} else if (action->profile) {
530-
return_value = app_info_single_ds_one_profile(sds_it, session, action->profile, action->file);
566+
return_value = app_info_single_ds_one_profile(sds_it, session, action);
531567
} else {
532568
return_value = app_info_single_ds_all(sds_it, session, action);
533569
}
@@ -768,6 +804,7 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action)
768804

769805
enum oscap_info_opts {
770806
OSCAP_INFO_OPT_REMOTE_RESOURCES,
807+
OSCAP_INFO_OPT_LIST_RULES,
771808
OSCAP_INFO_OPT_LOCAL_FILES,
772809
OSCAP_INFO_OPT_PROFILE,
773810
OSCAP_INFO_OPT_PROFILES,
@@ -778,6 +815,7 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action)
778815
/* Command-options */
779816
const struct option long_options[] = {
780817
{"fetch-remote-resources", no_argument, &action->remote_resources, 1},
818+
{"list-rules", no_argument, 0, OSCAP_INFO_OPT_LIST_RULES},
781819
{"local-files", required_argument, NULL, OSCAP_INFO_OPT_LOCAL_FILES},
782820
{"profile", required_argument, 0, OSCAP_INFO_OPT_PROFILE},
783821
{"profiles", no_argument, 0, OSCAP_INFO_OPT_PROFILES},
@@ -792,6 +830,10 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action)
792830
while ((c = getopt_long(argc, argv, "", long_options, NULL)) != -1) {
793831
switch(c) {
794832
case 0: break;
833+
case OSCAP_INFO_OPT_LIST_RULES:
834+
action->list_rules = 1;
835+
action->provide_machine_readable_output = 1;
836+
break;
795837
case OSCAP_INFO_OPT_PROFILE:
796838
action->profile = optarg;
797839
break;
@@ -815,6 +857,11 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action)
815857
}
816858
}
817859

860+
if (action->list_rules && action->profile == NULL) {
861+
oscap_module_usage(action->module, stderr, "The --list-rules option requires --profile.\n");
862+
return false;
863+
}
864+
818865
if (optind >= argc) {
819866
oscap_module_usage(action->module, stderr, "SCAP file needs to be specified!\n");
820867
return false;

utils/oscap-tool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ struct oscap_action {
163163
int references;
164164
int raw;
165165
int show_rule_details;
166+
int list_rules;
166167
};
167168

168169
int app_xslt(const char *infile, const char *xsltfile, const char *outfile, const char **params);

utils/oscap.8

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ For XCCDF or SCAP source data stream files, the info module prints out IDs of in
6363
Allow download of remote components referenced from data stream.
6464
.RE
6565
.TP
66+
\fB\-\-list-rules\fR
67+
.RS
68+
Print IDs of XCCDF rules that are selected by the given profile, one per line. This option must be used together with \fB\-\-profile\fR. The output is machine-readable and contains only rule IDs with no additional decoration.
69+
.RE
70+
.TP
6671
\fB\-\-local-files DIRECTORY\fR
6772
.RS
6873
Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists.

0 commit comments

Comments
 (0)