Skip to content
Draft
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
12 changes: 12 additions & 0 deletions src/XCCDF_POLICY/xccdf_policy_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ struct xccdf_policy {
*/
int xccdf_policy_resolve_fix_substitution(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result);

/**
* Resolve text substitution in given fix element containing Ansible remediation. Use given xccdf_policy settings
* for resolving.
* @memberof xccdf_policy
* @param policy XCCDF policy used for substitution
* @param fix a fix element to modify
* @param rule_result the rule-result for substitution instnace in fix
* @param test_result the TestResult for xccdf:fact resolution
* @returns 0 on success, 1 on failure, other value indicate warning
*/
int xccdf_policy_resolve_fix_substitution_ansible(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result);

/**
* Execute fix element for a given rule-result. Or find suitable (most appropriate) fix
* in the policy, assign it to the rule-result and execute.
Expand Down
68 changes: 53 additions & 15 deletions src/XCCDF_POLICY/xccdf_policy_remediate.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "xccdf_policy_model_priv.h"
#include "public/xccdf_policy.h"
#include "oscap_helpers.h"
#include "xccdf_benchmark.h"

struct kickstart_commands {
struct oscap_list *package_install;
Expand Down Expand Up @@ -764,12 +765,12 @@ static inline int _parse_blueprint_fix(const char *fix_text, struct blueprint_cu
return ret;
}

static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *variables, struct oscap_list *tasks)
static inline int _parse_ansible_fix(struct xccdf_policy *policy, const char *fix_text, struct oscap_list *variables, struct oscap_list *tasks)
{
// TODO: Tolerate different indentation styles in this regex
const char *pattern =
"- name: XCCDF Value [^ ]+ # promote to variable\n set_fact:\n"
" ([^:]+): (.+)\n tags:\n - always\n";
" ([^:]+): (!!str )?(.+)\n tags:\n - always\n";
char *err;
int errofs;

Expand All @@ -783,11 +784,11 @@ static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *va

// ovector sizing:
// 2 elements are used for the whole needle,
// 4 elements are used for the 2 capture groups
// 6 elements are used for the 3 capture groups
// pcre documentation says we should allocate a third extra for additional
// workspace.
// (2 + 4) * (3 / 2) = 9
int ovector[9];
// (2 + 6) * (3 / 2) = 12
int ovector[12];

const size_t fix_text_len = strlen(fix_text);
int start_offset = 0;
Expand All @@ -796,8 +797,8 @@ static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *va
0, ovector, sizeof(ovector) / sizeof(ovector[0]));
if (match == -1)
break;
if (match != 3) {
dE("Expected 2 capture group matches per XCCDF variable. Found %i!",
if (match != 4) {
dE("Expected 3 capture group matches per XCCDF variable. Found %i!",
match - 1);
oscap_pcre_free(re);
return 1;
Expand All @@ -806,18 +807,44 @@ static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *va
// ovector[0] and [1] hold the start and end of the whole needle match
// ovector[2] and [3] hold the start and end of the first capture group
// ovector[4] and [5] hold the start and end of the second capture group
// ovector[6] and [7] hold the start and end of the third capture group
char *variable_name = malloc((ovector[3] - ovector[2] + 1) * sizeof(char));
memcpy(variable_name, &fix_text[ovector[2]], ovector[3] - ovector[2]);
variable_name[ovector[3] - ovector[2]] = '\0';

char *variable_value = malloc((ovector[5] - ovector[4] + 1) * sizeof(char));
memcpy(variable_value, &fix_text[ovector[4]], ovector[5] - ovector[4]);
variable_value[ovector[5] - ovector[4]] = '\0';
char *cast = malloc((ovector[5] - ovector[4] + 1) * sizeof(char));
memcpy(cast, &fix_text[ovector[4]], ovector[5] - ovector[4]);
cast[ovector[5] - ovector[4]] = '\0';

char *var_line = oscap_sprintf(" %s: %s\n", variable_name, variable_value);
char *variable_id = malloc((ovector[7] - ovector[6] + 1) * sizeof(char));
memcpy(variable_id, &fix_text[ovector[6]], ovector[7] - ovector[6]);
variable_id[ovector[7] - ovector[6]] = '\0';

char *variable_value = NULL;
struct xccdf_item *item = xccdf_benchmark_get_item(xccdf_policy_get_benchmark(policy), variable_id);
if (item == NULL) {
dI("Variable not found: %s", variable_id);
variable_value = strdup(variable_id);
} else {
variable_value = strdup(xccdf_policy_get_value_of_item(policy, item));
}
free(variable_id);

char *var_line;
if (strchr(variable_value, '\n') != NULL) {
/* The value contains a multiline string. To ensure a valid YAML output
we need to put is as scalar block and indent it.*/
char *indented_variable_value = oscap_indent(variable_value, 6);
const char *terminator = oscap_str_endswith(indented_variable_value, "\n") ? "" : "\n";
var_line = oscap_sprintf(" %s: %s|\n%s%s", variable_name, cast, indented_variable_value, terminator);
free(indented_variable_value);
} else {
var_line = oscap_sprintf(" %s: %s%s\n", variable_name, cast, variable_value);
}

free(variable_name);
free(variable_value);
free(cast);

if (!oscap_list_contains(variables, var_line, (oscap_cmp_func) oscap_streq)) {
oscap_list_add(variables, var_line);
Expand All @@ -829,7 +856,10 @@ static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *va
char *remediation_part = malloc((length_between_matches + 1) * sizeof(char));
memcpy(remediation_part, &fix_text[start_offset], length_between_matches);
remediation_part[length_between_matches] = '\0';
oscap_list_add(tasks, remediation_part);
oscap_trim(remediation_part);
if (strlen(remediation_part) > 0) {
oscap_list_add(tasks, remediation_part);
}

start_offset = ovector[1]; // next time start after the entire pattern
}
Expand All @@ -838,7 +868,10 @@ static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *va
char *remediation_part = malloc((fix_text_len - start_offset + 1) * sizeof(char));
memcpy(remediation_part, &fix_text[start_offset], fix_text_len - start_offset);
remediation_part[fix_text_len - start_offset] = '\0';
oscap_list_add(tasks, remediation_part);
oscap_trim(remediation_part);
if (strlen(remediation_part) > 0) {
oscap_list_add(tasks, remediation_part);
}
}

oscap_pcre_free(re);
Expand All @@ -863,7 +896,12 @@ static int _xccdf_policy_rule_get_fix_text(struct xccdf_policy *policy, struct x

// Process Text Substitute within the fix
struct xccdf_fix *cfix = xccdf_fix_clone(fix);
int res = xccdf_policy_resolve_fix_substitution(policy, cfix, NULL, NULL);
int res = 0;
if (strcmp(template, "urn:xccdf:fix:script:ansible") == 0) {
res = xccdf_policy_resolve_fix_substitution_ansible(policy, cfix, NULL, NULL);
} else {
res = xccdf_policy_resolve_fix_substitution(policy, cfix, NULL, NULL);
}
if (res != 0) {
oscap_seterr(OSCAP_EFAMILY_OSCAP, "A fix for Rule/@id=\"%s\" was skipped: Text substitution failed.",
xccdf_rule_get_id(rule));
Expand Down Expand Up @@ -1128,7 +1166,7 @@ static int _xccdf_policy_rule_generate_ansible_fix(struct xccdf_policy *policy,
if (fix_text == NULL) {
return ret;
}
ret = _parse_ansible_fix(fix_text, variables, tasks);
ret = _parse_ansible_fix(policy, fix_text, variables, tasks);
free(fix_text);
return ret;
}
Expand Down
41 changes: 39 additions & 2 deletions src/XCCDF_POLICY/xccdf_policy_substitute.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,21 +192,58 @@ static int _xccdf_text_substitution_cb(xmlNode **node, void *user_data)
}
}

int xccdf_policy_resolve_fix_substitution(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result)
static int _xccdf_text_substitution_cb_ansible(xmlNode **node, void *user_data)
{
if (node == NULL || *node == NULL || user_data == NULL)
return 1;

xmlNode *cur = *node;
if (!oscap_streq((const char *) cur->name, "sub") || !xccdf_is_supported_namespace(cur->ns))
return 0;

if (cur->children != NULL)
dW("The xccdf:sub element SHALL NOT have any content.");

char *sub_idref = (char *) xmlGetProp(cur, BAD_CAST "idref");
if (sub_idref == NULL || *sub_idref == '\0') {
oscap_seterr(OSCAP_EFAMILY_XCCDF, "The xccdf:sub MUST have a single @idref attribute.");
free(sub_idref);
return 2;
}

xmlNode *new_node = xmlNewText(BAD_CAST sub_idref);
xmlReplaceNode(cur, new_node);
xmlFreeNode(cur);
*node = new_node;
free(sub_idref);
return 0;
}

static int _xccdf_policy_resolve_fix_substitution_impl(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result, int (*callback)(xmlNode **, void *))
{
struct _xccdf_text_substitution_data data;
data.policy = policy;
data.processing_type = _DOCUMENT_GENERATION_TYPE | _ASSESSMENT_TYPE;
data.rule_result = rule_result;

char *result = NULL;
int res = xml_iterate_dfs(xccdf_fix_get_content(fix), &result, _xccdf_text_substitution_cb, &data);
int res = xml_iterate_dfs(xccdf_fix_get_content(fix), &result, callback, &data);
if (res == 0)
xccdf_fix_set_content(fix, result);
free(result);
return res;
}

int xccdf_policy_resolve_fix_substitution(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result)
{
return _xccdf_policy_resolve_fix_substitution_impl(policy, fix, rule_result, test_result, _xccdf_text_substitution_cb);
}

int xccdf_policy_resolve_fix_substitution_ansible(struct xccdf_policy *policy, struct xccdf_fix *fix, struct xccdf_rule_result *rule_result, struct xccdf_result *test_result)
{
return _xccdf_policy_resolve_fix_substitution_impl(policy, fix, rule_result, test_result, _xccdf_text_substitution_cb_ansible);
}

char* xccdf_policy_substitute(const char *text, struct xccdf_policy *policy) {
struct _xccdf_text_substitution_data data;
data.policy = policy;
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 @@ -115,3 +115,4 @@ add_oscap_test("test_single_line_tailoring.sh")
add_oscap_test("test_reference.sh")
add_oscap_test("test_remediation_bootc.sh")
add_oscap_test("openscap_2289_regression.sh")
add_oscap_test("test_multiline_string_in_ansible_remediation.sh")
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@
- CCE-82462-3
- NIST-800-53-AU-2(a)


Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
. $builddir/tests/test_common.sh

# Test XCCDF values with multiline strings are correctly processed when generating Ansible remediation Playbooks

set -e
set -o pipefail

ds="$srcdir/test_multiline_string_in_ansible_remediation_ds.xml"

function test_oscap() {
local variant="$1"
local raw_output="$(mktemp)"
local no_header_output="$(mktemp)"
local stdout="$(mktemp)"
local stderr="$(mktemp)"
$OSCAP xccdf generate fix --profile "xccdf_com.example.www_profile_test_$variant" --fix-type ansible --output "$raw_output" "$ds" >"$stdout" 2>"$stderr"
[ -f "$stdout" ]
[ ! -s "$stdout" ]
[ -f "$stderr" ]
[ ! -s "$stderr" ]
sed '/^#/d' "$raw_output" > "$no_header_output"
diff -u "$no_header_output" "$srcdir/test_multiline_string_in_ansible_remediation_playbook_$variant.yml"
rm "$raw_output"
rm "$no_header_output"
rm "$stdout"
rm "$stderr"
}

test_oscap "single_line_string"
test_oscap "multi_line_string"
Loading
Loading