Skip to content

Commit

Permalink
Merge pull request #2166 from jan-cerny/bootc_sce
Browse files Browse the repository at this point in the history
Introduce bootc remediation type
  • Loading branch information
matusmarhefka authored Oct 22, 2024
2 parents 97d8831 + 4b6d0f2 commit 93e6ee3
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 3 deletions.
145 changes: 145 additions & 0 deletions src/XCCDF_POLICY/xccdf_policy_remediate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
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 @@ -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")
87 changes: 87 additions & 0 deletions tests/API/XCCDF/unittests/test_remediation_bootc.ds.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<ds:data-stream-collection xmlns:ds="http://scap.nist.gov/schema/scap/source/1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:cat="urn:oasis:names:tc:entity:xmlns:xml:catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="scap_org.openscap.www_collection_from_xccdf_test_single_rule.xccdf.xml" schematron-version="1.3" xsi:schemaLocation="http://scap.nist.gov/schema/scap/source/1.2 https://scap.nist.gov/schema/scap/1.3/scap-source-data-stream_1.3.xsd">
<ds:data-stream id="scap_org.openscap.www_datastream_simple" scap-version="1.3" use-case="OTHER">
<ds:checklists>
<ds:component-ref id="scap_org.openscap.www_cref_test_single_rule.xccdf.xml" xlink:href="#scap_org.openscap.www_comp_test_single_rule.xccdf.xml">
<cat:catalog>
<cat:uri name="test_single_rule.oval.xml" uri="#scap_org.openscap.www_cref_test_single_rule.oval.xml"/>
</cat:catalog>
</ds:component-ref>
</ds:checklists>
<ds:checks>
<ds:component-ref id="scap_org.openscap.www_cref_test_single_rule.oval.xml" xlink:href="#scap_org.openscap.www_comp_test_single_rule.oval.xml"/>
</ds:checks>
</ds:data-stream>
<ds:component id="scap_org.openscap.www_comp_test_single_rule.oval.xml" timestamp="2021-02-01T08:07:06+01:00">
<oval_definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5" xmlns:ind-def="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent" xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5" xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5" xmlns:win-def="http://oval.mitre.org/XMLSchema/oval-definitions-5#windows" xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#independent independent-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#windows windows-definitions-schema.xsd">
<generator>
<oval:schema_version>5.11.2</oval:schema_version>
<oval:timestamp>2021-02-01T08:07:06+01:00</oval:timestamp>
</generator>
<definitions>
<definition class="compliance" id="oval:org.openscap.www:def:1" version="1">
<metadata>
<title>PASS</title>
<description>pass</description>
</metadata>
<criteria>
<criterion comment="PASS test" test_ref="oval:org.openscap.www:tst:1"/>
</criteria>
</definition>
</definitions>
<tests>
<variable_test xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent" id="oval:org.openscap.www:tst:1" check="all" comment="always pass" version="1">
<object object_ref="oval:org.openscap.www:obj:1"/>
</variable_test>
</tests>
<objects>
<variable_object xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent" id="oval:org.openscap.www:obj:1" version="1" comment="x">
<var_ref>oval:org.openscap.www:var:1</var_ref>
</variable_object>
</objects>
<variables>
<constant_variable id="oval:org.openscap.www:var:1" version="1" comment="x" datatype="int">
<value>100</value>
</constant_variable>
</variables>
</oval_definitions>
</ds:component>
<ds:component id="scap_org.openscap.www_comp_test_single_rule.xccdf.xml" timestamp="2021-02-01T08:07:06+01:00">
<Benchmark xmlns="http://checklists.nist.gov/xccdf/1.2" id="xccdf_org.openscap.www_benchmark_test">
<status>accepted</status>
<version>1.0</version>
<Profile id="xccdf_org.openscap.www_profile_common">
<title>Common hardening profile</title>
<description>This is a very cool profile</description>
<select idref="xccdf_org.openscap.www_rule_1" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_2" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_3" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_4" selected="true"/>
</Profile>
<Rule selected="false" id="xccdf_org.openscap.www_rule_1">
<title>Rule 1: Install rsyslog package</title>
<fix system="urn:xccdf:fix:script:bootc">
dnf install rsyslog
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_2">
<title>Rule 2: Remove USBGuard</title>
<fix system="urn:xccdf:fix:script:bootc">
dnf remove usbguard
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_3">
<title>Rule 3: Install reboot package</title>
<fix system="urn:xccdf:fix:script:bootc">
dnf install reboot
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_4">
<title>Rule 4: Install podman package</title>
<fix system="urn:xccdf:fix:script:bootc">
dnf install podman
</fix>
</Rule>
</Benchmark>
</ds:component>
</ds:data-stream-collection>
19 changes: 19 additions & 0 deletions tests/API/XCCDF/unittests/test_remediation_bootc.sh
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
dnf -y install \
rsyslog \
reboot \
podman

dnf -y remove \
usbguard
10 changes: 8 additions & 2 deletions utils/oscap-xccdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ static struct oscap_module XCCDF_GEN_FIX = {
.help = GEN_OPTS
"\nFix Options:\n"
" --fix-type <type> - Fix type. Should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes,\n"
" blueprint (default: bash).\n"
" blueprint, bootc (default: bash).\n"
" --output <file> - Write the script into file.\n"
" --result-id <id> - Fixes will be generated for failed rule-results of the specified TestResult.\n"
" --template <id|filename> - Fix template. (default: bash)\n"
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion utils/oscap.8
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 93e6ee3

Please sign in to comment.