Skip to content

Commit

Permalink
Introduce generating bootc remediation
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-cerny committed Oct 17, 2024
1 parent 97d8831 commit 7ba95a9
Show file tree
Hide file tree
Showing 3 changed files with 155 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 *package_install;
struct oscap_list *package_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_PACKAGE,
BOOTC_PACKAGE_INSTALL,
BOOTC_PACKAGE_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, "package")) {
state = BOOTC_PACKAGE;
} else {
ret = 1;
oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command: '%s'", word, line);
goto cleanup;
}
break;
case BOOTC_PACKAGE:
if (!strcmp(word, "install")) {
state = BOOTC_PACKAGE_INSTALL;
} else if (!strcmp(word, "remove")) {
state = BOOTC_PACKAGE_REMOVE;
} else {
ret = 1;
oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'package' command keyword '%s' in command:'%s'", word, line);
goto cleanup;
}
break;
case BOOTC_PACKAGE_INSTALL:
oscap_list_add(cmds->package_install, strdup(word));
break;
case BOOTC_PACKAGE_REMOVE:
oscap_list_add(cmds->package_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_packages(struct bootc_commands *cmds, int output_fd)
{
struct oscap_iterator *package_install_it = oscap_iterator_new(cmds->package_install);
if (oscap_iterator_has_more(package_install_it)) {
_write_text_to_fd(output_fd, "dnf -y install \\\n");
while (oscap_iterator_has_more(package_install_it)) {
char *package = (char *) oscap_iterator_next(package_install_it);
_write_text_to_fd(output_fd, " ");
_write_text_to_fd(output_fd, package);
if (oscap_iterator_has_more(package_install_it))
_write_text_to_fd(output_fd, " \\\n");
}
_write_text_to_fd(output_fd, "\n\n");
}
oscap_iterator_free(package_install_it);

struct oscap_iterator *package_remove_it = oscap_iterator_new(cmds->package_remove);
if (oscap_iterator_has_more(package_remove_it)) {
_write_text_to_fd(output_fd, "dnf -y remove \\\n");
while (oscap_iterator_has_more(package_remove_it)) {
char *package = (char *) oscap_iterator_next(package_remove_it);
_write_text_to_fd(output_fd, " ");
_write_text_to_fd(output_fd, package);
if (oscap_iterator_has_more(package_remove_it))
_write_text_to_fd(output_fd, " \\\n");
}
_write_text_to_fd(output_fd, "\n");
}
oscap_iterator_free(package_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 = {
.package_install = oscap_list_new(),
.package_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_packages(&cmds, output_fd);

oscap_list_free(cmds.package_install, free);
oscap_list_free(cmds.package_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
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 7ba95a9

Please sign in to comment.