diff --git a/.gitignore b/.gitignore index 0223773..33a3f05 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/openscap-1.3.5.tar.gz +SOURCES/openscap-1.3.6.tar.gz diff --git a/.openscap.metadata b/.openscap.metadata index 38345b6..5048b38 100644 --- a/.openscap.metadata +++ b/.openscap.metadata @@ -1 +1 @@ -77494383980082f8bc625a6e196a6760d30a5107 SOURCES/openscap-1.3.5.tar.gz +8c1b41bb7c32c22d541a6881ab8c5b8bef06890f SOURCES/openscap-1.3.6.tar.gz diff --git a/SOURCES/openscap-1.3.6-alternative-hostname-pr-1806.patch b/SOURCES/openscap-1.3.6-alternative-hostname-pr-1806.patch deleted file mode 100644 index 1d6b269..0000000 --- a/SOURCES/openscap-1.3.6-alternative-hostname-pr-1806.patch +++ /dev/null @@ -1,72 +0,0 @@ -From d97687c12ba6cbca1d732534ff7394bd14547d92 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= -Date: Fri, 10 Sep 2021 14:53:42 +0200 -Subject: [PATCH] Add an alternative source of hostname - -If /etc/hostname can't be read, we will try to open /proc/sys/kernel/hostname instead. - -Resolves: rhbz#1977668 ---- - src/XCCDF/result.c | 5 ++++ - tests/API/XCCDF/unittests/CMakeLists.txt | 1 + - .../XCCDF/unittests/test_results_hostname.sh | 26 +++++++++++++++++++ - 3 files changed, 32 insertions(+) - create mode 100755 tests/API/XCCDF/unittests/test_results_hostname.sh - -diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c -index 91fcc6041d..c0ad4b926f 100644 ---- a/src/XCCDF/result.c -+++ b/src/XCCDF/result.c -@@ -271,6 +271,11 @@ static char *_get_etc_hostname(const char *oscap_probe_root) - - fp = oscap_fopen_with_prefix(oscap_probe_root, "/etc/hostname"); - -+ if (fp == NULL) { -+ dD("Trying to use /proc/sys/kernel/hostname instead of /etc/hostname"); -+ fp = oscap_fopen_with_prefix(oscap_probe_root, "/proc/sys/kernel/hostname"); -+ } -+ - if (fp == NULL) - goto fail; - -diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt -index 52645834c4..6549538440 100644 ---- a/tests/API/XCCDF/unittests/CMakeLists.txt -+++ b/tests/API/XCCDF/unittests/CMakeLists.txt -@@ -101,3 +101,4 @@ add_oscap_test("test_fix_arf.sh") - add_oscap_test("test_fix_resultid_by_suffix.sh") - add_oscap_test("test_generate_fix_ansible_vars.sh") - add_oscap_test("test_xccdf_requires_conflicts.sh") -+add_oscap_test("test_results_hostname.sh") -diff --git a/tests/API/XCCDF/unittests/test_results_hostname.sh b/tests/API/XCCDF/unittests/test_results_hostname.sh -new file mode 100755 -index 0000000000..c4408affbb ---- /dev/null -+++ b/tests/API/XCCDF/unittests/test_results_hostname.sh -@@ -0,0 +1,26 @@ -+#!/usr/bin/env bash -+. $builddir/tests/test_common.sh -+ -+set -e -+set -o pipefail -+ -+result=$(mktemp) -+tmpdir=$(mktemp -d) -+ -+export OSCAP_PROBE_ROOT="$tmpdir" -+ -+mkdir -p "$tmpdir/etc" -+echo "hostname_defined_in_etc_hostname" > "$tmpdir/etc/hostname" -+$OSCAP xccdf eval --results "$result" "$srcdir/test_single_rule.ds.xml" || ret=$? -+assert_exists 1 '/Benchmark/TestResult/target[text()="hostname_defined_in_etc_hostname"]' -+assert_exists 0 '/Benchmark/TestResult/target[text()="hostname_defined_in_proc_sys_kernel"]' -+ -+rm -rf "$tmpdir/etc/hostname" -+mkdir -p "$tmpdir/proc/sys/kernel/" -+echo "hostname_defined_in_proc_sys_kernel" > "$tmpdir/proc/sys/kernel/hostname" -+$OSCAP xccdf eval --results "$result" "$srcdir/test_single_rule.ds.xml" || ret=$? -+assert_exists 0 '/Benchmark/TestResult/target[text()="hostname_defined_in_etc_hostname"]' -+assert_exists 1 '/Benchmark/TestResult/target[text()="hostname_defined_in_proc_sys_kernel"]' -+ -+rm -f "$result" -+rm -rf "$tmpdir" diff --git a/SOURCES/openscap-1.3.6-blueprint-fix-pr-1749.patch b/SOURCES/openscap-1.3.6-blueprint-fix-pr-1749.patch deleted file mode 100644 index 0e44989..0000000 --- a/SOURCES/openscap-1.3.6-blueprint-fix-pr-1749.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 5f0a9033b466d929613a2a55a1524ec75c09b5b0 Mon Sep 17 00:00:00 2001 -From: Evgeny Kolesnikov -Date: Thu, 6 May 2021 08:14:12 +0200 -Subject: [PATCH] Introduce OSBuild Blueprint fix type - ---- - utils/oscap-xccdf.c | 7 +++++-- - utils/oscap.8 | 2 +- - xsl/xccdf-share.xsl | 1 + - 3 files changed, 7 insertions(+), 3 deletions(-) - -diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c -index 95c1c7658d..801e54fa35 100644 ---- a/utils/oscap-xccdf.c -+++ b/utils/oscap-xccdf.c -@@ -275,7 +275,8 @@ static struct oscap_module XCCDF_GEN_FIX = { - .usage = "[options] xccdf-file.xml", - .help = GEN_OPTS - "\nFix Options:\n" -- " --fix-type - Fix type. Should be one of: bash, ansible, puppet, anaconda (default: bash).\n" -+ " --fix-type - Fix type. Should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes,\n" -+ " blueprint (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" -@@ -887,10 +888,12 @@ int app_generate_fix(const struct oscap_action *action) - template = "urn:xccdf:fix:script:ignition"; - } else if (strcmp(action->fix_type, "kubernetes") == 0) { - template = "urn:xccdf:fix:script:kubernetes"; -+ } else if (strcmp(action->fix_type, "blueprint") == 0) { -+ template = "urn:redhat:osbuild:blueprint"; - } else { - fprintf(stderr, - "Unknown fix type '%s'.\n" -- "Please provide one of: bash, ansible, puppet, anaconda, ignition, kubernetes.\n" -+ "Please provide one of: bash, ansible, puppet, anaconda, ignition, kubernetes, blueprint.\n" - "Or provide a custom template using '--template' instead.\n", - action->fix_type); - return OSCAP_ERROR; -diff --git a/utils/oscap.8 b/utils/oscap.8 -index 240b829d7b..6cae0ffe8a 100644 ---- a/utils/oscap.8 -+++ b/utils/oscap.8 -@@ -395,7 +395,7 @@ Result-oriented fixes are generated using result-id provided to select only the - 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. 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. 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. -diff --git a/xsl/xccdf-share.xsl b/xsl/xccdf-share.xsl -index 9f8e587676..d7a9f3b7e2 100644 ---- a/xsl/xccdf-share.xsl -+++ b/xsl/xccdf-share.xsl -@@ -295,6 +295,7 @@ Authors: - Puppet snippet - Anaconda snippet - Kubernetes snippet -+ OSBuild Blueprint snippet - script - - diff --git a/SOURCES/openscap-1.3.6-blueprint-toml-pr-1810.patch b/SOURCES/openscap-1.3.6-blueprint-toml-pr-1810.patch deleted file mode 100644 index ec31a3d..0000000 --- a/SOURCES/openscap-1.3.6-blueprint-toml-pr-1810.patch +++ /dev/null @@ -1,583 +0,0 @@ -From b0b7626dca08acd4563ae42c1c27ccc0777b5357 Mon Sep 17 00:00:00 2001 -From: Evgeny Kolesnikov -Date: Thu, 23 Sep 2021 00:58:29 +0200 -Subject: [PATCH] Add proper Blueprint's remediation snippets handling for - generation of the final TOML document. - -As the final Blueprint could not be created by just gluing up all -the snippets together we have to get a bit more creative. ---- - docs/manual/manual.adoc | 15 ++ - src/XCCDF_POLICY/xccdf_policy_remediate.c | 216 ++++++++++++++++-- - src/common/list.c | 19 ++ - src/common/list.h | 1 + - tests/API/XCCDF/unittests/CMakeLists.txt | 1 + - .../unittests/test_remediation_blueprint.sh | 27 +++ - .../unittests/test_remediation_blueprint.toml | 45 ++++ - .../test_remediation_blueprint.xccdf.xml | 102 +++++++++ - 8 files changed, 405 insertions(+), 21 deletions(-) - create mode 100755 tests/API/XCCDF/unittests/test_remediation_blueprint.sh - create mode 100644 tests/API/XCCDF/unittests/test_remediation_blueprint.toml - create mode 100644 tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml - -diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc -index e8664eb920..90e2cc2c63 100644 ---- a/docs/manual/manual.adoc -+++ b/docs/manual/manual.adoc -@@ -1084,6 +1084,21 @@ scanned during this command. If you want to generate remediation only for the - failed rules based on scan results, refer to <<_reviewing_remediations,Reviewing - remediations>>. - -+=== Generating Image Builder Blueprints -+ -+OpenSCAP can also create a remediation in form of Image Builder (OSBuild) Blueprint. This remeditaion -+is intendeded to be used as a bootstrap for image creation and usually it will contain only essential -+elements of the configuration, elements that would be hard or impossible to change after the image -+is created, like partitioning or set of installed packages. -+ -+It is recommended to combine this type of remediation with other types, executed on the running system. -+ -+For example, to generate a blueprint remediation for RHEL 8 OSPP profile, run: -+ -+---- -+$ oscap xccdf generate fix --profile ospp --fix-type blueprint /usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml > blueprint.toml -+---- -+ - == Details on SCAP conformance - - === Check Engines -diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c -index 0b3a037a5f..6033c3b54b 100644 ---- a/src/XCCDF_POLICY/xccdf_policy_remediate.c -+++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c -@@ -656,6 +656,78 @@ static int _write_fix_missing_warning_to_fd(const char *sys, int output_fd, stru - } - } - -+struct blueprint_entries { -+ const char *pattern; -+ struct oscap_list *list; -+ pcre *re; -+}; -+ -+static inline int _parse_blueprint_fix(const char *fix_text, struct oscap_list *generic, struct oscap_list *services_enable, struct oscap_list *services_disable, struct oscap_list *kernel_append) -+{ -+ const char *err; -+ int errofs; -+ int ret = 0; -+ -+ struct blueprint_entries tab[] = { -+ {"\\[customizations\\.services\\]\\s+enabled[=\\s]+\\[([^\\]]+)\\]\\s+", services_enable, NULL}, -+ {"\\[customizations\\.services\\]\\s+disabled[=\\s]+\\[([^\\]]+)\\]\\s+", services_disable, NULL}, -+ {"\\[customizations\\.kernel\\]\\s+append[=\\s\"]+([^\"]+)[\\s\"]+", kernel_append, NULL}, -+ // We do this only to pop the 'distro' entry to the top of the generic list, -+ // effectively placing it to the root of the TOML document. -+ {"\\s+(distro[=\\s\"]+[^\"]+[\\s\"]+)", generic, NULL}, -+ {NULL, NULL, NULL} -+ }; -+ -+ for (int i = 0; tab[i].pattern != NULL; i++) { -+ tab[i].re = pcre_compile(tab[i].pattern, PCRE_UTF8, &err, &errofs, NULL); -+ if (tab[i].re == NULL) { -+ dE("Unable to compile /%s/ regex pattern, pcre_compile() returned error (offset: %d): '%s'.\n", tab[i].pattern, errofs, err); -+ ret = 1; -+ goto exit; -+ } -+ } -+ -+ const size_t fix_text_len = strlen(fix_text); -+ size_t start_offset = 0; -+ int ovector[6] = {0}; -+ -+ for (int i = 0; tab[i].pattern != NULL; i++) { -+ while (true) { -+ const int match = pcre_exec(tab[i].re, NULL, fix_text, fix_text_len, start_offset, -+ 0, ovector, sizeof(ovector) / sizeof(ovector[0])); -+ if (match == -1) -+ break; -+ -+ if (match != 2) { -+ dE("Expected 1 capture group matches per entry. Found %i!", match - 1); -+ ret = 1; -+ goto exit; -+ } -+ -+ char *val = malloc((ovector[3] - ovector[2] + 1) * sizeof(char)); -+ memcpy(val, &fix_text[ovector[2]], ovector[3] - ovector[2]); -+ val[ovector[3] - ovector[2]] = '\0'; -+ -+ if (!oscap_list_contains(kernel_append, val, (oscap_cmp_func) oscap_streq)) { -+ oscap_list_prepend(tab[i].list, val); -+ } else { -+ free(val); -+ } -+ -+ start_offset = ovector[1]; -+ } -+ } -+ -+ if (start_offset < fix_text_len-1) { -+ oscap_list_add(generic, strdup(fix_text + start_offset)); -+ } -+ -+exit: -+ for (int i = 0; tab[i].pattern != NULL; i++) -+ pcre_free(tab[i].re); -+ -+ return ret; -+} - - static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *variables, struct oscap_list *tasks) - { -@@ -793,6 +865,18 @@ static int _xccdf_policy_rule_generate_fix(struct xccdf_policy *policy, struct x - return ret; - } - -+static int _xccdf_policy_rule_generate_blueprint_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct oscap_list *generic, struct oscap_list *services_enable, struct oscap_list *services_disable, struct oscap_list *kernel_append) -+{ -+ char *fix_text = NULL; -+ int ret = _xccdf_policy_rule_get_fix_text(policy, rule, template, &fix_text); -+ if (fix_text == NULL) { -+ return ret; -+ } -+ ret = _parse_blueprint_fix(fix_text, generic, services_enable, services_disable, kernel_append); -+ free(fix_text); -+ return ret; -+} -+ - static int _xccdf_policy_rule_generate_ansible_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct oscap_list *variables, struct oscap_list *tasks) - { - char *fix_text = NULL; -@@ -914,25 +998,45 @@ static char *_comment_multiline_text(char *text) - static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd) - { - if (!(oscap_streq(sys, "") || oscap_streq(sys, "urn:xccdf:fix:script:sh") || oscap_streq(sys, "urn:xccdf:fix:commands") || -- oscap_streq(sys, "urn:xccdf:fix:script:ansible"))) -+ oscap_streq(sys, "urn:xccdf:fix:script:ansible") || oscap_streq(sys, "urn:redhat:osbuild:blueprint"))) - return 0; // no header required - -- const bool ansible_script = strcmp(sys, "urn:xccdf:fix:script:ansible") == 0; -- const char *how_to_apply = ansible_script ? -- "# $ ansible-playbook -i \"localhost,\" -c local playbook.yml\n" -- "# $ ansible-playbook -i \"192.168.1.155,\" playbook.yml\n" -- "# $ ansible-playbook -i inventory.ini playbook.yml" : -- "# $ sudo ./remediation-script.sh"; - const char *oscap_version = oscap_get_version(); -- const char *format = ansible_script ? "ansible" : "bash"; -- const char *remediation_type = ansible_script ? "Ansible Playbook" : "Bash Remediation Script"; -- const char *shebang_with_newline = ansible_script ? "" : "#!/usr/bin/env bash\n"; -+ char *how_to_apply = ""; -+ char *format = (char *)sys; -+ char *remediation_type = "Unknown"; -+ char *shebang_with_newline = ""; -+ -+ if (oscap_streq(sys, "urn:xccdf:fix:script:ansible")) { -+ how_to_apply = "# $ ansible-playbook -i \"localhost,\" -c local playbook.yml\n" -+ "# $ ansible-playbook -i \"192.168.1.155,\" playbook.yml\n" -+ "# $ ansible-playbook -i inventory.ini playbook.yml"; -+ format = "ansible"; -+ remediation_type = "Ansible Playbook"; -+ } -+ -+ if (oscap_streq(sys, "urn:redhat:osbuild:blueprint")) { -+ how_to_apply = "# composer-cli blueprints push blueprint.toml"; -+ format = "blueprint"; -+ remediation_type = "Blueprint"; -+ } -+ -+ if (oscap_streq(sys, "") || oscap_streq(sys, "urn:xccdf:fix:script:sh") || oscap_streq(sys, "urn:xccdf:fix:commands")) { -+ how_to_apply = "# $ sudo ./remediation-script.sh"; -+ format = "bash"; -+ remediation_type = "Bash Remediation Script"; -+ shebang_with_newline = "#!/usr/bin/env bash\n"; -+ } - - char *fix_header; - - struct xccdf_profile *profile = xccdf_policy_get_profile(policy); - const char *profile_id = xccdf_profile_get_id(profile); - -+ struct xccdf_benchmark *benchmark = xccdf_policy_get_benchmark(policy); -+ const char *benchmark_version_info = benchmark ? xccdf_benchmark_get_version(benchmark) : "Unknown"; -+ const char *benchmark_id = benchmark ? xccdf_benchmark_get_id(benchmark) : "Unknown"; -+ - // Title - struct oscap_text_iterator *title_iterator = xccdf_profile_get_title(profile); - char *raw_profile_title = oscap_textlist_get_preferred_plaintext(title_iterator, NULL); -@@ -942,11 +1046,6 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ - - if (result == NULL) { - // Profile-based remediation fix -- struct xccdf_benchmark *benchmark = xccdf_policy_get_benchmark(policy); -- if (benchmark == NULL) { -- free(profile_title); -- return 1; -- } - // Description - struct oscap_text_iterator *description_iterator = xccdf_profile_get_description(profile); - char *profile_description = description_iterator != NULL ? -@@ -955,10 +1054,8 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ - char *commented_profile_description = _comment_multiline_text(profile_description); - free(profile_description); - -- const char *benchmark_version_info = xccdf_benchmark_get_version(benchmark); -- const char *benchmark_id = xccdf_benchmark_get_id(benchmark); -- const struct xccdf_version_info *xccdf_version = xccdf_benchmark_get_schema_version(benchmark); -- const char *xccdf_version_name = xccdf_version_info_get_version(xccdf_version); -+ const struct xccdf_version_info *xccdf_version = benchmark ? xccdf_benchmark_get_schema_version(benchmark) : NULL; -+ const char *xccdf_version_name = xccdf_version ? xccdf_version_info_get_version(xccdf_version) : "Unknown"; - - fix_header = oscap_sprintf( - "%s" -@@ -1026,9 +1123,8 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ - result_id, format, remediation_type, remediation_type, how_to_apply - ); - } -- free(profile_title); - -- if (ansible_script) { -+ if (oscap_streq(sys, "urn:xccdf:fix:script:ansible")) { - char *ansible_fix_header = oscap_sprintf( - "---\n" - "%s\n" -@@ -1036,9 +1132,85 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ - fix_header); - free(fix_header); - return _write_text_to_fd_and_free(output_fd, ansible_fix_header); -+ } else if (oscap_streq(sys, "urn:redhat:osbuild:blueprint")) { -+ char *blueprint_fix_header = oscap_sprintf( -+ "%s" -+ "name = \"%s\"\n" -+ "description = \"%s\"\n" -+ "version = \"%s\"\n", -+ fix_header, profile_id, profile_title, benchmark_version_info); -+ free(fix_header); -+ return _write_text_to_fd_and_free(output_fd, blueprint_fix_header); - } else { - return _write_text_to_fd_and_free(output_fd, fix_header); - } -+ -+ free(profile_title); -+} -+ -+static int _xccdf_policy_generate_fix_blueprint(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) -+{ -+ int ret = 0; -+ struct oscap_list *generic = oscap_list_new(); -+ struct oscap_list *services_enable = oscap_list_new(); -+ struct oscap_list *services_disable = oscap_list_new(); -+ struct oscap_list *kernel_append = oscap_list_new(); -+ 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_blueprint_fix(policy, rule, sys, generic, services_enable, services_disable, kernel_append); -+ if (ret != 0) -+ break; -+ } -+ oscap_iterator_free(rules_to_fix_it); -+ -+ struct oscap_iterator *generic_it = oscap_iterator_new(generic); -+ while(oscap_iterator_has_more(generic_it)) { -+ char *var_line = (char *) oscap_iterator_next(generic_it); -+ _write_text_to_fd(output_fd, var_line); -+ } -+ _write_text_to_fd(output_fd, "\n"); -+ oscap_iterator_free(generic_it); -+ oscap_list_free(generic, free); -+ -+ _write_text_to_fd(output_fd, "[customizations.kernel]\nappend = \""); -+ struct oscap_iterator *kernel_append_it = oscap_iterator_new(kernel_append); -+ while(oscap_iterator_has_more(kernel_append_it)) { -+ char *var_line = (char *) oscap_iterator_next(kernel_append_it); -+ _write_text_to_fd(output_fd, var_line); -+ if (oscap_iterator_has_more(kernel_append_it)) -+ _write_text_to_fd(output_fd, " "); -+ } -+ _write_text_to_fd(output_fd, "\"\n\n"); -+ oscap_iterator_free(kernel_append_it); -+ oscap_list_free(kernel_append, free); -+ -+ _write_text_to_fd(output_fd, "[customizations.services]\n"); -+ _write_text_to_fd(output_fd, "enabled = ["); -+ struct oscap_iterator *services_enable_it = oscap_iterator_new(services_enable); -+ while(oscap_iterator_has_more(services_enable_it)) { -+ char *var_line = (char *) oscap_iterator_next(services_enable_it); -+ _write_text_to_fd(output_fd, var_line); -+ if (oscap_iterator_has_more(services_enable_it)) -+ _write_text_to_fd(output_fd, ","); -+ } -+ _write_text_to_fd(output_fd, "]\n"); -+ oscap_iterator_free(services_enable_it); -+ oscap_list_free(services_enable, free); -+ -+ _write_text_to_fd(output_fd, "disabled = ["); -+ struct oscap_iterator *services_disable_it = oscap_iterator_new(services_disable); -+ while(oscap_iterator_has_more(services_disable_it)) { -+ char *var_line = (char *) oscap_iterator_next(services_disable_it); -+ _write_text_to_fd(output_fd, var_line); -+ if (oscap_iterator_has_more(services_disable_it)) -+ _write_text_to_fd(output_fd, ","); -+ } -+ _write_text_to_fd(output_fd, "]\n\n"); -+ oscap_iterator_free(services_disable_it); -+ oscap_list_free(services_disable, free); -+ -+ return ret; - } - - static int _xccdf_policy_generate_fix_ansible(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) -@@ -1145,6 +1317,8 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * - // in Ansible we have to generate variables first and then tasks - if (strcmp(sys, "urn:xccdf:fix:script:ansible") == 0) { - 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 { - ret = _xccdf_policy_generate_fix_other(rules_to_fix, policy, sys, output_fd); - } -diff --git a/src/common/list.c b/src/common/list.c -index 2516d0f2f0..90381069f8 100644 ---- a/src/common/list.c -+++ b/src/common/list.c -@@ -66,6 +66,25 @@ bool oscap_list_add(struct oscap_list * list, void *value) - return true; - } - -+bool oscap_list_prepend(struct oscap_list * list, void *value) -+{ -+ __attribute__nonnull__(list); -+ if (value == NULL) return false; -+ -+ struct oscap_list_item *item = malloc(sizeof(struct oscap_list_item)); -+ item->next = NULL; -+ item->data = value; -+ ++list->itemcount; -+ -+ if (list->first == NULL) { -+ list->last = list->first = item; -+ } else { -+ item->next = list->first; -+ list->first = item; -+ } -+ return true; -+} -+ - bool oscap_list_push(struct oscap_list *list, void *value) - { - return oscap_list_add(list,value); -diff --git a/src/common/list.h b/src/common/list.h -index 7a0694dc8a..3179c514f0 100644 ---- a/src/common/list.h -+++ b/src/common/list.h -@@ -62,6 +62,7 @@ struct oscap_list *oscap_list_new(void); - void oscap_create_lists(struct oscap_list **first, ...); - bool oscap_list_add(struct oscap_list *list, void *value); - bool oscap_list_push(struct oscap_list *list, void *value); -+bool oscap_list_prepend(struct oscap_list *list, void *value); - bool oscap_list_pop(struct oscap_list *list, oscap_destruct_func destructor); - bool oscap_list_remove(struct oscap_list *list, void *value, oscap_cmp_func compare, oscap_destruct_func destructor); - struct oscap_list *oscap_list_clone(const struct oscap_list * list, oscap_clone_func cloner); -diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt -index 52645834c4..9c17ebb78a 100644 ---- a/tests/API/XCCDF/unittests/CMakeLists.txt -+++ b/tests/API/XCCDF/unittests/CMakeLists.txt -@@ -75,6 +75,7 @@ add_oscap_test("test_single_rule_stigw.sh") - add_oscap_test("test_remediation_simple.sh") - add_oscap_test("test_remediation_offline.sh") - add_oscap_test("test_remediation_metadata.sh") -+add_oscap_test("test_remediation_blueprint.sh") - add_oscap_test("test_remediation_bad_fix.sh") - add_oscap_test("test_remediation_subs_plain_text.sh") - add_oscap_test("test_remediation_subs_plain_text_empty.sh") -diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.sh b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh -new file mode 100755 -index 0000000000..7c79822529 ---- /dev/null -+++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh -@@ -0,0 +1,27 @@ -+#!/usr/bin/env bash -+. $builddir/tests/test_common.sh -+ -+set -e -+set -o pipefail -+ -+name=$(basename $0 .sh) -+result=$(make_temp_file /tmp ${name}.out) -+stderr=$(make_temp_file /tmp ${name}.out) -+ -+ret=0 -+ -+input_xml="$srcdir/${name}.xccdf.xml" -+valid_toml="$srcdir/${name}.toml" -+ -+echo "Stderr file = $stderr" -+echo "Result file = $result" -+[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr -+ -+# The $valid_toml file was generated without ' # This file was generated by OpenSCAP 1.3.5 using:' line -+# to make the test independent from the scanner version. We have to filter this line from the output as well. -+ -+$OSCAP xccdf generate fix --fix-type blueprint --profile 'common' "$input_xml" | grep -v "OpenSCAP" > "$result" -+ -+diff $valid_toml $result -+ -+rm "$result" -diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.toml b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml -new file mode 100644 -index 0000000000..e189adca9d ---- /dev/null -+++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml -@@ -0,0 +1,45 @@ -+############################################################################### -+# -+# Blueprint for Profile title on one line -+# -+# Profile Description: -+# Profile description -+# -+# Profile ID: xccdf_moc.elpmaxe.www_profile_common -+# Benchmark ID: xccdf_moc.elpmaxe.www_benchmark_test -+# Benchmark Version: 1.0 -+# XCCDF Version: 1.2 -+# -+# $ oscap xccdf generate fix --profile xccdf_moc.elpmaxe.www_profile_common --fix-type blueprint xccdf-file.xml -+# -+# It attempts to fix every selected rule, even if the system is already compliant. -+# -+# How to apply this Blueprint: -+# composer-cli blueprints push blueprint.toml -+# -+############################################################################### -+ -+name = "xccdf_moc.elpmaxe.www_profile_common" -+description = "Profile title on one line" -+version = "1.0" -+distro = rhel-80 -+ -+[[packages]] -+name = "aide" -+version = "*" -+ -+[[customizations.filesystem]] -+mountpoint = "/home" -+size = 1 -+ -+[[customizations.filesystem]] -+mountpoint = "/tmp" -+size = 2 -+ -+[customizations.kernel] -+append = "foo=bar audit=1" -+ -+[customizations.services] -+enabled = ["sshd","usbguard"] -+disabled = ["kdump"] -+ -diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml b/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml -new file mode 100644 -index 0000000000..e685620dac ---- /dev/null -+++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml -@@ -0,0 +1,102 @@ -+ -+ -+ accepted -+ 1.0 -+ -+ Profile title on one line -+ Profile description -+ -+