From c4f29cabad96f166f1f4e62c60e9d79ba4e84263 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Cope" Date: Thu, 25 Jun 2026 19:46:26 -0500 Subject: [PATCH 1/3] init cis smoketest Signed-off-by: Jonathan H. Cope --- test/assets/cis/cis-harden.yml | 13 +++ test/assets/cis/cis-requirements.yml | 7 ++ test/scenarios/periodics/el98-src@cis-lvl2.sh | 34 ++++++ test/suites/cis/validate-cis-lvl2.robot | 104 ++++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 test/assets/cis/cis-harden.yml create mode 100644 test/assets/cis/cis-requirements.yml create mode 100755 test/scenarios/periodics/el98-src@cis-lvl2.sh create mode 100644 test/suites/cis/validate-cis-lvl2.robot diff --git a/test/assets/cis/cis-harden.yml b/test/assets/cis/cis-harden.yml new file mode 100644 index 0000000000..7c6f050b1e --- /dev/null +++ b/test/assets/cis/cis-harden.yml @@ -0,0 +1,13 @@ +--- +- name: Apply CIS Level 2 hardening + hosts: localhost + connection: local + become: true + roles: + - role: ansible-role-rhel9-cis + vars: + # MicroShift OVN networking requires IP forwarding + sysctl_net_ipv4_ip_forward: false + sysctl_net_ipv6_conf_all_forwarding: false + # CI requires passwordless sudo + sudo_remove_nopasswd: false diff --git a/test/assets/cis/cis-requirements.yml b/test/assets/cis/cis-requirements.yml new file mode 100644 index 0000000000..d7930ad2e0 --- /dev/null +++ b/test/assets/cis/cis-requirements.yml @@ -0,0 +1,7 @@ +roles: + - name: ansible-role-rhel9-cis + src: https://github.com/RedHatOfficial/ansible-role-rhel9-cis + version: "0.1.80" +collections: + - community.general + - ansible.posix diff --git a/test/scenarios/periodics/el98-src@cis-lvl2.sh b/test/scenarios/periodics/el98-src@cis-lvl2.sh new file mode 100755 index 0000000000..4f07d5a6be --- /dev/null +++ b/test/scenarios/periodics/el98-src@cis-lvl2.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Sourced from scenario.sh and uses functions defined there. + +scenario_create_vms() { + prepare_kickstart host1 kickstart.ks.template rhel-9.8-microshift-source + launch_vm rhel-9.8 + + # Install packages needed for CIS hardening and OpenSCAP scanning + run_command_on_vm host1 "sudo dnf install -y openscap-scanner scap-security-guide ansible-core" + run_command_on_vm host1 "ansible-galaxy collection install community.general ansible.posix" + + # Copy ansible assets and install the CIS hardening role + copy_file_to_vm host1 "${ROOTDIR}/test/assets/cis/cis-requirements.yml" "/tmp/cis-requirements.yml" + copy_file_to_vm host1 "${ROOTDIR}/test/assets/cis/cis-harden.yml" "/tmp/cis-harden.yml" + run_command_on_vm host1 "ansible-galaxy role install -r /tmp/cis-requirements.yml" + + # Apply CIS Level 2 hardening + run_command_on_vm host1 "sudo ansible-playbook -c local /tmp/cis-harden.yml" + + # Reboot to apply hardening changes and wait for the VM to come back + run_command_on_vm host1 "sudo reboot" || true + sleep 10 + local -r ip=$(get_vm_property host1 ip) + wait_for_ssh "${ip}" +} + +scenario_remove_vms() { + remove_vm host1 +} + +scenario_run_tests() { + run_tests host1 suites/cis/ +} diff --git a/test/suites/cis/validate-cis-lvl2.robot b/test/suites/cis/validate-cis-lvl2.robot new file mode 100644 index 0000000000..8fbf5e8d96 --- /dev/null +++ b/test/suites/cis/validate-cis-lvl2.robot @@ -0,0 +1,104 @@ +*** Settings *** +Documentation Tests related to CIS Level 2 validation on a hardened RHEL system + +Resource ../../resources/common.resource +Resource ../../resources/oc.resource +Resource ../../resources/ostree-health.resource +Resource ../../resources/microshift-host.resource +Resource ../../resources/microshift-process.resource +Resource ../../resources/microshift-network.resource +Library Collections +Library String + +Suite Setup Setup +Suite Teardown Teardown + +Test Tags slow + + +*** Variables *** +${USHIFT_HOST} ${EMPTY} +${USHIFT_USER} ${EMPTY} +${OSCAP_REPORT_FILE} /tmp/cis-lvl2-report.html +${OSCAP_RESULTS_FILE} /tmp/cis-lvl2-results.xml +${OSCAP_PROFILE} xccdf_org.ssgproject.content_profile_cis +${SCAP_DS_FILE} /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml +${CIS_MAX_FAILURES} 16 + + +*** Test Cases *** +OpenSCAP Scan Runs And Produces Report + [Documentation] Run OpenSCAP scan with CIS Level 2 profile and verify it produces output files + Run CIS Level 2 Scan + Verify Remote File Exists With Sudo ${OSCAP_REPORT_FILE} + Verify Remote File Exists With Sudo ${OSCAP_RESULTS_FILE} + +CIS Failure Count Within Threshold + [Documentation] Verify the number of CIS failures does not exceed the expected threshold + ${fail_count}= Get CIS Failure Count + Log CIS Level 2 failures: ${fail_count} (threshold: ${CIS_MAX_FAILURES}) + Should Be True ${fail_count} <= ${CIS_MAX_FAILURES} + ... CIS failure count ${fail_count} exceeds threshold ${CIS_MAX_FAILURES} + +All Pods Are Running After CIS Hardening + [Documentation] Verify all MicroShift pods are running on the hardened system + All Pods Should Be Running + +Smoke Test With Route + [Documentation] Deploy hello-microshift and expose via route to verify networking on hardened system + [Setup] Setup Smoke Test + Wait Until Keyword Succeeds 10x 6s + ... Access Hello MicroShift Success ${HTTP_PORT} + [Teardown] Teardown Smoke Test + + +*** Keywords *** +Setup + [Documentation] Test suite setup + Check Required Env Variables + Login MicroShift Host + Setup Kubeconfig + Start MicroShift + Wait Until Greenboot Health Check Exited + +Teardown + [Documentation] Test suite teardown — archive scan artifacts + Run Keyword And Ignore Error + ... SSHLibrary.Get File ${OSCAP_REPORT_FILE} ${OUTPUTDIR}/cis-lvl2-report.html + Run Keyword And Ignore Error + ... SSHLibrary.Get File ${OSCAP_RESULTS_FILE} ${OUTPUTDIR}/cis-lvl2-results.xml + Logout MicroShift Host + +Run CIS Level 2 Scan + [Documentation] Execute the OpenSCAP scan with CIS Level 2 profile + ${stdout} ${stderr} ${rc}= Execute Command + ... oscap xccdf eval --profile ${OSCAP_PROFILE} --results ${OSCAP_RESULTS_FILE} --report ${OSCAP_REPORT_FILE} ${SCAP_DS_FILE} + ... sudo=True + ... return_rc=True + ... return_stdout=True + ... return_stderr=True + Should Be True ${rc} == 0 or ${rc} == 2 + ... OpenSCAP scan failed with unexpected return code ${rc}: ${stderr} + +Get CIS Failure Count + [Documentation] Parse the OpenSCAP results XML and count failures + ${stdout} ${stderr} ${rc}= Execute Command + ... grep -c 'fail' ${OSCAP_RESULTS_FILE} + ... sudo=False return_rc=True return_stdout=True return_stderr=True + Should Be Equal As Integers 0 ${rc} + ${fail_count}= Convert To Integer ${stdout.strip()} + RETURN ${fail_count} + +Setup Smoke Test + [Documentation] Create hello-microshift pod and expose via route + ${ns}= Create Unique Namespace + VAR ${NAMESPACE}= ${ns} scope=TEST + Create Hello MicroShift Pod + Expose Hello MicroShift + Oc Expose svc hello-microshift --hostname hello-microshift.cluster.local -n ${NAMESPACE} + +Teardown Smoke Test + [Documentation] Clean up smoke test resources + Run Keyword And Ignore Error Oc Delete route/hello-microshift -n ${NAMESPACE} + Run Keyword And Ignore Error Delete Hello MicroShift Pod And Service + Run Keyword And Ignore Error Remove Namespace ${NAMESPACE} From 5196251ea599ced249242d69d450957180bd1761 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Cope" Date: Mon, 13 Jul 2026 09:06:35 -0500 Subject: [PATCH 2/3] Add CIS Level 2 smoke test for MicroShift on hardened RHEL 9.8 Introduce a periodic CI scenario that installs MicroShift on a CIS Level 2 hardened system and validates it functions correctly. The test provisions a VM from the source image-installer ISO, registers it with subscription-manager, installs OpenSCAP and Ansible, applies CIS Level 2 hardening via ansible-role-rhel9-cis, then runs four validation checks: OpenSCAP scan produces a report, CIS failure count stays within threshold, all pods are running, and a smoke test route is accessible. New files: - test/scenarios/periodics/el98-src@cis-lvl2.sh - test/suites/cis/validate-cis-lvl2.robot - test/assets/cis/cis-harden.yml - test/assets/cis/cis-requirements.yml - test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer The .image-installer marker enables build_images.sh to produce rhel-9.8-microshift-source.iso, which is required for the liveimg kickstart pattern (mutable RPM-based system needed for post-boot CIS. --- .../group1/rhel98-source.image-installer | 1 + test/scenarios/periodics/el98-src@cis-lvl2.sh | 13 +++++++++---- test/suites/cis/validate-cis-lvl2.robot | 3 +-- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer diff --git a/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer b/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer new file mode 100644 index 0000000000..3a553dd88c --- /dev/null +++ b/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer @@ -0,0 +1 @@ +rhel-9.8-microshift-source diff --git a/test/scenarios/periodics/el98-src@cis-lvl2.sh b/test/scenarios/periodics/el98-src@cis-lvl2.sh index 4f07d5a6be..7dfffb69aa 100755 --- a/test/scenarios/periodics/el98-src@cis-lvl2.sh +++ b/test/scenarios/periodics/el98-src@cis-lvl2.sh @@ -3,17 +3,19 @@ # Sourced from scenario.sh and uses functions defined there. scenario_create_vms() { - prepare_kickstart host1 kickstart.ks.template rhel-9.8-microshift-source - launch_vm rhel-9.8 + prepare_kickstart host1 kickstart-liveimg.ks.template "" + launch_vm rhel-9.8-microshift-source + + subscription_manager_register host1 # Install packages needed for CIS hardening and OpenSCAP scanning run_command_on_vm host1 "sudo dnf install -y openscap-scanner scap-security-guide ansible-core" - run_command_on_vm host1 "ansible-galaxy collection install community.general ansible.posix" + run_command_on_vm host1 "sudo ansible-galaxy collection install community.general ansible.posix" # Copy ansible assets and install the CIS hardening role copy_file_to_vm host1 "${ROOTDIR}/test/assets/cis/cis-requirements.yml" "/tmp/cis-requirements.yml" copy_file_to_vm host1 "${ROOTDIR}/test/assets/cis/cis-harden.yml" "/tmp/cis-harden.yml" - run_command_on_vm host1 "ansible-galaxy role install -r /tmp/cis-requirements.yml" + run_command_on_vm host1 "sudo ansible-galaxy role install -r /tmp/cis-requirements.yml" # Apply CIS Level 2 hardening run_command_on_vm host1 "sudo ansible-playbook -c local /tmp/cis-harden.yml" @@ -23,6 +25,9 @@ scenario_create_vms() { sleep 10 local -r ip=$(get_vm_property host1 ip) wait_for_ssh "${ip}" + + # Re-apply MicroShift firewall rules (CIS hardening may have reset them) + configure_vm_firewall host1 } scenario_remove_vms() { diff --git a/test/suites/cis/validate-cis-lvl2.robot b/test/suites/cis/validate-cis-lvl2.robot index 8fbf5e8d96..567ef79d5d 100644 --- a/test/suites/cis/validate-cis-lvl2.robot +++ b/test/suites/cis/validate-cis-lvl2.robot @@ -83,9 +83,8 @@ Run CIS Level 2 Scan Get CIS Failure Count [Documentation] Parse the OpenSCAP results XML and count failures ${stdout} ${stderr} ${rc}= Execute Command - ... grep -c 'fail' ${OSCAP_RESULTS_FILE} + ... grep -c 'fail' ${OSCAP_RESULTS_FILE} || echo 0 ... sudo=False return_rc=True return_stdout=True return_stderr=True - Should Be Equal As Integers 0 ${rc} ${fail_count}= Convert To Integer ${stdout.strip()} RETURN ${fail_count} From 6690cdce7f101b33b357bafa98e8c43b677203c0 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Cope" Date: Thu, 16 Jul 2026 15:27:55 -0500 Subject: [PATCH 3/3] Reuse isolated ISO for CIS test and fix ansible SSH disconnect Switch the CIS scenario to use the existing rhel-9.8-microshift-source-isolated ISO instead of building a dedicated rhel-9.8-microshift-source ISO. The isolated ISO is already built for other periodic scenarios and is a functional superset. Run the CIS hardening playbook detached (nohup) because update-crypto-policies restarts sshd mid-run, killing the SSH session. Poll for PLAY RECAP in the log to detect completion and check for failures. Co-Authored-By: Claude Opus 4.6 --- .../group1/rhel98-source.image-installer | 1 - test/scenarios/periodics/el98-src@cis-lvl2.sh | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) delete mode 100644 test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer diff --git a/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer b/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer deleted file mode 100644 index 3a553dd88c..0000000000 --- a/test/image-blueprints/layer2-presubmit/group1/rhel98-source.image-installer +++ /dev/null @@ -1 +0,0 @@ -rhel-9.8-microshift-source diff --git a/test/scenarios/periodics/el98-src@cis-lvl2.sh b/test/scenarios/periodics/el98-src@cis-lvl2.sh index 7dfffb69aa..8f18839bf6 100755 --- a/test/scenarios/periodics/el98-src@cis-lvl2.sh +++ b/test/scenarios/periodics/el98-src@cis-lvl2.sh @@ -4,7 +4,7 @@ scenario_create_vms() { prepare_kickstart host1 kickstart-liveimg.ks.template "" - launch_vm rhel-9.8-microshift-source + launch_vm rhel-9.8-microshift-source-isolated subscription_manager_register host1 @@ -17,16 +17,28 @@ scenario_create_vms() { copy_file_to_vm host1 "${ROOTDIR}/test/assets/cis/cis-harden.yml" "/tmp/cis-harden.yml" run_command_on_vm host1 "sudo ansible-galaxy role install -r /tmp/cis-requirements.yml" - # Apply CIS Level 2 hardening - run_command_on_vm host1 "sudo ansible-playbook -c local /tmp/cis-harden.yml" + # Apply CIS Level 2 hardening. The playbook is run detached because + # update-crypto-policies restarts sshd mid-run, killing the SSH session. + # The playbook itself uses -c local and survives the sshd restart. + run_command_on_vm host1 "nohup sudo ansible-playbook -c local /tmp/cis-harden.yml &>/tmp/cis-harden.log &" + local -r ip=$(get_vm_property host1 ip) + wait_for_ssh "${ip}" + local attempts=0 + while ! run_command_on_vm host1 "grep -q 'PLAY RECAP' /tmp/cis-harden.log" 2>/dev/null ; do + (( attempts++ )) && (( attempts > 180 )) && { echo "Timed out waiting for CIS hardening"; return 1; } + sleep 10 + done + if run_command_on_vm host1 "grep -qE 'unreachable=[1-9]|failed=[1-9]' /tmp/cis-harden.log" 2>/dev/null ; then + run_command_on_vm host1 "cat /tmp/cis-harden.log" || true + echo "CIS hardening playbook failed" + return 1 + fi # Reboot to apply hardening changes and wait for the VM to come back run_command_on_vm host1 "sudo reboot" || true sleep 10 - local -r ip=$(get_vm_property host1 ip) wait_for_ssh "${ip}" - # Re-apply MicroShift firewall rules (CIS hardening may have reset them) configure_vm_firewall host1 }