Add support for Blueprint remediations

Resolves: rhbz#2020052
This commit is contained in:
Jan Černý 2021-11-04 09:08:21 +01:00
parent 522d98f271
commit 5ad69e624b
3 changed files with 650 additions and 0 deletions

View File

@ -0,0 +1,64 @@
From 5f0a9033b466d929613a2a55a1524ec75c09b5b0 Mon Sep 17 00:00:00 2001
From: Evgeny Kolesnikov <ekolesni@redhat.com>
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 <type> - Fix type. Should be one of: bash, ansible, puppet, anaconda (default: bash).\n"
+ " --fix-type <type> - Fix type. Should be one of: bash, ansible, puppet, anaconda, ignition, kubernetes,\n"
+ " blueprint (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"
@@ -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:
<xsl:when test="$fix/@system = 'urn:xccdf:fix:script:puppet'">Puppet snippet</xsl:when>
<xsl:when test="$fix/@system = 'urn:redhat:anaconda:pre'">Anaconda snippet</xsl:when>
<xsl:when test="$fix/@system = 'urn:xccdf:fix:script:kubernetes'">Kubernetes snippet</xsl:when>
+ <xsl:when test="$fix/@system = 'urn:redhat:osbuild:blueprint'">OSBuild Blueprint snippet</xsl:when>
<xsl:otherwise>script</xsl:otherwise>
</xsl:choose>
</xsl:variable>

View File

@ -0,0 +1,583 @@
From b0b7626dca08acd4563ae42c1c27ccc0777b5357 Mon Sep 17 00:00:00 2001
From: Evgeny Kolesnikov <ekolesni@redhat.com>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Benchmark xmlns="http://checklists.nist.gov/xccdf/1.2" id="xccdf_moc.elpmaxe.www_benchmark_test">
+ <status>accepted</status>
+ <version>1.0</version>
+ <Profile id="xccdf_moc.elpmaxe.www_profile_common">
+ <title>Profile title on one line</title>
+ <description>Profile description</description>
+ <select idref="xccdf_moc.elpmaxe.www_rule_1" selected="true"/>
+ </Profile>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_1">
+ <title>Install aide</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[[packages]]
+name = "aide"
+version = "*"
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_2">
+ <title>Define /home</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[[customizations.filesystem]]
+mountpoint = "/home"
+size = 1
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_3">
+ <title>Add audit=1 kernel option</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[customizations.kernel]
+append = "audit=1"
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_4">
+ <title>Add foo=bar kernel option</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[customizations.kernel]
+append = "foo=bar"
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_5">
+ <title>Define /tmp</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[[customizations.filesystem]]
+mountpoint = "/tmp"
+size = 2
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_6">
+ <title>Enable usbguard</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[customizations.services]
+enabled = ["usbguard"]
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_7">
+ <title>Disable kdump</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[customizations.services]
+disabled = ["kdump"]
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_8">
+ <title>Set distro (RHEL 8.0)</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+distro = rhel-80
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+ <Rule selected="true" id="xccdf_moc.elpmaxe.www_rule_9">
+ <title>Enable sshd</title>
+ <fix system="urn:redhat:osbuild:blueprint">
+[customizations.services]
+enabled = ["sshd"]
+</fix>
+ <check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
+ <check-content-ref href="test_remediation_simple.oval.xml" name="oval:moc.elpmaxe.www:def:1"/>
+ </check>
+ </Rule>
+</Benchmark>

View File

@ -20,6 +20,8 @@ Patch11: openscap-1.3.6-http_error_fix-PR_1805.patch
Patch12: openscap-1.3.6-empty-proc-in-offline-pr-1812.patch
Patch13: openscap-1.3.6-initialize-crapi-once-pr-1779.patch
Patch14: openscap-1.3.6-test-rhbz1959570-pr-1788.patch
Patch15: openscap-1.3.6-blueprint-fix-pr-1749.patch
Patch16: openscap-1.3.6-blueprint-toml-pr-1810.patch
BuildRequires: make
BuildRequires: cmake >= 2.6
BuildRequires: gcc
@ -212,6 +214,7 @@ pathfix.py -i %{__python3} -p -n $RPM_BUILD_ROOT%{_bindir}/scap-as-rpm
%changelog
* Thu Nov 04 2021 Jan Černý <jcerny@redhat.com> - 1:1.3.5-11
- Initialize crypto API only once (rhbz#2020044)
- Add support for Blueprint remediations (rhbz#2020052)
* Mon Nov 01 2021 Evgenii Kolesnikov <ekolesni@redhat.com> - 1:1.3.5-10
- Fix process58 probe errors when scanning minimalist filesystem in offline mode (rhbz#2019054)