From b47e1c49e38315944504b518b073d0fd2cf7af6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 4 Jul 2024 18:04:49 +0200 Subject: [PATCH 01/53] Introduce new fix type kickstart This fix type will be used to generate RHEL kickstarts to support unattended installation of hardened RHEL systems. --- utils/oscap-xccdf.c | 6 ++++-- utils/oscap.8 | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c index 852b3ef401..01458df469 100644 --- a/utils/oscap-xccdf.c +++ b/utils/oscap-xccdf.c @@ -281,7 +281,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, kickstart (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" " --benchmark-id - ID of XCCDF Benchmark in some component in the data stream that should be used.\n" @@ -948,6 +948,8 @@ int app_generate_fix(const struct oscap_action *action) remediation_system = "urn:xccdf:fix:script:sh"; } else if (strcmp(action->fix_type, "ansible") == 0) { remediation_system = "urn:xccdf:fix:script:ansible"; + } else if (strcmp(action->fix_type, "kickstart") == 0) { + remediation_system = "urn:xccdf:fix:script:kickstart"; } else if (strcmp(action->fix_type, "puppet") == 0) { remediation_system = "urn:xccdf:fix:script:puppet"; } else if (strcmp(action->fix_type, "anaconda") == 0) { @@ -961,7 +963,7 @@ int app_generate_fix(const struct oscap_action *action) } 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, kickstart, puppet, anaconda, ignition, kubernetes, blueprint.\n", action->fix_type); return OSCAP_ERROR; } diff --git a/utils/oscap.8 b/utils/oscap.8 index ac5f76b941..5c6dd8e554 100644 --- a/utils/oscap.8 +++ b/utils/oscap.8 @@ -419,7 +419,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, blueprint. Default is bash. +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, kickstart. Default is bash. .TP \fB\-\-output FILE\fR Write the report to this file instead of standard output. From 9d37973c2b303802ad15f8040e9d42f134343f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 8 Jul 2024 14:01:43 +0200 Subject: [PATCH 02/53] Add a generic header for Kickstarts --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 8c2aaf98c9..ab782a9f43 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1014,7 +1014,7 @@ 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:redhat:osbuild:blueprint"))) + oscap_streq(sys, "urn:xccdf:fix:script:ansible") || oscap_streq(sys, "urn:redhat:osbuild:blueprint") || oscap_streq(sys, "urn:xccdf:fix:script:kickstart") )) return 0; // no header required const char *oscap_version = oscap_get_version(); @@ -1044,6 +1044,12 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ shebang_with_newline = "#!/usr/bin/env bash\n"; } + if (oscap_streq(sys, "urn:xccdf:fix:script:kickstart")) { + how_to_apply = "# Customize the kickstart for your deployment, then perform operating system installation using this kickstart."; + format = "kickstart"; + remediation_type = "Kickstart"; + } + char *fix_header; struct xccdf_profile *profile = xccdf_policy_get_profile(policy); From e9e0067a33a4730928d31ef7c3f0d59e4fb70edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 8 Jul 2024 14:51:56 +0200 Subject: [PATCH 03/53] Add a simple data stream with a Kickstart remediation --- .../test_remediation_kickstart.ds.xml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml new file mode 100644 index 0000000000..e25d3c6f2c --- /dev/null +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + 5.11.2 + 2021-02-01T08:07:06+01:00 + + + + + PASS + pass + + + + + + + + + + + + + + oval:org.openscap.www:var:1 + + + + + 100 + + + + + + + accepted + 1.0 + + Common hardening profile + This is a very cool profile + + + - Ensure that file exists and it is not executable + Rule 1: Enable Audit Service - services --enable auditd + service enable auditd + + + + Rule 2: Install rsyslog package + + package install rsyslog + + + + Rule 3: Enable Rsyslog Service + + # this command should end up in the command section + service enable rsyslog + + + + Rule 4: Remove USBGuard + + package remove usbguard + + + + Rule 4: Install and enable SSHD + + # openssh-server will go to %packages section + package install openssh-server + + # the service commands will end up in the command section + service enable sshd + service disable telnet + + post mkdir /etc/scap - - - From 87cd6913d7b7f209a97bb1225104f157997ee72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 11:37:09 +0200 Subject: [PATCH 08/53] Triage lines to Kickstart sections If a like starts with packages, services or post, it will be added to the respective Kickstart section. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 183 ++++++++++++++++++++-- 1 file changed, 166 insertions(+), 17 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index fc7b25d2ea..93c3f32da4 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -49,6 +49,14 @@ #include "public/xccdf_policy.h" #include "oscap_helpers.h" +struct kickstart_commands { + struct oscap_list *package_install; + struct oscap_list *package_remove; + struct oscap_list *service_enable; + struct oscap_list *service_disable; + struct oscap_list *post; +}; + static int _rule_add_info_message(struct xccdf_rule_result *rr, ...) { va_list ap; @@ -675,10 +683,6 @@ struct blueprint_customizations { struct oscap_list *kernel_append; }; -struct kickstart_fixes { - struct oscap_list *others; -}; - static inline int _parse_blueprint_fix(const char *fix_text, struct blueprint_customizations *customizations) { char *err; @@ -897,14 +901,97 @@ static int _xccdf_policy_rule_generate_blueprint_fix(struct xccdf_policy *policy return ret; } -static int _xccdf_policy_rule_generate_kickstart_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct kickstart_fixes *fixes) +static int _parse_line(const char *line, struct kickstart_commands *cmds) +{ + int ret = 0; + char *dup = strdup(line); + char **words = oscap_split(dup, " "); + enum states {KS_START, KS_PACKAGE, KS_PACKAGE_INSTALL, KS_PACKAGE_REMOVE, KS_SERVICE, KS_SERVICE_ENABLE, KS_SERVICE_DISABLE, KS_POST}; + int state = KS_START; + for (unsigned int i = 0; words[i] != NULL; i++) { + char *word = words[i]; + switch (state) { + case KS_START: + if (!strcmp(word, "package")) { + state = KS_PACKAGE; + } else if (!strcmp(word, "service")) { + state = KS_SERVICE; + } else if (!strcmp(word, "post")) { + state = KS_POST; + } else { + ret = 1; + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command:'%s'", word, line); + goto cleanup; + } + break; + case KS_PACKAGE: + if (!strcmp(word, "install")) { + state = KS_PACKAGE_INSTALL; + } else if (!strcmp(word, "remove")) { + state = KS_PACKAGE_REMOVE; + } else { + ret = 1; + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'package' command keyword '%s' in command:'%s'", word, line); + goto cleanup; + } + break; + case KS_PACKAGE_INSTALL: + oscap_list_add(cmds->package_install, strdup(word)); + break; + case KS_PACKAGE_REMOVE: + oscap_list_add(cmds->package_remove, strdup(word)); + break; + case KS_SERVICE: + if (!strcmp(word, "enable")) { + state = KS_SERVICE_ENABLE; + } else if (!strcmp(word, "disable")) { + state = KS_SERVICE_DISABLE; + } else { + ret = 1; + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'service' command keyword '%s' in command:'%s'", word, line); + goto cleanup; + } + break; + case KS_SERVICE_ENABLE: + oscap_list_add(cmds->service_enable, strdup(word)); + break; + 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 "))); + goto cleanup; + break; + default: + break; + } + } + +cleanup: + free(words); + free(dup); + return ret; +} + +static int _xccdf_policy_rule_generate_kickstart_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct kickstart_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; } - oscap_list_add(fixes->others, fix_text); + char *dup = strdup(fix_text); + char **lines = oscap_split(dup, "\n"); + 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); + } + free(lines); + free(dup); + free(fix_text); return ret; } @@ -1310,37 +1397,99 @@ static int _xccdf_policy_generate_fix_other(struct oscap_list *rules_to_fix, str static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) { int ret = 0; - struct kickstart_fixes fixes = { - .others = oscap_list_new(), + struct kickstart_commands cmds = { + .package_install = oscap_list_new(), + .package_remove = oscap_list_new(), + .service_enable = oscap_list_new(), + .service_disable = oscap_list_new(), + .post = 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_kickstart_fix(policy, rule, sys, &fixes); + ret = _xccdf_policy_rule_generate_kickstart_fix(policy, rule, sys, &cmds); if (ret != 0) break; } oscap_iterator_free(rules_to_fix_it); - struct oscap_iterator *others_it = oscap_iterator_new(fixes.others); - while(oscap_iterator_has_more(others_it)) { - char *command = (char *) oscap_iterator_next(others_it); - _write_text_to_fd(output_fd, command); - } + struct oscap_iterator *service_disable_it = oscap_iterator_new(cmds.service_disable); + struct oscap_iterator *service_enable_it = oscap_iterator_new(cmds.service_enable); + if (oscap_iterator_has_more(service_disable_it) || oscap_iterator_has_more(service_enable_it)) { + _write_text_to_fd(output_fd, "# Disable and enable systemd services based on the SCAP profile\n"); + _write_text_to_fd(output_fd, "services"); + if (oscap_iterator_has_more(service_disable_it)) { + _write_text_to_fd(output_fd, " --disabled="); + while (oscap_iterator_has_more(service_disable_it)) { + char *command = (char *) oscap_iterator_next(service_disable_it); + _write_text_to_fd(output_fd, command); + if (oscap_iterator_has_more(service_disable_it)) + _write_text_to_fd(output_fd, ","); + } + } + if (oscap_iterator_has_more(service_enable_it)) { + _write_text_to_fd(output_fd, " --enabled="); + while (oscap_iterator_has_more(service_enable_it)) { + char *command = (char *) oscap_iterator_next(service_enable_it); + _write_text_to_fd(output_fd, command); + if (oscap_iterator_has_more(service_enable_it)) + _write_text_to_fd(output_fd, ","); + } + } + _write_text_to_fd(output_fd, "\n\n"); + } + oscap_iterator_free(service_disable_it); + oscap_iterator_free(service_enable_it); + + _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); + _write_text_to_fd(output_fd, "%packages\n"); + struct oscap_iterator *package_install_it = oscap_iterator_new(cmds.package_install); + while (oscap_iterator_has_more(package_install_it)) { + char *package = (char *) oscap_iterator_next(package_install_it); + _write_text_to_fd(output_fd, package); + _write_text_to_fd(output_fd, "\n"); + } + oscap_iterator_free(package_install_it); + struct oscap_iterator *package_remove_it = oscap_iterator_new(cmds.package_remove); + 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); + _write_text_to_fd(output_fd, "\n"); + } + oscap_iterator_free(package_remove_it); + _write_text_to_fd(output_fd, "%end\n"); _write_text_to_fd(output_fd, "\n"); + + _write_text_to_fd(output_fd, "%post\n"); const char *profile_id = xccdf_profile_get_id(xccdf_policy_get_profile(policy)); const char *ds_path = "/usr/share/xml/scap/ssg/content/ssg-xxxxx-ds.xml"; char *oscap_command = oscap_sprintf( "oscap xccdf eval --remediate --profile '%s' %s\n", profile_id, ds_path); - _write_text_to_fd(output_fd, "\n"); - _write_text_to_fd(output_fd, "%post\n"); _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening\n"); _write_text_to_fd_and_free(output_fd, oscap_command); + 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, "\n"); + } + oscap_iterator_free(post_it); _write_text_to_fd(output_fd, "%end\n"); + _write_text_to_fd(output_fd, "\n"); + + _write_text_to_fd(output_fd, "# Reboot after the installation is complete (optional)\n"); + _write_text_to_fd(output_fd, "# --eject - attempt to eject CD or DVD media before rebooting\n"); + _write_text_to_fd(output_fd, "reboot --eject\n"); - oscap_list_free(fixes.others, free); + oscap_list_free(cmds.package_install, free); + oscap_list_free(cmds.package_remove, free); + oscap_list_free(cmds.service_enable, free); + oscap_list_free(cmds.service_disable, free); + oscap_list_free(cmds.post, free); return ret; } From c62a90544f39653d1caf07fa89b8bbc550efcd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 11:49:48 +0200 Subject: [PATCH 09/53] Add a simple test for Kickstart fix --- tests/API/XCCDF/unittests/CMakeLists.txt | 1 + .../XCCDF/unittests/test_remediation_kickstart.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100755 tests/API/XCCDF/unittests/test_remediation_kickstart.sh diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt index 752da14b9e..c1d636abca 100644 --- a/tests/API/XCCDF/unittests/CMakeLists.txt +++ b/tests/API/XCCDF/unittests/CMakeLists.txt @@ -77,6 +77,7 @@ add_oscap_test("test_single_rule.sh") 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_kickstart.sh") add_oscap_test("test_remediation_metadata.sh") add_oscap_test("test_remediation_blueprint.sh") add_oscap_test("test_remediation_bad_fix.sh") diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh new file mode 100755 index 0000000000..0300827177 --- /dev/null +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +. $builddir/tests/test_common.sh + +set -e -o pipefail + +kickstart=$(mktemp) + +$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" + +rm -rf "$kickstart" From da9a41e2bf13814cf07ae5c579856d687d373c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 13:48:27 +0200 Subject: [PATCH 10/53] Disable result oriented fixes for the kickstart fix type You can't scan the machine that you will install using the generated kickstart (to scan it it would already have to be installed). Therefore it doesn't make sense to generate kickstarts for results. For simplicity we will disable this feature. --- .../XCCDF/unittests/test_remediation_kickstart.sh | 14 ++++++++++++++ utils/oscap-xccdf.c | 5 +++++ utils/oscap.8 | 1 + 3 files changed, 20 insertions(+) diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index 0300827177..65b2b5aebd 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -4,9 +4,23 @@ set -e -o pipefail kickstart=$(mktemp) +stderr=$(mktemp) + +$OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --result-id xccdf_org.open-scap_testresult_xccdf_org.ssgproject.content_profile_ospp "$srcdir/test_remediation_kickstart.ds.xml" 2> "$stderr" || ret=$? + +[ $ret = 1 ] +grep -q "It isn't possible to generate results-oriented Kickstarts." $stderr + +rm -rf "$kickstart" +rm -rf "$stderr" + + +kickstart=$(mktemp) +stderr=$(mktemp) $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" rm -rf "$kickstart" +rm -rf "$stderr" diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c index 01458df469..e4eece2a6e 100644 --- a/utils/oscap-xccdf.c +++ b/utils/oscap-xccdf.c @@ -971,6 +971,11 @@ int app_generate_fix(const struct oscap_action *action) remediation_system = "urn:xccdf:fix:script:sh"; } + if (action->id != NULL && action->fix_type != NULL && !strcmp(action->fix_type, "kickstart")) { + fprintf(stderr, "It isn't possible to generate results-oriented Kickstarts.\n"); + return OSCAP_ERROR; + } + int ret = OSCAP_ERROR; struct oscap_source *source = oscap_source_new_from_file(action->f_xccdf); oscap_document_type_t document_type = oscap_source_get_scap_type(source); diff --git a/utils/oscap.8 b/utils/oscap.8 index 5c6dd8e554..412e578982 100644 --- a/utils/oscap.8 +++ b/utils/oscap.8 @@ -415,6 +415,7 @@ ID of the XCCDF TestResult from which the report will be generated. 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 kickstart 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 From e366cbc553a6678c3d3490a51903e73ea1f0f151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 15:13:31 +0200 Subject: [PATCH 11/53] Document generating kickstarts in user manual --- docs/manual/manual.adoc | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index 686e5f78f4..2de266e2fa 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1120,6 +1120,44 @@ 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 ---- +=== Generating RHEL Kickstarts + +OpenSCAP can generate RHEL kickstarts which can be used for unattended installation of RHEL, Fedora and similar systems. +Information about RHEL kickstarts and their syntax can be found at https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/performing_an_advanced_rhel_9_installation/kickstart_references[Kickstart references]. + +To generate a kickstart, use `oscap xccdf generate fix` command with the `--fix-type kickstart` option. + +The kickstart will be generated from kickstart snippets in XCCDF rules in the input SCAP content. +The kickstart snippets need to be stored in `` 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. +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 + +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 + +For example, to generate a kickstart for RHEL 9 STIG profile, run: + +---- +$ oscap xccdf generate fix --profile stig --fix-type kickstart /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml > rhel9-kickstart-stig.cfg +---- + +The generated kickstart file needs to be reviewed and customized for the intended deployment. + +NOTE: The `kickstart` fix type shouldn't be confused with `anaconda` fix type. +The `anaconda` fix type is used by the OSCAP Anaconda Addon and shouldn't be used directly by users. +Users should use the `kickstart` fix type. + == Details on SCAP conformance === Check Engines @@ -2095,3 +2133,10 @@ You can find the ID of the customized profile with `oscap info >. + +*I have generated a kickstart but the generated file isn't a valid kickstart.* + +You are using a wrong `--fix-type` option. +To generate a kickstart, use the `--fix-type kickstart` option. +Do not use `--fix-type anaconda`. +For more information, please refer to section <<_generating_rhel_kickstarts,Generating RHEL Kickstarts>>. From 7907dd8343761f0f0d4fca681a4e1456da253466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 17:08:14 +0200 Subject: [PATCH 12/53] Add a common kickstart header This header will put some common options that are sensible for every RHEL kickstart. The purpose to make the installation easier and set sensible defaults. The code has been taken from ComplianceAsCode/content RHEL 9 CIS Kickstart. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 93c3f32da4..aed7f59093 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1394,6 +1394,78 @@ static int _xccdf_policy_generate_fix_other(struct oscap_list *rules_to_fix, str return ret; } +const char *common_kickstart_header = ( +"# Specify installation method to use for installation\n" +"# To use a different one comment out the 'url' one below, update\n" +"# the selected choice with proper options & un-comment it\n" +"#\n" +"# Install from an installation tree on a remote server via FTP or HTTP:\n" +"# --url the URL to install from\n" +"#\n" +"# Example:\n" +"#\n" +"# url --url=http://192.168.122.1/image\n" +"#\n" +"# Modify concrete URL in the above example appropriately to reflect the actual\n" +"# environment machine is to be installed in\n" +"#\n" +"# Other possible / supported installation methods:\n" +"# * install from the first CD-ROM/DVD drive on the system:\n" +"#\n" +"# cdrom\n" +"#\n" +"# * install from a directory of ISO images on a local drive:\n" +"#\n" +"# harddrive --partition=hdb2 --dir=/tmp/install-tree\n" +"#\n" +"# * install from provided NFS server:\n" +"#\n" +"# nfs --server= --dir= [--opts=]\n" +"#\n" +"\n" +"# Set language to use during installation and the default language to use on the installed system (required)\n" +"lang en_US.UTF-8\n" +"\n" +"# Set system keyboard type / layout (required)\n" +"keyboard --vckeymap us\n" +"\n" +"# Configure network information for target system and activate network devices in the installer environment (optional)\n" +"# --onboot enable device at a boot time\n" +"# --device device to be activated and / or configured with the network command\n" +"# --bootproto method to obtain networking configuration for device (default dhcp)\n" +"# --noipv6 disable IPv6 on this device\n" +"network --onboot yes --device eth0 --bootproto dhcp --noipv6\n" +"\n" +"# Set the system's root password (required)\n" +"# Plaintext password is: server\n" +"# Refer to e.g. https://pykickstart.readthedocs.io/en/latest/commands.html#rootpw to see how to create\n" +"# encrypted password form for different plaintext password\n" +"rootpw --iscrypted $6$/0RYeeRdK70ynvYz$jH2ZN/80HM6DjndHMxfUF9KIibwipitvizzXDH1zW.fTjyD3RD3tkNdNUaND18B/XqfAUW3vy1uebkBybCuIm0\n" +"\n" +"# The selected profile will restrict root login\n" +"# Add a user that can login and escalate privileges\n" +"# Plaintext password is: admin123\n" +"user --name=admin --groups=wheel --password=$6$Ga6ZnIlytrWpuCzO$q0LqT1USHpahzUafQM9jyHCY9BiE5/ahXLNWUMiVQnFGblu0WWGZ1e6icTaCGO4GNgZNtspp1Let/qpM7FMVB0 --iscrypted\n" +"\n" +"# Configure firewall settings for the system (optional)\n" +"# --enabled reject incoming connections that are not in response to outbound requests\n" +"# --ssh allow sshd service through the firewall\n" +"firewall --enabled --ssh\n" +"\n" +"# State of SELinux on the installed system (optional)\n" +"# Defaults to enforcing\n" +"selinux --enforcing\n" +"\n" +"# Set the system time zone (required)\n" +"timezone --utc America/New_York\n" +"\n" +"# Specify how the bootloader should be installed (required)\n" +"# Plaintext password is: password\n" +"# Refer to e.g. grub2-mkpasswd-pbkdf2 to see how to create\n" +"# encrypted password form for different plaintext password\n" +"bootloader --password=grub.pbkdf2.sha512.10000.45912D32B964BA58B91EAF9847F3CCE6F4C962638922543AFFAEE4D29951757F4336C181E6FC9030E07B7D9874DAD696A1B18978D995B1D7F27AF9C38159FDF3.99F65F3896012A0A3D571A99D6E6C695F3C51BE5343A01C1B6907E1C3E1373CB7F250C2BC66C44BB876961E9071F40205006A05189E51C2C14770C70C723F3FD --iscrypted\n" +); + static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) { int ret = 0; @@ -1414,6 +1486,9 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, } oscap_iterator_free(rules_to_fix_it); + _write_text_to_fd(output_fd, common_kickstart_header); + _write_text_to_fd(output_fd, "\n"); + struct oscap_iterator *service_disable_it = oscap_iterator_new(cmds.service_disable); struct oscap_iterator *service_enable_it = oscap_iterator_new(cmds.service_enable); if (oscap_iterator_has_more(service_disable_it) || oscap_iterator_has_more(service_enable_it)) { From 0d223e7916a53aa7119f5fb34a816442f9c303b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 9 Jul 2024 18:48:11 +0200 Subject: [PATCH 13/53] Make sure openscap and scap-security-guide are installed We will run an oscap scan in the post phase, therefore we need to install openscap and scap-security-guide to be able to run the oscap scan. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index aed7f59093..fb6a260d34 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1519,6 +1519,9 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); _write_text_to_fd(output_fd, "%packages\n"); + /* openscap-scanner and scap-security-guide needs to be installed because we will run oscap in the %post section */ + _write_text_to_fd(output_fd, "openscap-scanner\n"); + _write_text_to_fd(output_fd, "scap-security-guide\n"); struct oscap_iterator *package_install_it = oscap_iterator_new(cmds.package_install); while (oscap_iterator_has_more(package_install_it)) { char *package = (char *) oscap_iterator_next(package_install_it); From eaf2caccf44ed8be4864140ef0da9626c5a1e59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 13:12:42 +0200 Subject: [PATCH 14/53] Add a comment --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index fb6a260d34..7f80153fc2 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -960,6 +960,7 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) 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; default: From bf6f1112d6c516e67d8bd7b498a40b0f9c8275a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 13:45:06 +0200 Subject: [PATCH 15/53] Show a correct input file name Instead of hard-coded "xccdf-file.xml" we will show the real input SCAP file name in the kenerated remediations. --- src/XCCDF_POLICY/public/xccdf_policy.h | 3 ++- src/XCCDF_POLICY/xccdf_policy_remediate.c | 12 ++++++------ .../XCCDF/unittests/test_remediation_blueprint.sh | 8 ++++++-- .../XCCDF/unittests/test_remediation_blueprint.toml | 2 +- utils/oscap-xccdf.c | 4 ++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/XCCDF_POLICY/public/xccdf_policy.h b/src/XCCDF_POLICY/public/xccdf_policy.h index 85bedd0f1c..0f171ae2b9 100644 --- a/src/XCCDF_POLICY/public/xccdf_policy.h +++ b/src/XCCDF_POLICY/public/xccdf_policy.h @@ -515,10 +515,11 @@ OSCAP_API bool xccdf_policy_resolve(struct xccdf_policy * policy); * @param result XCCDF TestResult. This may be omitted to generate the prescription * based solely on the XCCDF Policy (xccdf:Profile). * @param sys Consider only those fixes that have @system attribute equal to sys + * @param input_file_name file name of the input SCAP file * @param output_fd write prescription to this file descriptor * @returns zero on success, non-zero indicate partial (incomplete) output. */ -OSCAP_API int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd); +OSCAP_API int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, int output_fd); /** * xccdf_policy_model_get_files and xccdf_item_get_files each return oscap_file_entries instead of raw strings diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 7f80153fc2..f0c8ad12a3 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1114,7 +1114,7 @@ static char *_comment_multiline_text(char *text) return buffer; } -static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd) +static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, 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:redhat:osbuild:blueprint") || oscap_streq(sys, "urn:xccdf:fix:script:kickstart") )) @@ -1197,7 +1197,7 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ "# XCCDF Version: %s\n" "#\n" "# This file was generated by OpenSCAP %s using:\n" - "# $ oscap xccdf generate fix --profile %s --fix-type %s xccdf-file.xml\n" + "# $ oscap xccdf generate fix --profile %s --fix-type %s %s\n" "#\n" "# This %s is generated from an OpenSCAP profile without preliminary evaluation.\n" "# It attempts to fix every selected rule, even if the system is already compliant.\n" @@ -1209,7 +1209,7 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ shebang_with_newline, remediation_type, profile_title, commented_profile_description, profile_id, benchmark_id, benchmark_version_info, xccdf_version_name, - oscap_version, profile_id, format, remediation_type, + oscap_version, profile_id, format, input_file_name, remediation_type, remediation_type, how_to_apply ); @@ -1572,7 +1572,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, return ret; } -int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd) +int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, int output_fd) { __attribute__nonnull__(policy); int ret = 0; @@ -1589,7 +1589,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * return 1; } - if (_write_script_header_to_fd(policy, result, sys, output_fd) != 0) { + if (_write_script_header_to_fd(policy, result, sys, input_file_name, output_fd) != 0) { oscap_list_free(rules_to_fix, NULL); return 1; } @@ -1606,7 +1606,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * else { dI("Generating result-oriented fixes for policy(result/@id=%s)", xccdf_result_get_id(result)); - if (_write_script_header_to_fd(policy, result, sys, output_fd) != 0) { + if (_write_script_header_to_fd(policy, result, sys, input_file_name, output_fd) != 0) { oscap_list_free(rules_to_fix, NULL); return 1; } diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.sh b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh index 7c79822529..8ea9fcab83 100755 --- a/tests/API/XCCDF/unittests/test_remediation_blueprint.sh +++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh @@ -13,15 +13,19 @@ ret=0 input_xml="$srcdir/${name}.xccdf.xml" valid_toml="$srcdir/${name}.toml" +expected_result=$(mktemp) +sed "s;TEST_XCCDF_FILE_NAME;$input_xml;" "$valid_toml" > "$expected_result" + 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 +# The expected 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 +diff "$expected_result" "$result" rm "$result" +rm "$expected_result" diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.toml b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml index 2dfd8fce50..e4ce106b72 100644 --- a/tests/API/XCCDF/unittests/test_remediation_blueprint.toml +++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml @@ -10,7 +10,7 @@ # 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 +# $ oscap xccdf generate fix --profile xccdf_moc.elpmaxe.www_profile_common --fix-type blueprint TEST_XCCDF_FILE_NAME # # It attempts to fix every selected rule, even if the system is already compliant. # diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c index e4eece2a6e..fe69893743 100644 --- a/utils/oscap-xccdf.c +++ b/utils/oscap-xccdf.c @@ -1040,7 +1040,7 @@ int app_generate_fix(const struct oscap_action *action) struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session); struct xccdf_result *result = xccdf_policy_get_result_by_id(policy, xccdf_session_get_result_id(session)); - if (xccdf_policy_generate_fix(policy, result, remediation_system, output_fd) == 0) + if (xccdf_policy_generate_fix(policy, result, remediation_system, action->f_xccdf, output_fd) == 0) ret = OSCAP_OK; } else { // Fallback to profile if result id is missing /* Profile-oriented fixes */ @@ -1054,7 +1054,7 @@ int app_generate_fix(const struct oscap_action *action) } } struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session); - if (xccdf_policy_generate_fix(policy, NULL, remediation_system, output_fd) == 0) + if (xccdf_policy_generate_fix(policy, NULL, remediation_system, action->f_xccdf, output_fd) == 0) ret = OSCAP_OK; } cleanup2: From e31d82f1c5a09e18d81696cc6c4b53aaa4f8d123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 14:02:27 +0200 Subject: [PATCH 16/53] Extract function _generate_kickstart_services --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 60 +++++++++++++---------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index f0c8ad12a3..0e715d5b0e 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1395,6 +1395,38 @@ static int _xccdf_policy_generate_fix_other(struct oscap_list *rules_to_fix, str return ret; } +static int _generate_kickstart_services(struct kickstart_commands *cmds, int output_fd) +{ + struct oscap_iterator *service_disable_it = oscap_iterator_new(cmds->service_disable); + struct oscap_iterator *service_enable_it = oscap_iterator_new(cmds->service_enable); + if (oscap_iterator_has_more(service_disable_it) || oscap_iterator_has_more(service_enable_it)) { + _write_text_to_fd(output_fd, "# Disable and enable systemd services based on the SCAP profile\n"); + _write_text_to_fd(output_fd, "services"); + if (oscap_iterator_has_more(service_disable_it)) { + _write_text_to_fd(output_fd, " --disabled="); + while (oscap_iterator_has_more(service_disable_it)) { + char *command = (char *) oscap_iterator_next(service_disable_it); + _write_text_to_fd(output_fd, command); + if (oscap_iterator_has_more(service_disable_it)) + _write_text_to_fd(output_fd, ","); + } + } + if (oscap_iterator_has_more(service_enable_it)) { + _write_text_to_fd(output_fd, " --enabled="); + while (oscap_iterator_has_more(service_enable_it)) { + char *command = (char *) oscap_iterator_next(service_enable_it); + _write_text_to_fd(output_fd, command); + if (oscap_iterator_has_more(service_enable_it)) + _write_text_to_fd(output_fd, ","); + } + } + _write_text_to_fd(output_fd, "\n\n"); + } + oscap_iterator_free(service_disable_it); + oscap_iterator_free(service_enable_it); + return 0; +} + const char *common_kickstart_header = ( "# Specify installation method to use for installation\n" "# To use a different one comment out the 'url' one below, update\n" @@ -1490,33 +1522,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _write_text_to_fd(output_fd, common_kickstart_header); _write_text_to_fd(output_fd, "\n"); - struct oscap_iterator *service_disable_it = oscap_iterator_new(cmds.service_disable); - struct oscap_iterator *service_enable_it = oscap_iterator_new(cmds.service_enable); - if (oscap_iterator_has_more(service_disable_it) || oscap_iterator_has_more(service_enable_it)) { - _write_text_to_fd(output_fd, "# Disable and enable systemd services based on the SCAP profile\n"); - _write_text_to_fd(output_fd, "services"); - if (oscap_iterator_has_more(service_disable_it)) { - _write_text_to_fd(output_fd, " --disabled="); - while (oscap_iterator_has_more(service_disable_it)) { - char *command = (char *) oscap_iterator_next(service_disable_it); - _write_text_to_fd(output_fd, command); - if (oscap_iterator_has_more(service_disable_it)) - _write_text_to_fd(output_fd, ","); - } - } - if (oscap_iterator_has_more(service_enable_it)) { - _write_text_to_fd(output_fd, " --enabled="); - while (oscap_iterator_has_more(service_enable_it)) { - char *command = (char *) oscap_iterator_next(service_enable_it); - _write_text_to_fd(output_fd, command); - if (oscap_iterator_has_more(service_enable_it)) - _write_text_to_fd(output_fd, ","); - } - } - _write_text_to_fd(output_fd, "\n\n"); - } - oscap_iterator_free(service_disable_it); - oscap_iterator_free(service_enable_it); + _generate_kickstart_services(&cmds, output_fd); _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); _write_text_to_fd(output_fd, "%packages\n"); From 90a90bf7c482ba44193aa1b04f924a40bdd632b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 14:13:20 +0200 Subject: [PATCH 17/53] Extract function _generate_kickstart_packages --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 51 +++++++++++++---------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 0e715d5b0e..b6ecc2c87e 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1427,6 +1427,33 @@ static int _generate_kickstart_services(struct kickstart_commands *cmds, int out return 0; } +static int _generate_kickstart_packages(struct kickstart_commands *cmds, int output_fd) +{ + _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); + _write_text_to_fd(output_fd, "%packages\n"); + /* openscap-scanner and scap-security-guide needs to be installed because we will run oscap in the %post section */ + _write_text_to_fd(output_fd, "openscap-scanner\n"); + _write_text_to_fd(output_fd, "scap-security-guide\n"); + struct oscap_iterator *package_install_it = oscap_iterator_new(cmds->package_install); + while (oscap_iterator_has_more(package_install_it)) { + char *package = (char *) oscap_iterator_next(package_install_it); + _write_text_to_fd(output_fd, package); + _write_text_to_fd(output_fd, "\n"); + } + oscap_iterator_free(package_install_it); + struct oscap_iterator *package_remove_it = oscap_iterator_new(cmds->package_remove); + 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); + _write_text_to_fd(output_fd, "\n"); + } + oscap_iterator_free(package_remove_it); + _write_text_to_fd(output_fd, "%end\n"); + _write_text_to_fd(output_fd, "\n"); + return 0; +} + const char *common_kickstart_header = ( "# Specify installation method to use for installation\n" "# To use a different one comment out the 'url' one below, update\n" @@ -1524,29 +1551,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _generate_kickstart_services(&cmds, output_fd); - _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); - _write_text_to_fd(output_fd, "%packages\n"); - /* openscap-scanner and scap-security-guide needs to be installed because we will run oscap in the %post section */ - _write_text_to_fd(output_fd, "openscap-scanner\n"); - _write_text_to_fd(output_fd, "scap-security-guide\n"); - struct oscap_iterator *package_install_it = oscap_iterator_new(cmds.package_install); - while (oscap_iterator_has_more(package_install_it)) { - char *package = (char *) oscap_iterator_next(package_install_it); - _write_text_to_fd(output_fd, package); - _write_text_to_fd(output_fd, "\n"); - } - oscap_iterator_free(package_install_it); - struct oscap_iterator *package_remove_it = oscap_iterator_new(cmds.package_remove); - 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); - _write_text_to_fd(output_fd, "\n"); - } - oscap_iterator_free(package_remove_it); - _write_text_to_fd(output_fd, "%end\n"); - _write_text_to_fd(output_fd, "\n"); - + _generate_kickstart_packages(&cmds, output_fd); _write_text_to_fd(output_fd, "%post\n"); const char *profile_id = xccdf_profile_get_id(xccdf_policy_get_profile(policy)); From 95c8728a55fdf77fef444a037acc0bdeb04e3806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 14:19:44 +0200 Subject: [PATCH 18/53] Extract function _generate_kickstart_post --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index b6ecc2c87e..bb62c54af7 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1454,6 +1454,26 @@ static int _generate_kickstart_packages(struct kickstart_commands *cmds, int out return 0; } +static int _generate_kickstart_post(struct kickstart_commands *cmds, const char *profile_id, const char *input_path, int output_fd) +{ + _write_text_to_fd(output_fd, "%post\n"); + char *oscap_command = oscap_sprintf( + "oscap xccdf eval --remediate --profile '%s' %s\n", + profile_id, input_path); + _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening\n"); + _write_text_to_fd_and_free(output_fd, oscap_command); + 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, "\n"); + } + oscap_iterator_free(post_it); + _write_text_to_fd(output_fd, "%end\n"); + _write_text_to_fd(output_fd, "\n"); + return 0; +} + const char *common_kickstart_header = ( "# Specify installation method to use for installation\n" "# To use a different one comment out the 'url' one below, update\n" @@ -1553,23 +1573,9 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _generate_kickstart_packages(&cmds, output_fd); - _write_text_to_fd(output_fd, "%post\n"); const char *profile_id = xccdf_profile_get_id(xccdf_policy_get_profile(policy)); const char *ds_path = "/usr/share/xml/scap/ssg/content/ssg-xxxxx-ds.xml"; - char *oscap_command = oscap_sprintf( - "oscap xccdf eval --remediate --profile '%s' %s\n", - profile_id, ds_path); - _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening\n"); - _write_text_to_fd_and_free(output_fd, oscap_command); - 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, "\n"); - } - oscap_iterator_free(post_it); - _write_text_to_fd(output_fd, "%end\n"); - _write_text_to_fd(output_fd, "\n"); + _generate_kickstart_post(&cmds, profile_id, ds_path, output_fd); _write_text_to_fd(output_fd, "# Reboot after the installation is complete (optional)\n"); _write_text_to_fd(output_fd, "# --eject - attempt to eject CD or DVD media before rebooting\n"); From ab946f699eee786cf805791dca754fe12b15f696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 10 Jul 2024 14:34:51 +0200 Subject: [PATCH 19/53] Improve the file name of oscap command This will give us the correct file name for most situations. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index bb62c54af7..9f45c6b57b 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1457,9 +1457,13 @@ static int _generate_kickstart_packages(struct kickstart_commands *cmds, int out static int _generate_kickstart_post(struct kickstart_commands *cmds, const char *profile_id, const char *input_path, int output_fd) { _write_text_to_fd(output_fd, "%post\n"); + char *dup = strdup(input_path); + char *basename = oscap_basename(dup); + free(dup); char *oscap_command = oscap_sprintf( - "oscap xccdf eval --remediate --profile '%s' %s\n", - profile_id, input_path); + "oscap xccdf eval --remediate --profile '%s' /usr/share/xml/scap/ssg/content/%s\n", + profile_id, basename); + free(basename); _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening\n"); _write_text_to_fd_and_free(output_fd, oscap_command); struct oscap_iterator *post_it = oscap_iterator_new(cmds->post); @@ -1546,7 +1550,7 @@ const char *common_kickstart_header = ( "bootloader --password=grub.pbkdf2.sha512.10000.45912D32B964BA58B91EAF9847F3CCE6F4C962638922543AFFAEE4D29951757F4336C181E6FC9030E07B7D9874DAD696A1B18978D995B1D7F27AF9C38159FDF3.99F65F3896012A0A3D571A99D6E6C695F3C51BE5343A01C1B6907E1C3E1373CB7F250C2BC66C44BB876961E9071F40205006A05189E51C2C14770C70C723F3FD --iscrypted\n" ); -static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) +static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, const char *input_file_name, int output_fd) { int ret = 0; struct kickstart_commands cmds = { @@ -1574,8 +1578,7 @@ 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)); - const char *ds_path = "/usr/share/xml/scap/ssg/content/ssg-xxxxx-ds.xml"; - _generate_kickstart_post(&cmds, profile_id, ds_path, output_fd); + _generate_kickstart_post(&cmds, profile_id, input_file_name, output_fd); _write_text_to_fd(output_fd, "# Reboot after the installation is complete (optional)\n"); _write_text_to_fd(output_fd, "# --eject - attempt to eject CD or DVD media before rebooting\n"); @@ -1646,7 +1649,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * } 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:kickstart") == 0) { - ret = _xccdf_policy_generate_fix_kickstart(rules_to_fix, policy, sys, output_fd); + ret = _xccdf_policy_generate_fix_kickstart(rules_to_fix, policy, sys, input_file_name, output_fd); } else { ret = _xccdf_policy_generate_fix_other(rules_to_fix, policy, sys, output_fd); } From b212b7cd4603cdb5fd4ed49690f8e5d1c28bc233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 12 Jul 2024 15:27:05 +0200 Subject: [PATCH 20/53] Add ability to define logical partitions The generator will consume `logvol` commands and put them into the output kickstart files. This will be used in rules for partitions, for example in the `partition_for_var` rule. --- docs/manual/manual.adoc | 3 +- src/XCCDF_POLICY/xccdf_policy_remediate.c | 52 ++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index 2de266e2fa..4958e84f5b 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1144,7 +1144,8 @@ Supported commands: * `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 +* `post command` - adds `command` to the `%post` section the kickstart +* `logvol command` - adds `logvol command` to the commands section the kickstart For example, to generate a kickstart for RHEL 9 STIG profile, run: diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 9f45c6b57b..328979e4aa 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -55,6 +55,7 @@ struct kickstart_commands { struct oscap_list *service_enable; struct oscap_list *service_disable; struct oscap_list *post; + struct oscap_list *logvol; }; static int _rule_add_info_message(struct xccdf_rule_result *rr, ...) @@ -906,7 +907,7 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) int ret = 0; char *dup = strdup(line); char **words = oscap_split(dup, " "); - enum states {KS_START, KS_PACKAGE, KS_PACKAGE_INSTALL, KS_PACKAGE_REMOVE, KS_SERVICE, KS_SERVICE_ENABLE, KS_SERVICE_DISABLE, KS_POST}; + enum states {KS_START, KS_PACKAGE, KS_PACKAGE_INSTALL, KS_PACKAGE_REMOVE, KS_SERVICE, KS_SERVICE_ENABLE, KS_SERVICE_DISABLE, KS_LOGVOL, KS_POST}; int state = KS_START; for (unsigned int i = 0; words[i] != NULL; i++) { char *word = words[i]; @@ -918,6 +919,8 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) state = KS_SERVICE; } else if (!strcmp(word, "post")) { state = KS_POST; + } else if (!strcmp(word, "logvol")) { + state = KS_LOGVOL; } else { ret = 1; oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command:'%s'", word, line); @@ -963,6 +966,11 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) /* we need to jump off because we have eaten the whole line */ goto cleanup; break; + case KS_LOGVOL: + oscap_list_add(cmds->logvol, strdup(line)); + /* we need to jump off because we have eaten the whole line */ + goto cleanup; + break; default: break; } @@ -1478,6 +1486,44 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char return 0; } + +const char *common_partition = ( +"# Initialize (format) all disks (optional)\n" +"zerombr\n" +"\n" +"# The following partition layout scheme assumes disk of size 20GB or larger\n" +"# Modify size of partitions appropriately to reflect actual machine's hardware\n" +"#\n" +"# Remove Linux partitions from the system prior to creating new ones (optional)\n" +"# --linux erase all Linux partitions\n" +"# --initlabel initialize the disk label to the default based on the underlying architecture\n" +"clearpart --linux --initlabel\n" +"\n" +"# Create primary system partitions (required for installs)\n" +"part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" +"part pv.01 --grow --size=1\n" +"\n" +"# Create a Logical Volume Management (LVM) group (optional)\n" +"volgroup VolGroup pv.01\n" +"\n" +"# Create particular logical volumes (optional)\n" +"\nlogvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" +); + +static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) +{ + _write_text_to_fd(output_fd, common_partition); + struct oscap_iterator *logvol_it = oscap_iterator_new(cmds->logvol); + while (oscap_iterator_has_more(logvol_it)) { + char *command = (char *) oscap_iterator_next(logvol_it); + _write_text_to_fd(output_fd, command); + _write_text_to_fd(output_fd, "\n"); + } + oscap_iterator_free(logvol_it); + _write_text_to_fd(output_fd, "logvol swap --name=swap --vgname=VolGroup --size=2016\n"); + return 0; +} + const char *common_kickstart_header = ( "# Specify installation method to use for installation\n" "# To use a different one comment out the 'url' one below, update\n" @@ -1559,6 +1605,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, .service_enable = oscap_list_new(), .service_disable = oscap_list_new(), .post = oscap_list_new(), + .logvol = oscap_list_new(), }; struct oscap_iterator *rules_to_fix_it = oscap_iterator_new(rules_to_fix); @@ -1573,6 +1620,8 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _write_text_to_fd(output_fd, common_kickstart_header); _write_text_to_fd(output_fd, "\n"); + _generate_kickstart_logvol(&cmds, output_fd); + _generate_kickstart_services(&cmds, output_fd); _generate_kickstart_packages(&cmds, output_fd); @@ -1589,6 +1638,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, oscap_list_free(cmds.service_enable, free); oscap_list_free(cmds.service_disable, free); oscap_list_free(cmds.post, free); + oscap_list_free(cmds.logvol, free); return ret; } From a39161cdba37112396d8ed41e74a172b31549731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 16 Jul 2024 10:50:29 +0200 Subject: [PATCH 21/53] Improve hardcoded items - network device should be autodetected by Anaconda - firewalld is added seamlessly to the port 22 - combination zerombr + clearpart --linux doesn't make sense --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 328979e4aa..816873a905 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1495,9 +1495,10 @@ const char *common_partition = ( "# Modify size of partitions appropriately to reflect actual machine's hardware\n" "#\n" "# Remove Linux partitions from the system prior to creating new ones (optional)\n" -"# --linux erase all Linux partitions\n" +"# --all erase all partitions\n" "# --initlabel initialize the disk label to the default based on the underlying architecture\n" -"clearpart --linux --initlabel\n" +"clearpart --all --initlabel\n" +"reqpart\n" "\n" "# Create primary system partitions (required for installs)\n" "part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" @@ -1561,10 +1562,8 @@ const char *common_kickstart_header = ( "\n" "# Configure network information for target system and activate network devices in the installer environment (optional)\n" "# --onboot enable device at a boot time\n" -"# --device device to be activated and / or configured with the network command\n" "# --bootproto method to obtain networking configuration for device (default dhcp)\n" -"# --noipv6 disable IPv6 on this device\n" -"network --onboot yes --device eth0 --bootproto dhcp --noipv6\n" +"network --onboot yes --bootproto dhcp\n" "\n" "# Set the system's root password (required)\n" "# Plaintext password is: server\n" @@ -1577,11 +1576,6 @@ const char *common_kickstart_header = ( "# Plaintext password is: admin123\n" "user --name=admin --groups=wheel --password=$6$Ga6ZnIlytrWpuCzO$q0LqT1USHpahzUafQM9jyHCY9BiE5/ahXLNWUMiVQnFGblu0WWGZ1e6icTaCGO4GNgZNtspp1Let/qpM7FMVB0 --iscrypted\n" "\n" -"# Configure firewall settings for the system (optional)\n" -"# --enabled reject incoming connections that are not in response to outbound requests\n" -"# --ssh allow sshd service through the firewall\n" -"firewall --enabled --ssh\n" -"\n" "# State of SELinux on the installed system (optional)\n" "# Defaults to enforcing\n" "selinux --enforcing\n" From 3ca05fee960ce28751a196a6327dad6e74f63e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 18 Jul 2024 11:22:21 +0200 Subject: [PATCH 22/53] Improve and reduce the hardcoded text - Remove optional items - Mark items that are required for security compliance purposes as "required for security compliance" - Minor whitespace improvements --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 104 +++------------------- 1 file changed, 13 insertions(+), 91 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 816873a905..f8b0cd8dc3 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1156,7 +1156,9 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ } if (oscap_streq(sys, "urn:xccdf:fix:script:kickstart")) { - how_to_apply = "# Customize the kickstart for your deployment, then perform operating system installation using this kickstart."; + how_to_apply = "# Review the kickstart and customize the kickstart for your deployment.\n" + "# Pay attention to items marked as \"required for security compliance\".\n" + "# Install the operating system using this kickstart."; format = "kickstart"; remediation_type = "Kickstart"; } @@ -1408,7 +1410,7 @@ static int _generate_kickstart_services(struct kickstart_commands *cmds, int out struct oscap_iterator *service_disable_it = oscap_iterator_new(cmds->service_disable); struct oscap_iterator *service_enable_it = oscap_iterator_new(cmds->service_enable); if (oscap_iterator_has_more(service_disable_it) || oscap_iterator_has_more(service_enable_it)) { - _write_text_to_fd(output_fd, "# Disable and enable systemd services based on the SCAP profile\n"); + _write_text_to_fd(output_fd, "# Disable and enable systemd services (required for security compliance)\n"); _write_text_to_fd(output_fd, "services"); if (oscap_iterator_has_more(service_disable_it)) { _write_text_to_fd(output_fd, " --disabled="); @@ -1437,7 +1439,7 @@ static int _generate_kickstart_services(struct kickstart_commands *cmds, int out static int _generate_kickstart_packages(struct kickstart_commands *cmds, int output_fd) { - _write_text_to_fd(output_fd, "# Packages selection (%packages section is required)\n"); + _write_text_to_fd(output_fd, "# Packages selection (required for security compliance)\n"); _write_text_to_fd(output_fd, "%packages\n"); /* openscap-scanner and scap-security-guide needs to be installed because we will run oscap in the %post section */ _write_text_to_fd(output_fd, "openscap-scanner\n"); @@ -1472,7 +1474,7 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char "oscap xccdf eval --remediate --profile '%s' /usr/share/xml/scap/ssg/content/%s\n", profile_id, basename); free(basename); - _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening\n"); + _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening (required for security compliance)\n"); _write_text_to_fd_and_free(output_fd, oscap_command); struct oscap_iterator *post_it = oscap_iterator_new(cmds->post); while (oscap_iterator_has_more(post_it)) { @@ -1488,108 +1490,33 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char const char *common_partition = ( -"# Initialize (format) all disks (optional)\n" +"# Create partition layout scheme (required for security compliance)\n" "zerombr\n" -"\n" -"# The following partition layout scheme assumes disk of size 20GB or larger\n" -"# Modify size of partitions appropriately to reflect actual machine's hardware\n" -"#\n" -"# Remove Linux partitions from the system prior to creating new ones (optional)\n" -"# --all erase all partitions\n" -"# --initlabel initialize the disk label to the default based on the underlying architecture\n" "clearpart --all --initlabel\n" "reqpart\n" -"\n" -"# Create primary system partitions (required for installs)\n" "part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" "part pv.01 --grow --size=1\n" -"\n" -"# Create a Logical Volume Management (LVM) group (optional)\n" "volgroup VolGroup pv.01\n" -"\n" -"# Create particular logical volumes (optional)\n" -"\nlogvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" +"logvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" +"logvol swap --name=swap --vgname=VolGroup --size=2016\n" ); static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) { - _write_text_to_fd(output_fd, common_partition); struct oscap_iterator *logvol_it = oscap_iterator_new(cmds->logvol); + if (oscap_iterator_has_more(logvol_it)) { + _write_text_to_fd(output_fd, common_partition); + } while (oscap_iterator_has_more(logvol_it)) { char *command = (char *) oscap_iterator_next(logvol_it); _write_text_to_fd(output_fd, command); _write_text_to_fd(output_fd, "\n"); } + _write_text_to_fd(output_fd, "\n"); oscap_iterator_free(logvol_it); - _write_text_to_fd(output_fd, "logvol swap --name=swap --vgname=VolGroup --size=2016\n"); return 0; } -const char *common_kickstart_header = ( -"# Specify installation method to use for installation\n" -"# To use a different one comment out the 'url' one below, update\n" -"# the selected choice with proper options & un-comment it\n" -"#\n" -"# Install from an installation tree on a remote server via FTP or HTTP:\n" -"# --url the URL to install from\n" -"#\n" -"# Example:\n" -"#\n" -"# url --url=http://192.168.122.1/image\n" -"#\n" -"# Modify concrete URL in the above example appropriately to reflect the actual\n" -"# environment machine is to be installed in\n" -"#\n" -"# Other possible / supported installation methods:\n" -"# * install from the first CD-ROM/DVD drive on the system:\n" -"#\n" -"# cdrom\n" -"#\n" -"# * install from a directory of ISO images on a local drive:\n" -"#\n" -"# harddrive --partition=hdb2 --dir=/tmp/install-tree\n" -"#\n" -"# * install from provided NFS server:\n" -"#\n" -"# nfs --server= --dir= [--opts=]\n" -"#\n" -"\n" -"# Set language to use during installation and the default language to use on the installed system (required)\n" -"lang en_US.UTF-8\n" -"\n" -"# Set system keyboard type / layout (required)\n" -"keyboard --vckeymap us\n" -"\n" -"# Configure network information for target system and activate network devices in the installer environment (optional)\n" -"# --onboot enable device at a boot time\n" -"# --bootproto method to obtain networking configuration for device (default dhcp)\n" -"network --onboot yes --bootproto dhcp\n" -"\n" -"# Set the system's root password (required)\n" -"# Plaintext password is: server\n" -"# Refer to e.g. https://pykickstart.readthedocs.io/en/latest/commands.html#rootpw to see how to create\n" -"# encrypted password form for different plaintext password\n" -"rootpw --iscrypted $6$/0RYeeRdK70ynvYz$jH2ZN/80HM6DjndHMxfUF9KIibwipitvizzXDH1zW.fTjyD3RD3tkNdNUaND18B/XqfAUW3vy1uebkBybCuIm0\n" -"\n" -"# The selected profile will restrict root login\n" -"# Add a user that can login and escalate privileges\n" -"# Plaintext password is: admin123\n" -"user --name=admin --groups=wheel --password=$6$Ga6ZnIlytrWpuCzO$q0LqT1USHpahzUafQM9jyHCY9BiE5/ahXLNWUMiVQnFGblu0WWGZ1e6icTaCGO4GNgZNtspp1Let/qpM7FMVB0 --iscrypted\n" -"\n" -"# State of SELinux on the installed system (optional)\n" -"# Defaults to enforcing\n" -"selinux --enforcing\n" -"\n" -"# Set the system time zone (required)\n" -"timezone --utc America/New_York\n" -"\n" -"# Specify how the bootloader should be installed (required)\n" -"# Plaintext password is: password\n" -"# Refer to e.g. grub2-mkpasswd-pbkdf2 to see how to create\n" -"# encrypted password form for different plaintext password\n" -"bootloader --password=grub.pbkdf2.sha512.10000.45912D32B964BA58B91EAF9847F3CCE6F4C962638922543AFFAEE4D29951757F4336C181E6FC9030E07B7D9874DAD696A1B18978D995B1D7F27AF9C38159FDF3.99F65F3896012A0A3D571A99D6E6C695F3C51BE5343A01C1B6907E1C3E1373CB7F250C2BC66C44BB876961E9071F40205006A05189E51C2C14770C70C723F3FD --iscrypted\n" -); - static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, const char *input_file_name, int output_fd) { int ret = 0; @@ -1611,7 +1538,6 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, } oscap_iterator_free(rules_to_fix_it); - _write_text_to_fd(output_fd, common_kickstart_header); _write_text_to_fd(output_fd, "\n"); _generate_kickstart_logvol(&cmds, output_fd); @@ -1623,10 +1549,6 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, const char *profile_id = xccdf_profile_get_id(xccdf_policy_get_profile(policy)); _generate_kickstart_post(&cmds, profile_id, input_file_name, output_fd); - _write_text_to_fd(output_fd, "# Reboot after the installation is complete (optional)\n"); - _write_text_to_fd(output_fd, "# --eject - attempt to eject CD or DVD media before rebooting\n"); - _write_text_to_fd(output_fd, "reboot --eject\n"); - oscap_list_free(cmds.package_install, free); oscap_list_free(cmds.package_remove, free); oscap_list_free(cmds.service_enable, free); From bf2cfd9c0c6a63ea302abe842c2129ff005eab01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 18 Jul 2024 15:20:29 +0200 Subject: [PATCH 23/53] Improve the logvol command format and parsing --- docs/manual/manual.adoc | 2 +- src/XCCDF_POLICY/xccdf_policy_remediate.c | 84 ++++++++++++++++++----- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index 4958e84f5b..6992e299f5 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1145,7 +1145,7 @@ Supported commands: * `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 command` - adds `logvol command` to the commands 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 For example, to generate a kickstart for RHEL 9 STIG profile, run: diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index f8b0cd8dc3..e813bb15b1 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -58,6 +58,11 @@ struct kickstart_commands { struct oscap_list *logvol; }; +struct logvol_cmd { + char *path; + char *size; +}; + static int _rule_add_info_message(struct xccdf_rule_result *rr, ...) { va_list ap; @@ -907,8 +912,20 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) int ret = 0; char *dup = strdup(line); char **words = oscap_split(dup, " "); - enum states {KS_START, KS_PACKAGE, KS_PACKAGE_INSTALL, KS_PACKAGE_REMOVE, KS_SERVICE, KS_SERVICE_ENABLE, KS_SERVICE_DISABLE, KS_LOGVOL, KS_POST}; + enum states { + KS_START, + KS_PACKAGE, + KS_PACKAGE_INSTALL, + KS_PACKAGE_REMOVE, + KS_SERVICE, + KS_SERVICE_ENABLE, + KS_SERVICE_DISABLE, + KS_LOGVOL, + KS_LOGVOL_SIZE, + KS_POST + }; int state = KS_START; + struct logvol_cmd *current_logvol_cmd = NULL; for (unsigned int i = 0; words[i] != NULL; i++) { char *word = words[i]; switch (state) { @@ -967,9 +984,14 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) goto cleanup; break; case KS_LOGVOL: - oscap_list_add(cmds->logvol, strdup(line)); - /* we need to jump off because we have eaten the whole line */ - goto cleanup; + current_logvol_cmd = malloc(sizeof(struct logvol_cmd)); + current_logvol_cmd->path = strdup(word); + state = KS_LOGVOL_SIZE; + break; + case KS_LOGVOL_SIZE: + current_logvol_cmd->size = strdup(word); + oscap_list_add(cmds->logvol, current_logvol_cmd); + current_logvol_cmd = NULL; break; default: break; @@ -1488,17 +1510,34 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char return 0; } +static char *_remove_slash(const char *in) +{ + if (in == NULL) + return NULL; + char *out = malloc(strlen(in)); + char *p = (char *) in; + char *q = out; + while (*p != '\0') { + if (*p != '/') { + *q = *p; + q++; + } + p++; + } + *q = '\0'; + return out; +} const char *common_partition = ( -"# Create partition layout scheme (required for security compliance)\n" -"zerombr\n" -"clearpart --all --initlabel\n" -"reqpart\n" -"part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" -"part pv.01 --grow --size=1\n" -"volgroup VolGroup pv.01\n" -"logvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" -"logvol swap --name=swap --vgname=VolGroup --size=2016\n" + "# Create partition layout scheme (required for security compliance)\n" + "zerombr\n" + "clearpart --all --initlabel\n" + "reqpart\n" + "part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" + "part pv.01 --grow --size=1\n" + "volgroup VolGroup pv.01\n" + "logvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" + "logvol swap --name=swap --vgname=VolGroup --size=2016\n" ); static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) @@ -1508,15 +1547,26 @@ static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int outpu _write_text_to_fd(output_fd, common_partition); } while (oscap_iterator_has_more(logvol_it)) { - char *command = (char *) oscap_iterator_next(logvol_it); - _write_text_to_fd(output_fd, command); - _write_text_to_fd(output_fd, "\n"); + struct logvol_cmd *command = (struct logvol_cmd *) oscap_iterator_next(logvol_it); + char *name = _remove_slash(command->path); + char *fmt = oscap_sprintf("logvol %s --fstype=xfs --name=%s --vgname=VolGroup --size=%s\n", command->path, name, command->size); + _write_text_to_fd(output_fd, fmt); + free(name); + free(fmt); } _write_text_to_fd(output_fd, "\n"); oscap_iterator_free(logvol_it); return 0; } +static void logvol_cmd_free(void *ptr) +{ + struct logvol_cmd *cmd = (struct logvol_cmd *) ptr; + free(cmd->path); + free(cmd->size); + free(cmd); +} + static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, const char *input_file_name, int output_fd) { int ret = 0; @@ -1554,7 +1604,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, oscap_list_free(cmds.service_enable, free); oscap_list_free(cmds.service_disable, free); oscap_list_free(cmds.post, free); - oscap_list_free(cmds.logvol, free); + oscap_list_free(cmds.logvol, logvol_cmd_free); return ret; } From 796cf3278aad1ee46a36c0a19fe361c87540dde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 18 Jul 2024 16:01:00 +0200 Subject: [PATCH 24/53] Extend test test_remediation_kickstart.sh --- .../XCCDF/unittests/test_remediation_kickstart.ds.xml | 11 +++++++++-- .../API/XCCDF/unittests/test_remediation_kickstart.sh | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml index 96f9f812ef..c5ec071617 100644 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml @@ -58,6 +58,7 @@ Rule 1: Enable Audit Service @@ -85,7 +86,7 @@ - Rule 4: Install and enable SSHD + Rule 5: Install and enable SSHD # openssh-server will go to %packages section package install openssh-server @@ -97,6 +98,12 @@ post mkdir /etc/scap + + Rule 6: Configure all partitions + + logvol /var/tmp 1024 + + - \ No newline at end of file + diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index 65b2b5aebd..a08dd874e5 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -3,6 +3,7 @@ set -e -o pipefail + kickstart=$(mktemp) stderr=$(mktemp) @@ -20,7 +21,11 @@ stderr=$(mktemp) $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 '# Kickstart for Common hardening profile' "$kickstart" +grep -q 'services --disabled=telnet --enabled=auditd,rsyslog,sshd' "$kickstart" +grep -q 'logvol /var/tmp --fstype=xfs --name=vartmp --vgname=VolGroup --size=1024' "$kickstart" +grep -q 'mkdir /etc/scap' "$kickstart" +grep -q '\-usbguard' "$kickstart" rm -rf "$kickstart" rm -rf "$stderr" From ea73415e73ad65a5083dde1fe5a7a1523811dbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 18 Jul 2024 16:51:47 +0200 Subject: [PATCH 25/53] Add boot loader options --- docs/manual/manual.adoc | 1 + src/XCCDF_POLICY/xccdf_policy_remediate.c | 34 ++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index 6992e299f5..00bca2daa6 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1146,6 +1146,7 @@ Supported commands: * `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=value` - adds `option=value` to list in the `--append=` option in the `bootloader` command in commands section in the kickstart For example, to generate a kickstart for RHEL 9 STIG profile, run: diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index e813bb15b1..9ab8c21275 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -56,6 +56,7 @@ struct kickstart_commands { struct oscap_list *service_disable; struct oscap_list *post; struct oscap_list *logvol; + struct oscap_list *bootloader; }; struct logvol_cmd { @@ -922,7 +923,8 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) KS_SERVICE_DISABLE, KS_LOGVOL, KS_LOGVOL_SIZE, - KS_POST + KS_POST, + KS_BOOTLOADER, }; int state = KS_START; struct logvol_cmd *current_logvol_cmd = NULL; @@ -938,6 +940,8 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) state = KS_POST; } else if (!strcmp(word, "logvol")) { state = KS_LOGVOL; + } else if (!strcmp(word, "bootloader")) { + state = KS_BOOTLOADER; } else { ret = 1; oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command:'%s'", word, line); @@ -993,6 +997,9 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) oscap_list_add(cmds->logvol, current_logvol_cmd); current_logvol_cmd = NULL; break; + case KS_BOOTLOADER: + oscap_list_add(cmds->bootloader, strdup(word)); + break; default: break; } @@ -1559,6 +1566,27 @@ static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int outpu return 0; } +static int _generate_kickstart_bootloader(struct kickstart_commands *cmds, int output_fd) +{ + struct oscap_iterator *bl_it = oscap_iterator_new(cmds->bootloader); + if (!oscap_iterator_has_more(bl_it)) { + oscap_iterator_free(bl_it); + return 0; + } + _write_text_to_fd(output_fd, "# Configure boot loader options (required for security compliance)\n"); + _write_text_to_fd(output_fd, "bootloader --append=\""); + while (oscap_iterator_has_more(bl_it)) { + char *optval = (char *) oscap_iterator_next(bl_it); + _write_text_to_fd(output_fd, optval); + if (oscap_iterator_has_more(bl_it)) + _write_text_to_fd(output_fd, " "); + } + _write_text_to_fd(output_fd, "\"\n"); + _write_text_to_fd(output_fd, "\n"); + oscap_iterator_free(bl_it); + return 0; +} + static void logvol_cmd_free(void *ptr) { struct logvol_cmd *cmd = (struct logvol_cmd *) ptr; @@ -1577,6 +1605,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, .service_disable = oscap_list_new(), .post = oscap_list_new(), .logvol = oscap_list_new(), + .bootloader = oscap_list_new(), }; struct oscap_iterator *rules_to_fix_it = oscap_iterator_new(rules_to_fix); @@ -1592,6 +1621,8 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, _generate_kickstart_logvol(&cmds, output_fd); + _generate_kickstart_bootloader(&cmds, output_fd); + _generate_kickstart_services(&cmds, output_fd); _generate_kickstart_packages(&cmds, output_fd); @@ -1605,6 +1636,7 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, oscap_list_free(cmds.service_disable, free); oscap_list_free(cmds.post, free); oscap_list_free(cmds.logvol, logvol_cmd_free); + oscap_list_free(cmds.bootloader, free); return ret; } From ec8da8d2b9ed81086f902217b12e39a1c99baa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 19 Jul 2024 09:39:39 +0200 Subject: [PATCH 26/53] Change partitioning defaults - Remove part /boot and change reqpart to reqpart --add-boot, the mount options should be remediated by oscap in %post - Rename VolGroup to system, to differentiate it from other VGs added by a sysadmin for non-OS storage - Remove all --fstype=xfs and just use RHEL default - that's currently XFS, but may be some other filesystem on a future RHEL version - Change / size from 10240 to 2000 (2GB), current minimal install is ~200 MB and if profiles add partitions for /usr and/or /var, then we'll have just a few MBs used on / - Resize swap to 1000 (1GB), using 2016 seems random --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 9ab8c21275..46049a4615 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1539,12 +1539,11 @@ const char *common_partition = ( "# Create partition layout scheme (required for security compliance)\n" "zerombr\n" "clearpart --all --initlabel\n" - "reqpart\n" - "part /boot --fstype=xfs --size=512 --fsoptions=\"nodev,nosuid,noexec\"\n" + "reqpart --add-boot\n" "part pv.01 --grow --size=1\n" - "volgroup VolGroup pv.01\n" - "logvol / --fstype=xfs --name=root --vgname=VolGroup --size=10240 --grow\n" - "logvol swap --name=swap --vgname=VolGroup --size=2016\n" + "volgroup system pv.01\n" + "logvol / --name=root --vgname=system --size=2000 --grow\n" + "logvol swap --name=swap --vgname=system --size=1000\n" ); static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) @@ -1556,7 +1555,7 @@ static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int outpu while (oscap_iterator_has_more(logvol_it)) { struct logvol_cmd *command = (struct logvol_cmd *) oscap_iterator_next(logvol_it); char *name = _remove_slash(command->path); - char *fmt = oscap_sprintf("logvol %s --fstype=xfs --name=%s --vgname=VolGroup --size=%s\n", command->path, name, command->size); + char *fmt = oscap_sprintf("logvol %s --name=%s --vgname=system --size=%s\n", command->path, name, command->size); _write_text_to_fd(output_fd, fmt); free(name); free(fmt); From 3730e55b49625937638c8273bef0f6625467926a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 19 Jul 2024 10:03:32 +0200 Subject: [PATCH 27/53] Update expected line in the unit test --- tests/API/XCCDF/unittests/test_remediation_kickstart.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index a08dd874e5..d9a60ec3dd 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -23,7 +23,7 @@ $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile c grep -q '# Kickstart for Common hardening profile' "$kickstart" grep -q 'services --disabled=telnet --enabled=auditd,rsyslog,sshd' "$kickstart" -grep -q 'logvol /var/tmp --fstype=xfs --name=vartmp --vgname=VolGroup --size=1024' "$kickstart" +grep -q 'logvol /var/tmp --name=vartmp --vgname=system --size=1024' "$kickstart" grep -q 'mkdir /etc/scap' "$kickstart" grep -q '\-usbguard' "$kickstart" From 77ee27b74ee7a620a7ee8df2aa0628ce870ae809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 19 Jul 2024 11:12:30 +0200 Subject: [PATCH 28/53] Handle more unexpected situations --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 46049a4615..09660f0a3e 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -925,11 +925,14 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) KS_LOGVOL_SIZE, KS_POST, KS_BOOTLOADER, + KS_ERROR }; int state = KS_START; struct logvol_cmd *current_logvol_cmd = NULL; for (unsigned int i = 0; words[i] != NULL; i++) { - char *word = words[i]; + char *word = oscap_trim(words[i]); + if (*word == '\0') + continue; switch (state) { case KS_START: if (!strcmp(word, "package")) { @@ -972,7 +975,7 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) state = KS_SERVICE_DISABLE; } else { ret = 1; - oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'service' command keyword '%s' in command:'%s'", word, line); + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported 'service' command keyword '%s' in command: '%s'", word, line); goto cleanup; } break; @@ -996,10 +999,15 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) current_logvol_cmd->size = strdup(word); oscap_list_add(cmds->logvol, current_logvol_cmd); current_logvol_cmd = NULL; + state = KS_ERROR; break; case KS_BOOTLOADER: oscap_list_add(cmds->bootloader, strdup(word)); break; + case KS_ERROR: + ret = 1; + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unexpected string '%s' in command: '%s'", word, line); + goto cleanup; default: break; } From 954cdf0cf8d44cd56787025ad568eb89d005a22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 19 Jul 2024 14:24:25 +0200 Subject: [PATCH 29/53] Extend tests to cover error situations Add tests to cover handling unsupported or broken kickstart commands. Also, reorganize the test code for better readability. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 2 +- .../unittests/test_remediation_kickstart.sh | 91 +++++++++++++---- .../test_remediation_kickstart_invalid.ds.xml | 99 +++++++++++++++++++ 3 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 tests/API/XCCDF/unittests/test_remediation_kickstart_invalid.ds.xml diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 09660f0a3e..84b8d52896 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -947,7 +947,7 @@ static int _parse_line(const char *line, struct kickstart_commands *cmds) state = KS_BOOTLOADER; } else { ret = 1; - oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command:'%s'", word, line); + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Unsupported command keyword '%s' in command: '%s'", word, line); goto cleanup; } break; diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index d9a60ec3dd..5c0cdaf212 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -3,29 +3,86 @@ set -e -o pipefail +function test_normal { + kickstart=$(mktemp) + stderr=$(mktemp) -kickstart=$(mktemp) -stderr=$(mktemp) + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile common "$srcdir/test_remediation_kickstart.ds.xml" -$OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --result-id xccdf_org.open-scap_testresult_xccdf_org.ssgproject.content_profile_ospp "$srcdir/test_remediation_kickstart.ds.xml" 2> "$stderr" || ret=$? + 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" -[ $ret = 1 ] -grep -q "It isn't possible to generate results-oriented Kickstarts." $stderr + rm -rf "$kickstart" + rm -rf "$stderr" +} -rm -rf "$kickstart" -rm -rf "$stderr" +function test_results_oriented { + kickstart=$(mktemp) + stderr=$(mktemp) + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --result-id xccdf_org.open-scap_testresult_xccdf_org.ssgproject.content_profile_ospp "$srcdir/test_remediation_kickstart.ds.xml" 2> "$stderr" || ret=$? -kickstart=$(mktemp) -stderr=$(mktemp) + [ $ret = 1 ] + grep -q "It isn't possible to generate results-oriented Kickstarts." $stderr -$OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile common "$srcdir/test_remediation_kickstart.ds.xml" + rm -rf "$kickstart" + rm -rf "$stderr" +} -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" +function test_error_service { + kickstart=$(mktemp) + stderr=$(mktemp) -rm -rf "$kickstart" -rm -rf "$stderr" + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile broken_service "$srcdir/test_remediation_kickstart_invalid.ds.xml" 2> "$stderr" + + grep -q "Unsupported 'service' command keyword 'slow' in command: 'service slow down'" "$stderr" + + rm -rf "$kickstart" + rm -rf "$stderr" +} + +function test_error_package { + kickstart=$(mktemp) + stderr=$(mktemp) + + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile broken_package "$srcdir/test_remediation_kickstart_invalid.ds.xml" 2> "$stderr" + + grep -q "Unsupported 'package' command keyword 'build' in command:'package build sources'" "$stderr" + + rm -rf "$kickstart" + rm -rf "$stderr" +} + +function test_error_logvol { + kickstart=$(mktemp) + stderr=$(mktemp) + + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile broken_logvol "$srcdir/test_remediation_kickstart_invalid.ds.xml" 2> "$stderr" + + grep -q "Unexpected string 'crypto' in command: 'logvol /var 158 crypto'" "$stderr" + + rm -rf "$kickstart" + rm -rf "$stderr" +} + +function test_error_unknown { + kickstart=$(mktemp) + stderr=$(mktemp) + + $OSCAP xccdf generate fix --fix-type kickstart --output "$kickstart" --profile unknown_command "$srcdir/test_remediation_kickstart_invalid.ds.xml" 2> "$stderr" + + grep -q "Unsupported command keyword 'unknown' in command: 'unknown command'" "$stderr" + + rm -rf "$kickstart" + rm -rf "$stderr" +} + +test_normal +test_results_oriented +test_error_service +test_error_package +test_error_logvol +test_error_unknown diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart_invalid.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart_invalid.ds.xml new file mode 100644 index 0000000000..daeb52d425 --- /dev/null +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart_invalid.ds.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + 5.11.2 + 2021-02-01T08:07:06+01:00 + + + + + PASS + pass + + + + + + + + + + + + + + oval:org.openscap.www:var:1 + + + + + 100 + + + + + + + accepted + 1.0 + + Common hardening profile + This is a very cool profile + + + + Common hardening profile + This is a very cool profile + + + + Rule 1: Unsupported service command + + service slow down + + + + Rule 2: Unsupported package command + + package build sources + + + + Rule 3: Logvol command with offending trail + + logvol /var 158 crypto + + + + Rule 4: Unknown command + + unknown command + + + + + From f7d0e4a283dab044349145678164a247e54c1b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 22 Jul 2024 11:48:59 +0200 Subject: [PATCH 30/53] Replace custom fiction by oscap_strrm --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 84b8d52896..4d7bc9a5f5 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1525,24 +1525,6 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char return 0; } -static char *_remove_slash(const char *in) -{ - if (in == NULL) - return NULL; - char *out = malloc(strlen(in)); - char *p = (char *) in; - char *q = out; - while (*p != '\0') { - if (*p != '/') { - *q = *p; - q++; - } - p++; - } - *q = '\0'; - return out; -} - const char *common_partition = ( "# Create partition layout scheme (required for security compliance)\n" "zerombr\n" @@ -1562,7 +1544,8 @@ static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int outpu } while (oscap_iterator_has_more(logvol_it)) { struct logvol_cmd *command = (struct logvol_cmd *) oscap_iterator_next(logvol_it); - char *name = _remove_slash(command->path); + char *name = strdup(command->path); + oscap_strrm(name, "/"); char *fmt = oscap_sprintf("logvol %s --name=%s --vgname=system --size=%s\n", command->path, name, command->size); _write_text_to_fd(output_fd, fmt); free(name); From 96abfeb56dd5ac230a704b0eb83238d24284c20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 22 Jul 2024 15:42:35 +0200 Subject: [PATCH 31/53] Generate results and reports from the remediation in post phase --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 4d7bc9a5f5..1da29106f0 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1508,10 +1508,11 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char char *basename = oscap_basename(dup); free(dup); char *oscap_command = oscap_sprintf( - "oscap xccdf eval --remediate --profile '%s' /usr/share/xml/scap/ssg/content/%s\n", + "oscap xccdf eval --remediate --results-arf /root/openscap_data/arf.xml --report /root/openscap_data/report.html --profile '%s' /usr/share/xml/scap/ssg/content/%s\n", profile_id, basename); free(basename); _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening (required for security compliance)\n"); + _write_text_to_fd(output_fd, "mkdir -p /root/openscap_data\n"); _write_text_to_fd_and_free(output_fd, oscap_command); struct oscap_iterator *post_it = oscap_iterator_new(cmds->post); while (oscap_iterator_has_more(post_it)) { From 5e7a16f409ae3fc3ce4a3aa90d92c5dfd55b93f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 17:17:24 +0200 Subject: [PATCH 32/53] Introduce function xccdf_session_get_user_tailoring_file --- src/XCCDF/public/xccdf_session.h | 2 ++ src/XCCDF/xccdf_session.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h index fbadeccd2e..7987c7d07e 100644 --- a/src/XCCDF/public/xccdf_session.h +++ b/src/XCCDF/public/xccdf_session.h @@ -240,6 +240,8 @@ OSCAP_API void xccdf_session_set_user_cpe(struct xccdf_session *session, const c */ OSCAP_API void xccdf_session_set_user_tailoring_file(struct xccdf_session *session, const char *user_tailoring_file); +OSCAP_API struct oscap_source *xccdf_session_get_user_tailoring_file(struct xccdf_session *session); + /** * Set ID of Tailoring component for the session. This function is applicable * only before session loads. It has no effect if run afterwards. diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c index 5ec127be68..a2fd8cd544 100644 --- a/src/XCCDF/xccdf_session.c +++ b/src/XCCDF/xccdf_session.c @@ -502,6 +502,11 @@ void xccdf_session_set_user_tailoring_file(struct xccdf_session *session, const oscap_source_new_from_file(user_tailoring_file) : NULL; } +struct oscap_source *xccdf_session_get_user_tailoring_file(struct xccdf_session *session) +{ + return session->tailoring.user_file; +} + void xccdf_session_set_user_tailoring_cid(struct xccdf_session *session, const char *user_tailoring_cid) { free(session->tailoring.user_component_id); From d1203d50157ee64386f8188f181c92d77c3407a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 17:18:45 +0200 Subject: [PATCH 33/53] Introduce function oscap_source_to_fd --- src/source/oscap_source.c | 11 +++++++++++ src/source/public/oscap_source.h | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/source/oscap_source.c b/src/source/oscap_source.c index 59ca9dbafd..2acbe88b27 100644 --- a/src/source/oscap_source.c +++ b/src/source/oscap_source.c @@ -443,6 +443,17 @@ int oscap_source_save_as(struct oscap_source *source, const char *filename) return oscap_xml_save_filename(target, doc) == 1 ? 0 : -1; } +int oscap_source_to_fd(struct oscap_source *source, int fd) +{ + xmlDoc *doc = oscap_source_get_xmlDoc(source); + if (doc == NULL) { + oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not save document: DOM representation not available."); + return -1; + } + return oscap_xml_save_fd(fd, doc) == 1 ? 0 : -1; + +} + int oscap_source_get_raw_memory(struct oscap_source *source, char **buffer, size_t *size) { if (source->origin.memory != NULL) { diff --git a/src/source/public/oscap_source.h b/src/source/public/oscap_source.h index a1469fc4ec..06a8dda9af 100644 --- a/src/source/public/oscap_source.h +++ b/src/source/public/oscap_source.h @@ -165,6 +165,15 @@ OSCAP_API const char *oscap_source_readable_origin(const struct oscap_source *so */ OSCAP_API int oscap_source_save_as(struct oscap_source *source, const char *filename); +/** + * Store the resource represented by oscap_source to the file descriptor. + * @memberof oscap_source + * @param source The oscap_source to save + * @param fd file descriptor + * @returns 0 on success, 1 or -1 to indicate error + */ +OSCAP_API int oscap_source_to_fd(struct oscap_source *source, int fd); + /** * Retrieve contents refered to by oscap_source as raw memory. * The memory is always copied. If the origin of oscap_source is raw memory, From e8513a58147243887afb916de8f90795a8468fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 17:20:21 +0200 Subject: [PATCH 34/53] Introduce function oscap_xml_save_fd --- src/common/elements.c | 22 ++++++++++++++++++++++ src/common/elements.h | 11 ++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/common/elements.c b/src/common/elements.c index de4be88f12..6870509dfe 100644 --- a/src/common/elements.c +++ b/src/common/elements.c @@ -250,6 +250,28 @@ int oscap_xml_save_filename(const char *filename, xmlDocPtr doc) return (xmlCode >= 1) ? 1 : -1; } +int oscap_xml_save_fd(int fd, xmlDocPtr doc) +{ + xmlOutputBufferPtr buff; + int xmlCode; + + buff = xmlOutputBufferCreateFd(fd, NULL); + if (buff == NULL) { + close(fd); + oscap_setxmlerr(xmlGetLastError()); + dW("xmlOutputBufferCreateFile() failed."); + return -1; + } + + xmlCode = xmlSaveFormatFileTo(buff, doc, "UTF-8", 1); + if (xmlCode <= 0) { + oscap_setxmlerr(xmlGetLastError()); + dW("No bytes exported: xmlCode: %d.", xmlCode); + } + + return (xmlCode >= 1) ? 1 : -1; +} + int oscap_xml_save_filename_free(const char *filename, xmlDocPtr doc) { int ret = oscap_xml_save_filename(filename, doc); diff --git a/src/common/elements.h b/src/common/elements.h index aea6f9b830..a8f1cee5bf 100644 --- a/src/common/elements.h +++ b/src/common/elements.h @@ -64,10 +64,19 @@ xmlNode *oscap_xmlstr_to_dom(xmlNode *parent, const char *elname, const char *co * Save XML Document to the file of the given filename. * @param filename path to the file * @param doc the XML document content - * @return 1 on success, -1 on failure (oscap_seterr is set appropriatly). + * @return 1 on success, -1 on failure (oscap_seterr is set appropriately). */ int oscap_xml_save_filename(const char *filename, xmlDocPtr doc); +/** + * Save XML Document to the given file descriptor. + * The file descriptor isn't closed by this function. + * @param fd file descriptor + * @param doc the XML document content + * @return 1 on success, -1 on failure (oscap_seterr is set appropriately). + */ +int oscap_xml_save_fd(int fd, xmlDocPtr doc); + /** * Save XML Document to the file of the given filename and dispose the document afterwards. * @param filename path to the file From 8d0e338a7aaf72906810f2a07b82b5d7cac9837b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 17:20:44 +0200 Subject: [PATCH 35/53] Support using tailoring to generate kickstarts The tailoring file will be embedded to the generated kickstart. During the post installation phase the tailoring file will be automatically extracted from the kickstart so that it can be consumed by the oscap command called in the post installation phase. --- src/XCCDF_POLICY/public/xccdf_policy.h | 3 +- src/XCCDF_POLICY/xccdf_policy_remediate.c | 43 ++++++++++++++++------- utils/oscap-xccdf.c | 5 +-- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/XCCDF_POLICY/public/xccdf_policy.h b/src/XCCDF_POLICY/public/xccdf_policy.h index 0f171ae2b9..9cdde59a17 100644 --- a/src/XCCDF_POLICY/public/xccdf_policy.h +++ b/src/XCCDF_POLICY/public/xccdf_policy.h @@ -516,10 +516,11 @@ OSCAP_API bool xccdf_policy_resolve(struct xccdf_policy * policy); * based solely on the XCCDF Policy (xccdf:Profile). * @param sys Consider only those fixes that have @system attribute equal to sys * @param input_file_name file name of the input SCAP file + * @param tailoring input tailoring file (parsed as oscap source) * @param output_fd write prescription to this file descriptor * @returns zero on success, non-zero indicate partial (incomplete) output. */ -OSCAP_API int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, int output_fd); +OSCAP_API int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, struct oscap_source *tailoring, int output_fd); /** * xccdf_policy_model_get_files and xccdf_item_get_files each return oscap_file_entries instead of raw strings diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 1da29106f0..cfbcfdc592 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1159,7 +1159,7 @@ static char *_comment_multiline_text(char *text) return buffer; } -static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, int output_fd) +static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, const char *tailoring_file_name, 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:redhat:osbuild:blueprint") || oscap_streq(sys, "urn:xccdf:fix:script:kickstart") )) @@ -1228,6 +1228,7 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ 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"; + char *tailoring_option = tailoring_file_name ? oscap_sprintf(" --tailoring-file %s", tailoring_file_name) : strdup(""); fix_header = oscap_sprintf( "%s" @@ -1244,7 +1245,7 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ "# XCCDF Version: %s\n" "#\n" "# This file was generated by OpenSCAP %s using:\n" - "# $ oscap xccdf generate fix --profile %s --fix-type %s %s\n" + "# $ oscap xccdf generate fix%s --profile %s --fix-type %s %s\n" "#\n" "# This %s is generated from an OpenSCAP profile without preliminary evaluation.\n" "# It attempts to fix every selected rule, even if the system is already compliant.\n" @@ -1256,10 +1257,11 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ shebang_with_newline, remediation_type, profile_title, commented_profile_description, profile_id, benchmark_id, benchmark_version_info, xccdf_version_name, - oscap_version, profile_id, format, input_file_name, remediation_type, + oscap_version, tailoring_option, profile_id, format, input_file_name, remediation_type, remediation_type, how_to_apply ); + free(tailoring_option); free(commented_profile_description); } else { @@ -1501,18 +1503,32 @@ static int _generate_kickstart_packages(struct kickstart_commands *cmds, int out return 0; } -static int _generate_kickstart_post(struct kickstart_commands *cmds, const char *profile_id, const char *input_path, int output_fd) +static void _write_tailoring_to_fd(struct oscap_source *tailoring, int output_fd) +{ + if (tailoring == NULL) + return; + _write_text_to_fd(output_fd, "cat >/root/openscap_data/tailoring.xml <post); while (oscap_iterator_has_more(post_it)) { @@ -1586,7 +1602,7 @@ static void logvol_cmd_free(void *ptr) free(cmd); } -static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, const char *input_file_name, int output_fd) +static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, const char *input_file_name, struct oscap_source *tailoring, int output_fd) { int ret = 0; struct kickstart_commands cmds = { @@ -1619,7 +1635,7 @@ 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, output_fd); + _generate_kickstart_post(&cmds, profile_id, input_file_name, tailoring, output_fd); oscap_list_free(cmds.package_install, free); oscap_list_free(cmds.package_remove, free); @@ -1631,10 +1647,11 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, return ret; } -int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, int output_fd) +int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, const char *input_file_name, struct oscap_source *tailoring, int output_fd) { __attribute__nonnull__(policy); int ret = 0; + const char *tailoring_file_name = tailoring ? oscap_source_get_filepath(tailoring) : NULL; struct oscap_list *rules_to_fix = oscap_list_new(); if (result == NULL) { @@ -1648,7 +1665,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * return 1; } - if (_write_script_header_to_fd(policy, result, sys, input_file_name, output_fd) != 0) { + if (_write_script_header_to_fd(policy, result, sys, input_file_name, tailoring_file_name, output_fd) != 0) { oscap_list_free(rules_to_fix, NULL); return 1; } @@ -1665,7 +1682,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * else { dI("Generating result-oriented fixes for policy(result/@id=%s)", xccdf_result_get_id(result)); - if (_write_script_header_to_fd(policy, result, sys, input_file_name, output_fd) != 0) { + if (_write_script_header_to_fd(policy, result, sys, input_file_name, tailoring_file_name, output_fd) != 0) { oscap_list_free(rules_to_fix, NULL); return 1; } @@ -1688,7 +1705,7 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * } 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:kickstart") == 0) { - ret = _xccdf_policy_generate_fix_kickstart(rules_to_fix, policy, sys, input_file_name, output_fd); + ret = _xccdf_policy_generate_fix_kickstart(rules_to_fix, policy, sys, input_file_name, tailoring, 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 fe69893743..d4d191ea25 100644 --- a/utils/oscap-xccdf.c +++ b/utils/oscap-xccdf.c @@ -1033,6 +1033,7 @@ int app_generate_fix(const struct oscap_action *action) } } + struct oscap_source *tailoring = xccdf_session_get_user_tailoring_file(session); if (action->id != NULL) { /* Result-oriented fixes */ if (xccdf_session_build_policy_from_testresult(session, action->id) != 0) @@ -1040,7 +1041,7 @@ int app_generate_fix(const struct oscap_action *action) struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session); struct xccdf_result *result = xccdf_policy_get_result_by_id(policy, xccdf_session_get_result_id(session)); - if (xccdf_policy_generate_fix(policy, result, remediation_system, action->f_xccdf, output_fd) == 0) + if (xccdf_policy_generate_fix(policy, result, remediation_system, action->f_xccdf, tailoring, output_fd) == 0) ret = OSCAP_OK; } else { // Fallback to profile if result id is missing /* Profile-oriented fixes */ @@ -1054,7 +1055,7 @@ int app_generate_fix(const struct oscap_action *action) } } struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session); - if (xccdf_policy_generate_fix(policy, NULL, remediation_system, action->f_xccdf, output_fd) == 0) + if (xccdf_policy_generate_fix(policy, NULL, remediation_system, action->f_xccdf, tailoring, output_fd) == 0) ret = OSCAP_OK; } cleanup2: From fb3fb8a86f1bf2faaecae051417b91a3bf6a2f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 17:26:19 +0200 Subject: [PATCH 36/53] Add a simple test generating kickstart with tailoring We will test that the tailoring is applied and that is embedded in the generated kickstart file. --- .../test_remediation_kickstart.ds.xml | 12 ++++++------ .../unittests/test_remediation_kickstart.sh | 18 ++++++++++++++++++ .../test_remediation_kickstart.tailoring.xml | 12 ++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 tests/API/XCCDF/unittests/test_remediation_kickstart.tailoring.xml diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml index c5ec071617..10e4008cb7 100644 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml @@ -60,32 +60,32 @@ - + Rule 1: Enable Audit Service service enable auditd - + Rule 2: Install rsyslog package package install rsyslog - + Rule 3: Enable Rsyslog Service # this command should end up in the command section service enable rsyslog - + Rule 4: Remove USBGuard package remove usbguard - + Rule 5: Install and enable SSHD # openssh-server will go to %packages section @@ -98,7 +98,7 @@ post mkdir /etc/scap - + Rule 6: Configure all partitions logvol /var/tmp 1024 diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index 5c0cdaf212..b245f61f19 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -19,6 +19,23 @@ function test_normal { rm -rf "$stderr" } +function test_tailoring { + kickstart=$(mktemp) + stderr=$(mktemp) + + $OSCAP xccdf generate fix --tailoring-file "$srcdir/test_remediation_kickstart.tailoring.xml" --fix-type kickstart --output "$kickstart" --profile custom "$srcdir/test_remediation_kickstart.ds.xml" + + grep -q 'services --enabled=auditd,rsyslog' "$kickstart" + ! grep -q 'openssh-server' "$kickstart" + grep -q 'cat >/root/openscap_data/tailoring.xml < + + + 1 + + + + + + + + From f39cff4b5a40037d440b806c6cff2b60774f8beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 23 Jul 2024 18:00:02 +0200 Subject: [PATCH 37/53] Add a reboot command This will help to achieve a fully automated installation. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index cfbcfdc592..ea055e0f07 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1637,6 +1637,8 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, 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); + _write_text_to_fd(output_fd, "# Reboot after the installation is complete\nreboot\n"); + oscap_list_free(cmds.package_install, free); oscap_list_free(cmds.package_remove, free); oscap_list_free(cmds.service_enable, free); From 48b1043bce4535beaa22ec9cb48b8c18a13fec35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Wed, 24 Jul 2024 09:07:40 +0200 Subject: [PATCH 38/53] Do not create /root/openscap_data directory In IB we put them right in the /root by default. Also, oscap_data does not follow XDG specs. As our Kickstart will become more complex we could run in race condition with it. The /root OTOH is always there. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 7 +++---- tests/API/XCCDF/unittests/test_remediation_kickstart.sh | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index ea055e0f07..268bb4a651 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1507,7 +1507,7 @@ static void _write_tailoring_to_fd(struct oscap_source *tailoring, int output_fd { if (tailoring == NULL) return; - _write_text_to_fd(output_fd, "cat >/root/openscap_data/tailoring.xml </root/oscap_tailoring.xml <post); diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index b245f61f19..a593143422 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -27,9 +27,9 @@ function test_tailoring { grep -q 'services --enabled=auditd,rsyslog' "$kickstart" ! grep -q 'openssh-server' "$kickstart" - grep -q 'cat >/root/openscap_data/tailoring.xml </root/oscap_tailoring.xml < Date: Thu, 25 Jul 2024 10:04:52 +0200 Subject: [PATCH 39/53] Fail if remediation fails We will have a dedicated post section for compliance hardening. This section will fail if oscap command fails. It helps us prevent partially or wrongly hardened systems. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 268bb4a651..75af8329a2 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1514,7 +1514,8 @@ static void _write_tailoring_to_fd(struct oscap_source *tailoring, int output_fd static int _generate_kickstart_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, "%post\n"); + _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening (required for security compliance)\n"); + _write_text_to_fd(output_fd, "%post --erroronfail\n"); const char *fmt; if (tailoring != NULL) { fmt = "oscap xccdf eval --remediate --tailoring-file /root/oscap_tailoring.xml --results-arf /root/oscap_arf.xml --report /root/oscap_report.html --profile '%s' /usr/share/xml/scap/ssg/content/%s\n"; @@ -1526,9 +1527,9 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char free(dup); char *oscap_command = oscap_sprintf(fmt, profile_id, basename); free(basename); - _write_text_to_fd(output_fd, "# Perform OpenSCAP hardening (required for security compliance)\n"); _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 ]\n"); 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); From f4a35f784f002a3e3b26777129fc265195b8a8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 25 Jul 2024 10:06:03 +0200 Subject: [PATCH 40/53] Add default values for fully automated installation The intention is to be able to perform the installation fully automatically without manual intervention during the installation process. We will add sensible default values to the kickstart so that Anaconda won't ask for setting these options. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 75af8329a2..952663750e 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1625,6 +1625,17 @@ static int _xccdf_policy_generate_fix_kickstart(struct oscap_list *rules_to_fix, oscap_iterator_free(rules_to_fix_it); _write_text_to_fd(output_fd, "\n"); + const char *common = ( + "# Default values for automated installation\n" + "lang en_US.UTF-8\n" + "keyboard --vckeymap us\n" + "timezone --utc America/New_York\n" + "\n" + "# Root password is required for system rescue tasks\n" + "rootpw changeme\n" + "\n" + ); + _write_text_to_fd(output_fd, common); _generate_kickstart_logvol(&cmds, output_fd); From 563cf6bb7535bc5bcd2da835c9d40acb0b506c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 25 Jul 2024 11:05:41 +0200 Subject: [PATCH 41/53] Add a default partition layout We will add a fallback partition layout for profiles that don't specify partitioning layout, for example RHEL 9 PCI-DSS profile. We need to specify at least some layout in the kickstart to make the installation fully automated. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 952663750e..356e0215e3 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1553,11 +1553,26 @@ const char *common_partition = ( "logvol swap --name=swap --vgname=system --size=1000\n" ); +/* Fallback partition layout for profiles that don't specify partitioning layout */ +/* We need to specify at least some layout in the kickstart to make the installation fully automated. */ +const char *fallback_partition = ( + "# Create partition layout scheme (optional)\n" + "zerombr\n" + "clearpart --all --initlabel\n" + "reqpart --add-boot\n" + "part pv.01 --grow --size=1\n" + "volgroup system pv.01\n" + "logvol / --name=root --vgname=system --size=10000 --grow\n" + "logvol swap --name=swap --vgname=system --size=1000\n" +); + static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) { struct oscap_iterator *logvol_it = oscap_iterator_new(cmds->logvol); if (oscap_iterator_has_more(logvol_it)) { _write_text_to_fd(output_fd, common_partition); + } else { + _write_text_to_fd(output_fd, fallback_partition); } while (oscap_iterator_has_more(logvol_it)) { struct logvol_cmd *command = (struct logvol_cmd *) oscap_iterator_next(logvol_it); From 3c2e679124489cd131525791eae2085bc66c4ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 25 Jul 2024 11:22:43 +0200 Subject: [PATCH 42/53] Add an empty line to the user manual --- docs/manual/manual.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index 00bca2daa6..e903edc210 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1140,6 +1140,7 @@ The following rules are applied on each line: * excess whitespace are trimmed 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 From 509460b2079deb73d11c6fdfc77894d9e1faa84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 25 Jul 2024 14:28:39 +0200 Subject: [PATCH 43/53] Use autopart in the default partition option To simplify the code, we can use the "autopart" command. --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 356e0215e3..8f6c5162ea 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1559,11 +1559,7 @@ const char *fallback_partition = ( "# Create partition layout scheme (optional)\n" "zerombr\n" "clearpart --all --initlabel\n" - "reqpart --add-boot\n" - "part pv.01 --grow --size=1\n" - "volgroup system pv.01\n" - "logvol / --name=root --vgname=system --size=10000 --grow\n" - "logvol swap --name=swap --vgname=system --size=1000\n" + "autopart --type=lvm\n" ); static int _generate_kickstart_logvol(struct kickstart_commands *cmds, int output_fd) From 0e8a0f5c93c7aae84a2ef5e3cc1cde10692b1401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 25 Jul 2024 15:54:45 +0200 Subject: [PATCH 44/53] Add exit 1 --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 8f6c5162ea..3829f26652 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -1529,7 +1529,7 @@ static int _generate_kickstart_post(struct kickstart_commands *cmds, const char free(basename); _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 ]\n"); + _write_text_to_fd(output_fd, "[ $? -eq 0 -o $? -eq 2 ] || exit 1\n"); 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); From fd16278dbc08a9c180c6a4dfec458ac26c84cb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 26 Jul 2024 10:20:50 +0200 Subject: [PATCH 45/53] Improve the bootloader command Options for bootloader sometimes aren't in the form 'option=value' and so they sometimes don't contain '='. This commit clarifies the documentation about the bootloader command with regards to this fact. Also, it adds a simple test for this command. --- docs/manual/manual.adoc | 2 +- .../API/XCCDF/unittests/test_remediation_kickstart.ds.xml | 8 ++++++++ tests/API/XCCDF/unittests/test_remediation_kickstart.sh | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index e903edc210..aab8c43cce 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1147,7 +1147,7 @@ Supported commands: * `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=value` - adds `option=value` to list in the `--append=` option in the `bootloader` command in commands section in the kickstart +* `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 For example, to generate a kickstart for RHEL 9 STIG profile, run: diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml index 10e4008cb7..031aa6034a 100644 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml @@ -59,6 +59,7 @@ Rule 1: Enable Audit Service @@ -104,6 +105,13 @@ logvol /var/tmp 1024 + + Rule 6: Bootloader + + bootloader quick + bootloader audit=1 + + diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh index a593143422..c2c7203acd 100755 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.sh +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.sh @@ -14,6 +14,7 @@ function test_normal { 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" rm -rf "$kickstart" rm -rf "$stderr" From 24ade058da65fe00407239d584a1871e1d44bccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 26 Jul 2024 13:40:46 +0200 Subject: [PATCH 46/53] Introduce function oscap_concat * Concatenate 2 strings * Convenience wrapper over strncat. --- src/common/util.c | 11 +++++++++++ src/common/util.h | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/common/util.c b/src/common/util.c index a8b4a34dda..66f36e26f6 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -474,3 +474,14 @@ bool oscap_path_startswith(const char *path, const char *prefix) free(prefix_split); return res; } + +char *oscap_concat(char *str1, char *str2) +{ + if (str1 == NULL || str2 == NULL) + return str1; + size_t str1_len = strlen(str1); + size_t str2_len = strlen(str2); + str1 = realloc(str1, str1_len + str2_len + 1); + strncat(str1, str2, str2_len); + return str1; +} diff --git a/src/common/util.h b/src/common/util.h index 59a500d1f4..f7e2044134 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -550,4 +550,13 @@ int oscap_open_writable(const char *filename); */ bool oscap_path_startswith(const char *path, const char *prefix); +/** + * Concatenate 2 strings + * Convenience wrapper over strncat. + * @param str1 string 1 + * @param str2 string 2 + * @return string 1 with string 2 appended inside + */ +char *oscap_concat(char *str1, char *str2); + #endif /* OSCAP_UTIL_H_ */ From fcf313a5a0f32451cfec51d6c9ca4ef29c9a37d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Fri, 26 Jul 2024 13:53:30 +0200 Subject: [PATCH 47/53] Add ability to specify post sections 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. --- docs/manual/manual.adoc | 18 ++-- src/XCCDF_POLICY/xccdf_policy_remediate.c | 64 ++++++++++---- .../test_remediation_kickstart.ds.xml | 28 +++++- .../unittests/test_remediation_kickstart.sh | 16 ++-- .../test_remediation_kickstart.tailoring.xml | 3 + .../test_remediation_kickstart_expected.cfg | 85 +++++++++++++++++++ 6 files changed, 181 insertions(+), 33 deletions(-) create mode 100644 tests/API/XCCDF/unittests/test_remediation_kickstart_expected.cfg diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc index aab8c43cce..470ede7b16 100644 --- a/docs/manual/manual.adoc +++ b/docs/manual/manual.adoc @@ -1130,14 +1130,19 @@ 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 `` 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: @@ -1145,7 +1150,6 @@ Supported commands: * `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 diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 3829f26652..aebe02c888 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -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 }; @@ -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")) { @@ -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); @@ -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); @@ -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"); @@ -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; } @@ -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"); diff --git a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml index 031aa6034a..5c82065375 100644 --- a/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml +++ b/tests/API/XCCDF/unittests/test_remediation_kickstart.ds.xml @@ -60,6 +60,8 @@ +