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

Introduce bootc remediation type #2166

Merged
merged 3 commits into from
Oct 22, 2024
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
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
Loading