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..8f18839bf6 --- /dev/null +++ b/test/scenarios/periodics/el98-src@cis-lvl2.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Sourced from scenario.sh and uses functions defined there. + +scenario_create_vms() { + prepare_kickstart host1 kickstart-liveimg.ks.template "" + launch_vm rhel-9.8-microshift-source-isolated + + 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 "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 "sudo ansible-galaxy role install -r /tmp/cis-requirements.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 + wait_for_ssh "${ip}" + + configure_vm_firewall host1 +} + +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..567ef79d5d --- /dev/null +++ b/test/suites/cis/validate-cis-lvl2.robot @@ -0,0 +1,103 @@ +*** 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} || echo 0 + ... sudo=False return_rc=True return_stdout=True return_stderr=True + ${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}