From 7ba95a91dc1123f09ebcd0f7db2bfddfb0f12074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 11 Oct 2024 17:07:06 +0200 Subject: [PATCH] Introduce generating bootc remediation --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 145 ++++++++++++++++++++++ utils/oscap-xccdf.c | 10 +- utils/oscap.8 | 3 +- 3 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 8c2aaf98c9..592114a8e3 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 *package_install; + struct oscap_list *package_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_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); @@ -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/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.