diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c
index 8c2aaf98c9..70eb188838 100644
--- a/src/XCCDF_POLICY/xccdf_policy_remediate.c
+++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c
@@ -49,6 +49,11 @@
#include "public/xccdf_policy.h"
#include "oscap_helpers.h"
+struct bootc_commands {
+ struct oscap_list *dnf_install;
+ struct oscap_list *dnf_remove;
+};
+
static int _rule_add_info_message(struct xccdf_rule_result *rr, ...)
{
va_list ap;
@@ -1286,6 +1291,144 @@ static int _xccdf_policy_generate_fix_other(struct oscap_list *rules_to_fix, str
return ret;
}
+static int _parse_bootc_line(const char *line, struct bootc_commands *cmds)
+{
+ int ret = 0;
+ char *dup = strdup(line);
+ char **words = oscap_split(dup, " ");
+ enum states {
+ BOOTC_START,
+ BOOTC_DNF,
+ BOOTC_DNF_INSTALL,
+ BOOTC_DNF_REMOVE,
+ BOOTC_ERROR
+ };
+ int state = BOOTC_START;
+ for (unsigned int i = 0; words[i] != NULL; i++) {
+ char *word = oscap_trim(words[i]);
+ if (*word == '\0')
+ continue;
+ switch (state) {
+ case BOOTC_START:
+ if (!strcmp(word, "dnf")) {
+ state = BOOTC_DNF;
+ } else {
+ ret = 1;
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command: '%s'", word, line);
+ goto cleanup;
+ }
+ break;
+ case BOOTC_DNF:
+ if (!strcmp(word, "install")) {
+ state = BOOTC_DNF_INSTALL;
+ } else if (!strcmp(word, "remove")) {
+ state = BOOTC_DNF_REMOVE;
+ } else {
+ ret = 1;
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'dnf' command keyword '%s' in command:'%s'", word, line);
+ goto cleanup;
+ }
+ break;
+ case BOOTC_DNF_INSTALL:
+ oscap_list_add(cmds->dnf_install, strdup(word));
+ break;
+ case BOOTC_DNF_REMOVE:
+ oscap_list_add(cmds->dnf_remove, strdup(word));
+ break;
+ case BOOTC_ERROR:
+ ret = 1;
+ oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unexpected string '%s' in command: '%s'", word, line);
+ goto cleanup;
+ default:
+ break;
+ }
+ }
+
+cleanup:
+ free(words);
+ free(dup);
+ return ret;
+}
+
+static int _xccdf_policy_rule_generate_bootc_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct bootc_commands *cmds)
+{
+ char *fix_text = NULL;
+ int ret = _xccdf_policy_rule_get_fix_text(policy, rule, template, &fix_text);
+ if (fix_text == NULL) {
+ return ret;
+ }
+ char *dup = strdup(fix_text);
+ char **lines = oscap_split(dup, "\n");
+ for (unsigned int i = 0; lines[i] != NULL; i++) {
+ char *line = lines[i];
+ char *trim_line = oscap_trim(strdup(line));
+ if (*trim_line != '#' && *trim_line != '\0') {
+ _parse_bootc_line(trim_line, cmds);
+ }
+ free(trim_line);
+ }
+ free(lines);
+ free(dup);
+ free(fix_text);
+ return ret;
+}
+
+static int _generate_bootc_dnf(struct bootc_commands *cmds, int output_fd)
+{
+ struct oscap_iterator *dnf_install_it = oscap_iterator_new(cmds->dnf_install);
+ if (oscap_iterator_has_more(dnf_install_it)) {
+ _write_text_to_fd(output_fd, "dnf -y install \\\n");
+ while (oscap_iterator_has_more(dnf_install_it)) {
+ char *package = (char *) oscap_iterator_next(dnf_install_it);
+ _write_text_to_fd(output_fd, " ");
+ _write_text_to_fd(output_fd, package);
+ if (oscap_iterator_has_more(dnf_install_it))
+ _write_text_to_fd(output_fd, " \\\n");
+ }
+ _write_text_to_fd(output_fd, "\n\n");
+ }
+ oscap_iterator_free(dnf_install_it);
+
+ struct oscap_iterator *dnf_remove_it = oscap_iterator_new(cmds->dnf_remove);
+ if (oscap_iterator_has_more(dnf_remove_it)) {
+ _write_text_to_fd(output_fd, "dnf -y remove \\\n");
+ while (oscap_iterator_has_more(dnf_remove_it)) {
+ char *package = (char *) oscap_iterator_next(dnf_remove_it);
+ _write_text_to_fd(output_fd, " ");
+ _write_text_to_fd(output_fd, package);
+ if (oscap_iterator_has_more(dnf_remove_it))
+ _write_text_to_fd(output_fd, " \\\n");
+ }
+ _write_text_to_fd(output_fd, "\n");
+ }
+ oscap_iterator_free(dnf_remove_it);
+ return 0;
+}
+
+static int _xccdf_policy_generate_fix_bootc(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd)
+{
+ struct bootc_commands cmds = {
+ .dnf_install = oscap_list_new(),
+ .dnf_remove = oscap_list_new(),
+ };
+ int ret = 0;
+ struct oscap_iterator *rules_to_fix_it = oscap_iterator_new(rules_to_fix);
+ while (oscap_iterator_has_more(rules_to_fix_it)) {
+ struct xccdf_rule *rule = (struct xccdf_rule *) oscap_iterator_next(rules_to_fix_it);
+ ret = _xccdf_policy_rule_generate_bootc_fix(policy, rule, sys, &cmds);
+ if (ret != 0)
+ break;
+ }
+ oscap_iterator_free(rules_to_fix_it);
+
+ _write_text_to_fd(output_fd, "#!/bin/bash\n");
+ _generate_bootc_dnf(&cmds, output_fd);
+
+ oscap_list_free(cmds.dnf_install, free);
+ oscap_list_free(cmds.dnf_remove, free);
+ return ret;
+}
+
int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd)
{
__attribute__nonnull__(policy);
@@ -1342,6 +1485,8 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *
ret = _xccdf_policy_generate_fix_ansible(rules_to_fix, policy, sys, output_fd);
} else if (strcmp(sys, "urn:redhat:osbuild:blueprint") == 0) {
ret = _xccdf_policy_generate_fix_blueprint(rules_to_fix, policy, sys, output_fd);
+ } else if (strcmp(sys, "urn:xccdf:fix:script:bootc") == 0) {
+ ret = _xccdf_policy_generate_fix_bootc(rules_to_fix, policy, sys, output_fd);
} else {
ret = _xccdf_policy_generate_fix_other(rules_to_fix, policy, sys, output_fd);
}
diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt
index 7a9b3b452c..164b795e0e 100644
--- a/tests/API/XCCDF/unittests/CMakeLists.txt
+++ b/tests/API/XCCDF/unittests/CMakeLists.txt
@@ -110,3 +110,4 @@ 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")
+add_oscap_test("test_remediation_bootc.sh")
diff --git a/tests/API/XCCDF/unittests/test_remediation_bootc.ds.xml b/tests/API/XCCDF/unittests/test_remediation_bootc.ds.xml
new file mode 100644
index 0000000000..6134381d4c
--- /dev/null
+++ b/tests/API/XCCDF/unittests/test_remediation_bootc.ds.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5.11.2
+ 2021-02-01T08:07:06+01:00
+
+
+
+
+ PASS
+ pass
+
+
+
+
+
+
+
+
+
+
+
+
+
+ oval:org.openscap.www:var:1
+
+
+
+
+ 100
+
+
+
+
+
+
+ accepted
+ 1.0
+
+ Common hardening profile
+ This is a very cool profile
+
+
+
+
+
+
+ Rule 1: Install rsyslog package
+
+ dnf install rsyslog
+
+
+
+ Rule 2: Remove USBGuard
+
+ dnf remove usbguard
+
+
+
+ Rule 3: Install reboot package
+
+ dnf install reboot
+
+
+
+ Rule 4: Install podman package
+
+ dnf install podman
+
+
+
+
+
diff --git a/tests/API/XCCDF/unittests/test_remediation_bootc.sh b/tests/API/XCCDF/unittests/test_remediation_bootc.sh
new file mode 100755
index 0000000000..cf193e75a5
--- /dev/null
+++ b/tests/API/XCCDF/unittests/test_remediation_bootc.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+. $builddir/tests/test_common.sh
+
+set -e
+set -o pipefail
+
+name=$(basename $0 .sh)
+result=$(mktemp)
+stderr=$(mktemp)
+
+echo "Result file = $result"
+echo "Stderr file = $stderr"
+
+$OSCAP xccdf generate fix --fix-type bootc --profile common "$srcdir/test_remediation_bootc.ds.xml" > "$result" 2> "$stderr"
+[ -e $stderr ]
+
+diff -u "$srcdir/test_remediation_bootc_expected_output.sh" "$result"
+
+rm -rf "$stdout" "$stderr" "$result"
diff --git a/tests/API/XCCDF/unittests/test_remediation_bootc_expected_output.sh b/tests/API/XCCDF/unittests/test_remediation_bootc_expected_output.sh
new file mode 100644
index 0000000000..57a7ffb3df
--- /dev/null
+++ b/tests/API/XCCDF/unittests/test_remediation_bootc_expected_output.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+dnf -y install \
+ rsyslog \
+ reboot \
+ podman
+
+dnf -y remove \
+ usbguard
diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c
index 2bcdac2e1c..54680b3595 100644
--- a/utils/oscap-xccdf.c
+++ b/utils/oscap-xccdf.c
@@ -285,7 +285,7 @@ static struct oscap_module XCCDF_GEN_FIX = {
.help = GEN_OPTS
"\nFix Options:\n"
" --fix-type - Fix type. Should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes,\n"
- " blueprint (default: bash).\n"
+ " blueprint, bootc (default: bash).\n"
" --output - Write the script into file.\n"
" --result-id - Fixes will be generated for failed rule-results of the specified TestResult.\n"
" --template - Fix template. (default: bash)\n"
@@ -971,10 +971,12 @@ int app_generate_fix(const struct oscap_action *action)
template = "urn:xccdf:fix:script:kubernetes";
} else if (strcmp(action->fix_type, "blueprint") == 0) {
template = "urn:redhat:osbuild:blueprint";
+ } else if (strcmp(action->fix_type, "bootc") == 0) {
+ template = "urn:xccdf:fix:script:bootc";
} else {
fprintf(stderr,
"Unknown fix type '%s'.\n"
- "Please provide one of: bash, ansible, puppet, anaconda, ignition, kubernetes, blueprint.\n"
+ "Please provide one of: bash, ansible, puppet, anaconda, ignition, kubernetes, blueprint, bootc.\n"
"Or provide a custom template using '--template' instead.\n",
action->fix_type);
return OSCAP_ERROR;
@@ -984,6 +986,10 @@ int app_generate_fix(const struct oscap_action *action)
} else {
template = "urn:xccdf:fix:script:sh";
}
+ if (action->id != NULL && action->fix_type != NULL && !strcmp(action->fix_type, "bootc")) {
+ fprintf(stderr, "It isn't possible to generate results-oriented bootc remediations.\n");
+ return OSCAP_ERROR;
+ }
int ret = OSCAP_ERROR;
struct oscap_source *source = oscap_source_new_from_file(action->f_xccdf);
diff --git a/utils/oscap.8 b/utils/oscap.8
index 09da46d008..23c6c80cca 100644
--- a/utils/oscap.8
+++ b/utils/oscap.8
@@ -430,11 +430,12 @@ To use the ability to include additional information from SCE in XCCDF result fi
Generate a script that shall bring the system to a state of compliance with given XCCDF Benchmark. There are 2 possibilities when generating fixes: Result-oriented fixes (--result-id) or Profile-oriented fixes (--profile). Result-oriented takes precedences over Profile-oriented, if result-id is given, oscap will ignore any profile provided.
.TP
Result-oriented fixes are generated using result-id provided to select only the failing rules from results in xccdf-file, it skips all other rules.
+It isn't possible to generate result-oriented fixes for the bootc fix type.
.TP
Profile-oriented fixes are generated using all rules within the provided profile. If no result-id/profile are provided, (default) profile will be used to generate fixes.
.TP
\fB\-\-fix-type TYPE\fR
-Specify fix type. There are multiple programming languages in which the fix script can be generated. TYPE should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes, blueprint. Default is bash. This option is mutually exclusive with --template, because fix type already determines the template URN.
+Specify fix type. There are multiple programming languages in which the fix script can be generated. TYPE should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes, blueprint, bootc. Default is bash. This option is mutually exclusive with --template, because fix type already determines the template URN.
.TP
\fB\-\-output FILE\fR
Write the report to this file instead of standard output.