Skip to content

Commit

Permalink
Add ability to specify post sections
Browse files Browse the repository at this point in the history
If a line in kickstart remediation starts with `%post`, that line and
all following lines until a line starting with `%end` are considered a
block. Blocks are propagated to the output without any processing.
  • Loading branch information
jan-cerny committed Jul 26, 2024
1 parent 24ade05 commit fcf313a
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 33 deletions.
18 changes: 11 additions & 7 deletions docs/manual/manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1130,22 +1130,26 @@ To generate a kickstart, use `oscap xccdf generate fix` command with the `--fix-
The kickstart will be generated from kickstart snippets in XCCDF rules in the input SCAP content.
The kickstart snippets need to be stored in `<fix>` elements with `system` attribute set to `urn:xccdf:fix:script:kickstart`.

When processing the kickstart snippets comming from the XCCDF Rules, each line is processed separately.
When processing the kickstart snippets from the XCCDF Rules, each line is processed separately.
The following rules are applied on each line:

* lines starting with `#` are ignored
* empty lines are ignored
* lines starting with a supported command are processed
* lines starting with something else than a supported command are dropped
* excess whitespace are trimmed
* Lines starting with `#` are ignored.
* Empty lines are ignored.
* If a line starts with a supported block keyword, that line and all following lines until a line starting with `%end` are considered a block. Blocks are propagated to the output without any processing.
* Lines starting with a supported command are processed.
* Lines starting with something else than a supported command are dropped and error is produced.
* Excess whitespace are trimmed.

Supported block keywords:

* `%post` - represents a start of a `%post` kickstart section, all lines until corresponding '%end' are overthrown

Supported commands:

* `package install package_name` - adds `package_name` to `%packages` section in the kickstart
* `package remove package_name` - adds `-package_name` to `%packages` section in the kickstart
* `service enable service_name` - adds `service_name` to list in the `--enabled=` option in the `services` command in commands section in the kickstart
* `service disable service_name` - adds `service_name` to list in the `--disabled=` option in the `services` command in commands section in the kickstart
* `post command` - adds `command` to the `%post` section the kickstart
* `logvol path size` - adds `logvol` entry to the commands section of the kickstart that will mount a partition of the given `size` in MB to the given `path` as a mount point
* `bootloader option` or `bootloader option=value` - adds `option` or `option=value` to the list in the `--append=` option in the `bootloader` command in commands section in the kickstart

Expand Down
64 changes: 46 additions & 18 deletions src/XCCDF_POLICY/xccdf_policy_remediate.c
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,6 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds)
KS_SERVICE_DISABLE,
KS_LOGVOL,
KS_LOGVOL_SIZE,
KS_POST,
KS_BOOTLOADER,
KS_ERROR
};
Expand All @@ -939,8 +938,6 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds)
state = KS_PACKAGE;
} else if (!strcmp(word, "service")) {
state = KS_SERVICE;
} else if (!strcmp(word, "post")) {
state = KS_POST;
} else if (!strcmp(word, "logvol")) {
state = KS_LOGVOL;
} else if (!strcmp(word, "bootloader")) {
Expand Down Expand Up @@ -985,11 +982,6 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds)
case KS_SERVICE_DISABLE:
oscap_list_add(cmds->service_disable, strdup(word));
break;
case KS_POST:
oscap_list_add(cmds->post, strdup(line + strlen("post ")));
/* we need to jump off because we have eaten the whole line */
goto cleanup;
break;
case KS_LOGVOL:
current_logvol_cmd = malloc(sizeof(struct logvol_cmd));
current_logvol_cmd->path = strdup(word);
Expand Down Expand Up @@ -1028,12 +1020,40 @@ static int _xccdf_policy_rule_generate_kickstart_fix(struct xccdf_policy *policy
}
char *dup = strdup(fix_text);
char **lines = oscap_split(dup, "\n");
enum states {
KS_R_P_NORMAL,
KS_R_P_POST_BLOCK
};
int state = KS_R_P_NORMAL;
char *block = NULL;
for (unsigned int i = 0; lines[i] != NULL; i++) {
char *line = lines[i];
oscap_trim(line);
if (*line == '#' || *line == '\0')
continue;
_parse_line(line, cmds);
char *trim_line = oscap_trim(strdup(line));
switch (state) {
case KS_R_P_NORMAL:
if (oscap_str_startswith(trim_line, "%post")) {
state = KS_R_P_POST_BLOCK;
block = strdup(trim_line);
block = oscap_concat(block, "\n");
} else if (*trim_line != '#' && *trim_line != '\0') {
_parse_line(trim_line, cmds);
}
break;
case KS_R_P_POST_BLOCK:
if (oscap_str_startswith(trim_line, "%end")) {
state = KS_R_P_NORMAL;
block = oscap_concat(block, trim_line);
block = oscap_concat(block, "\n");
oscap_list_add(cmds->post, block);
block = NULL;
} else {
block = oscap_concat(block, line);
block = oscap_concat(block, "\n");
}
default:
break;
}
free(trim_line);
}
free(lines);
free(dup);
Expand Down Expand Up @@ -1512,7 +1532,7 @@ static void _write_tailoring_to_fd(struct oscap_source *tailoring, int output_fd
_write_text_to_fd(output_fd, "END_OF_TAILORING\n");
}

static int _generate_kickstart_post(struct kickstart_commands *cmds, const char *profile_id, const char *input_path, struct oscap_source *tailoring, int output_fd)
static int _generate_kickstart_oscap_post(struct kickstart_commands *cmds, const char *profile_id, const char *input_path, struct oscap_source *tailoring, int output_fd)
{
_write_text_to_fd(output_fd, "# Perform OpenSCAP hardening (required for security compliance)\n");
_write_text_to_fd(output_fd, "%post --erroronfail\n");
Expand All @@ -1530,15 +1550,21 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char
_write_tailoring_to_fd(tailoring, output_fd);
_write_text_to_fd_and_free(output_fd, oscap_command);
_write_text_to_fd(output_fd, "[ $? -eq 0 -o $? -eq 2 ] || exit 1\n");
_write_text_to_fd(output_fd, "%end\n");
_write_text_to_fd(output_fd, "\n");
return 0;
}

static int _generate_kickstart_post(struct kickstart_commands *cmds, int output_fd)
{
struct oscap_iterator *post_it = oscap_iterator_new(cmds->post);
while (oscap_iterator_has_more(post_it)) {
char *command = (char *) oscap_iterator_next(post_it);
_write_text_to_fd(output_fd, command);
_write_text_to_fd(output_fd, "# Additional %post section (required for security compliance)\n");
char *post_content = (char *) oscap_iterator_next(post_it);
_write_text_to_fd(output_fd, post_content);
_write_text_to_fd(output_fd, "\n");
}
oscap_iterator_free(post_it);
_write_text_to_fd(output_fd, "%end\n");
_write_text_to_fd(output_fd, "\n");
return 0;
}

Expand Down Expand Up @@ -1657,7 +1683,9 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix,
_generate_kickstart_packages(&cmds, output_fd);

const char *profile_id = xccdf_profile_get_id(xccdf_policy_get_profile(policy));
_generate_kickstart_post(&cmds, profile_id, input_file_name, tailoring, output_fd);
_generate_kickstart_oscap_post(&cmds, profile_id, input_file_name, tailoring, output_fd);

_generate_kickstart_post(&cmds, output_fd);

_write_text_to_fd(output_fd, "# Reboot after the installation is complete\nreboot\n");

Expand Down
28 changes: 26 additions & 2 deletions tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<select idref="xccdf_org.openscap.www_rule_5" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_6" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_7" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_8" selected="true"/>
<select idref="xccdf_org.openscap.www_rule_9" selected="true"/>
</Profile>
<Rule selected="false" id="xccdf_org.openscap.www_rule_1">
<title>Rule 1: Enable Audit Service</title>
Expand Down Expand Up @@ -95,8 +97,6 @@
# the service commands will end up in the command section
service enable sshd
service disable telnet

post mkdir /etc/scap
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_6">
Expand All @@ -112,6 +112,30 @@
bootloader audit=1
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_8">
<title>Rule 8: Extra nochroot %post section</title>
<fix system="urn:xccdf:fix:script:kickstart">
%post --nochroot
mkdir -p /etc/ddfds
%end
</fix>
</Rule>
<Rule selected="false" id="xccdf_org.openscap.www_rule_9">
<title>Rule 9: Crazy remediation</title>
<fix system="urn:xccdf:fix:script:kickstart">
%post --nochroot
mkdir -p /etc/abcd
%end
service disable httpd
# other post section
%post
rm -rf /etc/xyz
# create a new path
feel /etc/xyz
%end
package install podman
</fix>
</Rule>
</Benchmark>
</ds:component>
</ds:data-stream-collection>
16 changes: 10 additions & 6 deletions tests/API/XCCDF/unittests/test_remediation_kickstart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ set -e -o pipefail
function test_normal {
kickstart=$(mktemp)
stderr=$(mktemp)
expected_modified=$(mktemp)
kickstart_modified=$(mktemp)

sed "/This file was generated by OpenSCAP .* using:/d" "$srcdir/test_remediation_kickstart_expected.cfg" > "$expected_modified"
sed -i "s;TEST_DATA_STREAM_PATH;$srcdir/test_remediation_kickstart.ds.xml;" "$expected_modified"

$OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile common "$srcdir/test_remediation_kickstart.ds.xml"

grep -q '# Kickstart for Common hardening profile' "$kickstart"
grep -q 'services --disabled=telnet --enabled=auditd,rsyslog,sshd' "$kickstart"
grep -q 'logvol /var/tmp --name=vartmp --vgname=system --size=1024' "$kickstart"
grep -q 'mkdir /etc/scap' "$kickstart"
grep -q '\-usbguard' "$kickstart"
grep -q 'bootloader --append="quick audit=1"' "$kickstart"
sed "/This file was generated by OpenSCAP .* using:/d" "$kickstart" > "$kickstart_modified"

diff -u "$expected_modified" "$kickstart_modified"

rm -rf "$kickstart"
rm -rf "$stderr"
rm -rf "$expected_modified"
rm -rf "$kickstart_modified"
}

function test_tailoring {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
<ns0:select idref="xccdf_org.openscap.www_rule_4" selected="false"/>
<ns0:select idref="xccdf_org.openscap.www_rule_5" selected="false"/>
<ns0:select idref="xccdf_org.openscap.www_rule_6" selected="false"/>
<ns0:select idref="xccdf_org.openscap.www_rule_7" selected="false"/>
<ns0:select idref="xccdf_org.openscap.www_rule_8" selected="false"/>
<ns0:select idref="xccdf_org.openscap.www_rule_9" selected="false"/>
</ns0:Profile>
</ns0:Tailoring>
85 changes: 85 additions & 0 deletions tests/API/XCCDF/unittests/test_remediation_kickstart_expected.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
###############################################################################
#
# Kickstart for Common hardening profile
#
# Profile Description:
# This is a very cool profile
#
# Profile ID: xccdf_org.openscap.www_profile_common
# Benchmark ID: xccdf_org.openscap.www_benchmark_test
# Benchmark Version: 1.0
# XCCDF Version: 1.2
#
# This file was generated by OpenSCAP 1.4.0 using:
# $ oscap xccdf generate fix --profile xccdf_org.openscap.www_profile_common --fix-type kickstart TEST_DATA_STREAM_PATH
#
# This Kickstart is generated from an OpenSCAP profile without preliminary evaluation.
# It attempts to fix every selected rule, even if the system is already compliant.
#
# How to apply this Kickstart:
# Review the kickstart and customize the kickstart for your deployment.
# Pay attention to items marked as "required for security compliance".
# Install the operating system using this kickstart.
#
###############################################################################


# Default values for automated installation
lang en_US.UTF-8
keyboard --vckeymap us
timezone --utc America/New_York

# Root password is required for system rescue tasks
rootpw changeme

# Create partition layout scheme (required for security compliance)
zerombr
clearpart --all --initlabel
reqpart --add-boot
part pv.01 --grow --size=1
volgroup system pv.01
logvol / --name=root --vgname=system --size=2000 --grow
logvol swap --name=swap --vgname=system --size=1000
logvol /var/tmp --name=vartmp --vgname=system --size=1024

# Configure boot loader options (required for security compliance)
bootloader --append="quick audit=1"

# Disable and enable systemd services (required for security compliance)
services --disabled=telnet,httpd --enabled=auditd,rsyslog,sshd

# Packages selection (required for security compliance)
%packages
openscap-scanner
scap-security-guide
rsyslog
openssh-server
podman
-usbguard
%end

# Perform OpenSCAP hardening (required for security compliance)
%post --erroronfail
oscap xccdf eval --remediate --results-arf /root/oscap_arf.xml --report /root/oscap_report.html --profile 'xccdf_org.openscap.www_profile_common' /usr/share/xml/scap/ssg/content/test_remediation_kickstart.ds.xml
[ $? -eq 0 -o $? -eq 2 ] || exit 1
%end

# Additional %post section (required for security compliance)
%post --nochroot
mkdir -p /etc/ddfds
%end

# Additional %post section (required for security compliance)
%post --nochroot
mkdir -p /etc/abcd
%end

# Additional %post section (required for security compliance)
%post
rm -rf /etc/xyz
# create a new path
feel /etc/xyz
%end

# Reboot after the installation is complete
reboot

0 comments on commit fcf313a

Please sign in to comment.