diff --git a/.github/workflows/gate.yaml b/.github/workflows/gate.yaml index 518286d8a1d..d074b53a801 100644 --- a/.github/workflows/gate.yaml +++ b/.github/workflows/gate.yaml @@ -88,7 +88,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Install Deps - run: sudo apt-get update && sudo apt-get install cmake ninja-build libopenscap8 libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build libopenscap8 libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - name: Install deps python @@ -107,7 +107,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Deps - run: sudo apt-get update && sudo apt-get install cmake ninja-build libopenscap8 libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build libopenscap8 libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - name: Install deps python @@ -126,7 +126,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Install Deps - run: sudo apt-get update && sudo apt-get install cmake ninja-build openscap-utils libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build openscap-utils libxml2-utils xsltproc ansible-lint bats python3-github python3-jinja2 python3-pip python3-pytest python3-pytest-cov python3-setuptools python3-yaml shellcheck - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - name: Install deps python @@ -180,8 +180,8 @@ jobs: name: Build on Windows runs-on: windows-latest env: - OPENSCAP_VERSION: "1.4.1" - OPENSCAP_ROOT_DIR: "C:\\Program Files\\OpenSCAP 1.4.1" + OPENSCAP_VERSION: "1.4.2" + OPENSCAP_ROOT_DIR: "C:\\Program Files\\OpenSCAP 1.4.2" steps: - name: Install Deps run: choco install xsltproc diff --git a/.github/workflows/k8s-content-pr.yaml b/.github/workflows/k8s-content-pr.yaml index 83901945aef..c2533370a81 100644 --- a/.github/workflows/k8s-content-pr.yaml +++ b/.github/workflows/k8s-content-pr.yaml @@ -63,7 +63,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3 + uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3 - name: Docker metadata @@ -84,7 +84,7 @@ jobs: org.opencontainers.image.vendor='Compliance Operator Authors' - name: Build container images and push id: docker_build - uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6 with: context: . file: ./Dockerfiles/ocp4_content diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 976c519fd63..68342993a5e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,7 +45,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Release - uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0 + uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 with: draft: True name: Content ${{ steps.set_version.outputs.ver }} diff --git a/controls/ism_o.yml b/controls/ism_o.yml index 768fb93ec2c..b2e92cd26a7 100644 --- a/controls/ism_o.yml +++ b/controls/ism_o.yml @@ -31,7 +31,7 @@ controls: - set_password_hashing_algorithm_passwordauth - set_password_hashing_algorithm_systemauth - sshd_disable_gssapi_auth - - var_password_hashing_algorithm_pam=sha512 + - var_password_hashing_algorithm_pam=yescrypt status: automated - id: '0421' diff --git a/controls/srg_gpos/SRG-OS-000730-GPOS-00190.yml b/controls/srg_gpos/SRG-OS-000730-GPOS-00190.yml index 0fa076d796b..b8fab142b59 100644 --- a/controls/srg_gpos/SRG-OS-000730-GPOS-00190.yml +++ b/controls/srg_gpos/SRG-OS-000730-GPOS-00190.yml @@ -10,7 +10,6 @@ controls: - var_password_pam_maxclassrepeat=3 - var_password_pam_dictcheck=1 - accounts_password_pam_dictcheck - - var_password_hashing_algorithm_pam=sha512 - - var_password_pam_unix_rounds=5000 + - var_password_pam_unix_rounds=5 - var_password_pam_remember=5 - var_password_pam_remember_control_flag=requisite_or_required diff --git a/linux_os/guide/services/ntp/file_groupowner_etc_chrony_keys/oval/shared.xml b/linux_os/guide/services/ntp/file_groupowner_etc_chrony_keys/oval/shared.xml new file mode 100644 index 00000000000..9c3a8f32ac6 --- /dev/null +++ b/linux_os/guide/services/ntp/file_groupowner_etc_chrony_keys/oval/shared.xml @@ -0,0 +1,72 @@ + + + + {{{ oval_metadata("/etc/chrony.keys should be owned by chrony group") }}} + + + + + + + + + + + + +{{{ oval_test_nsswitch_uses_altfiles() }}} + + + + + + /etc/chrony.keys + state_file_groupowner_etc_chrony_keys_uid_chrony + state_file_groupowner_etc_chrony_keys_gid_chrony + + + /etc/group + ^chrony:\w+:(\w+):.* + 1 + + + + + + symbolic link + + + + + + + + + + /etc/chrony.keys + state_file_groupowner_etc_chrony_keys_uid_chrony + state_file_groupowner_etc_chrony_keys_gid_chrony_with_usrlib + + + + object_file_groupowner_etc_chrony_keys_etc_group + object_file_groupowner_etc_chrony_keys_usr_lib_group + + + + /usr/lib/group + ^chrony:\w+:(\w+):.* + 1 + + + + + + + + + diff --git a/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_password_auth/rule.yml b/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_password_auth/rule.yml index fb7bc6dd758..fab9d0587c7 100644 --- a/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_password_auth/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_password_auth/rule.yml @@ -54,7 +54,6 @@ references: nist@sle15: IA-5(1)(e),IA-5(1).1(v) pcidss: Req-8.2.5 srg: SRG-OS-000077-GPOS-00045 - stigid@rhel8: RHEL-08-020220 ocil_clause: |- the pam_pwhistory.so module is not used, the "remember" module option is not set in diff --git a/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_system_auth/rule.yml b/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_system_auth/rule.yml index 8ad3e9c5d3b..cd38fca2ca2 100644 --- a/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_system_auth/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/locking_out_password_attempts/accounts_password_pam_pwhistory_remember_system_auth/rule.yml @@ -54,7 +54,6 @@ references: nist@sle15: IA-5(1)(e),IA-5(1).1(v) pcidss: Req-8.2.5 srg: SRG-OS-000077-GPOS-00045 - stigid@rhel8: RHEL-08-020221 ocil_clause: |- the pam_pwhistory.so module is not used, the "remember" module option is not set in diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/ansible/shared.yml b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/ansible/shared.yml index 25a0da980c0..bb71a6d80fa 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/ansible/shared.yml +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/ansible/shared.yml @@ -5,7 +5,7 @@ # disruption = medium {{% if 'ubuntu' in product %}} {{% set configuration_files = ["common-password"] %}} -{{% elif product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} +{{% elif product in ['ol8', 'ol9'] or 'rhel' in product %}} {{% set configuration_files = ["password-auth","system-auth"] %}} {{% else %}} {{% set configuration_files = ["system-auth"] %}} diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/bash/shared.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/bash/shared.sh index 608e1ab3fbd..4b26c7dda75 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/bash/shared.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/bash/shared.sh @@ -1,6 +1,6 @@ # platform = multi_platform_all -{{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} +{{% if product in ['ol8', 'ol9'] or 'rhel' in product %}} {{% set configuration_files = ["password-auth","system-auth"] %}} {{% else %}} {{% set configuration_files = ["system-auth"] %}} @@ -9,7 +9,7 @@ {{{ bash_instantiate_variables("var_password_pam_retry") }}} -{{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] -%}} +{{% if product in ['ol8', 'ol9'] or 'rhel' in product -%}} {{{ bash_replace_or_append('/etc/security/pwquality.conf', '^retry', '$var_password_pam_retry', diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/oval/shared.xml b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/oval/shared.xml index 4ae8aec49b3..85ef117bd51 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/oval/shared.xml +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/oval/shared.xml @@ -1,6 +1,6 @@ {{% if 'ubuntu' in product or 'debian' in product %}} {{% set configuration_files = ["common-password"] %}} -{{% elif product in ['ol8','ol9','rhel8', 'rhel9'] %}} +{{% elif product in ['ol8','ol9'] or 'rhel' in product %}} {{% set configuration_files = ["password-auth","system-auth"] %}} {{% else %}} {{% set configuration_files = ["system-auth"] %}} @@ -17,7 +17,7 @@ {{% for file in configuration_files %}} - {{% endfor %}} diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/rule.yml b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/rule.yml index 6e25f29481a..1fe3c52f0a5 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/rule.yml @@ -5,7 +5,7 @@ title: 'Ensure PAM Enforces Password Requirements - Authentication Retry Prompts description: |- To configure the number of retry prompts that are permitted per-session: - {{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} + {{% if product in ['ol8', 'ol9'] or 'rhel' in product %}} Edit the /etc/security/pwquality.conf to include {{% else %}} Edit the pam_pwquality.so statement in @@ -56,7 +56,7 @@ ocil_clause: 'the value of "retry" is set to "0" or greater than "{{{ xccdf_valu ocil: |- Verify {{{ full_name }}} is configured to limit the "pwquality" retry option to {{{ xccdf_value("var_password_pam_retry") }}}. - {{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} + {{% if product in ['ol8', 'ol9'] or 'rhel' in product %}} Check for the use of the "pwquality" retry option in the pwquality.conf file with the following command:
$ grep retry /etc/security/pwquality.conf
{{% else %}} @@ -75,7 +75,7 @@ platform: package[pam] fixtext: |- Configure {{{ full_name }}} to limit the "pwquality" retry option to {{{ xccdf_value("var_password_pam_retry") }}}. - {{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} + {{% if product in ['ol8', 'ol9'] or 'rhel' in product %}} Add the following line to the "/etc/security/pwquality.conf" file (or modify the line to have the required value): retry={{{ xccdf_value("var_password_pam_retry") }}} diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/common.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/common.sh index 02bd487048c..0ab3da26636 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/common.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/common.sh @@ -1,13 +1,13 @@ {{% if 'ubuntu' in product %}} configuration_files=("common-password") -{{% elif product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} +{{% elif product in ['ol8', 'ol9'] or 'rhel' in product %}} configuration_files=("password-auth" "system-auth") {{% else %}} configuration_files=("system-auth") {{% endif %}} -{{% if product in ['ol8', 'ol9', 'rhel8', 'rhel9'] %}} +{{% if product in ['ol8', 'ol9'] or 'rhel' in product %}} authselect create-profile testingProfile --base-on sssd for file in ${configuration_files[@]}; do diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_commented.fail.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_commented.fail.sh index 19cac93f41d..c61f9b6d5fa 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_commented.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_commented.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9 +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel # variables = var_password_pam_retry=3 source common.sh diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct.pass.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct.pass.sh index ae605f71726..601d3275906 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct.pass.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct.pass.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9 +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel # variables = var_password_pam_retry=3 source common.sh diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct_with_space.pass.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct_with_space.pass.sh index ce7f4b7a3cb..e4f1de0cc4a 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct_with_space.pass.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_correct_with_space.pass.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9 +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel # variables = var_password_pam_retry=3 source common.sh diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_overriden.fail.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_overriden.fail.sh index 962112d6a25..d70521e76fe 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_overriden.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_overriden.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9 +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel # variables = var_password_pam_retry=3 source common.sh diff --git a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_wrong.fail.sh b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_wrong.fail.sh index ea2eb57fed5..dc7fe32d110 100644 --- a/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_wrong.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/password_quality/password_quality_pwquality/accounts_password_pam_retry/tests/pwquality_conf_wrong.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9 +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel # variables = var_password_pam_retry=3 source common.sh diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/rule.yml b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/rule.yml index 13da3921ff6..d03a7af4415 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/rule.yml @@ -63,7 +63,7 @@ ocil: |- platform: package[pam] -{{% if product in ['ol9', 'rhel9'] %}} +{{% if product in ['ol9', 'rhel9', 'rhel10'] %}} srg_requirement: 'The {{{ full_name }}} pam_unix.so module must be configured in the password-auth file to use a FIPS 140-3 approved cryptographic hashing algorithm for system authentication.' fixtext: |- diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_correct_value.pass.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_correct_value.pass.sh index abcdf02f5a2..ee1213c2df0 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_correct_value.pass.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_correct_value.pass.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_incorrect_option.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_incorrect_option.fail.sh index 1572f0d9ba1..8d6be38f4d4 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_incorrect_option.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_incorrect_option.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_missing_option.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_missing_option.fail.sh index 463b78e5527..13f217f0273 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_missing_option.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_missing_option.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_modified_pam.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_modified_pam.fail.sh index a36ff143d44..5632949e220 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_modified_pam.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_modified_pam.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 # remediation = none diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_multiple_options.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_multiple_options.fail.sh index b874f33d6da..7f6ff9a978f 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_multiple_options.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_multiple_options.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_wrong_control.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_wrong_control.fail.sh index 98aff168eb6..10a02eb86d1 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_wrong_control.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_passwordauth/tests/authselect_wrong_control.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/bash/shared.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/bash/shared.sh index 977e62cd3ea..77b57a39555 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/bash/shared.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/bash/shared.sh @@ -4,13 +4,13 @@ {{% if 'sle' in product or 'slmicro' in product -%}} PAM_FILE_PATH="/etc/pam.d/common-password" -CONTROL="required" +{{% set control = "required" %}} {{%- elif 'ubuntu' in product -%}} {{{ bash_pam_unix_enable() }}} PAM_FILE_PATH=/usr/share/pam-configs/cac_unix {{%- else -%}} PAM_FILE_PATH="/etc/pam.d/system-auth" -CONTROL="sufficient" +{{% set control = "sufficient" %}} {{%- endif %}} {{% if 'ubuntu' in product -%}} @@ -31,7 +31,7 @@ if ! grep -qzP "Password-Initial:\s*\n\s+.*\s+pam_unix.so\s+.*\b$var_password_ha fi {{%- else -%}} -{{{ bash_ensure_pam_module_configuration("$PAM_FILE_PATH", 'password', "$CONTROL", 'pam_unix.so', "$var_password_hashing_algorithm_pam", '', '') }}} +{{{ bash_ensure_pam_module_configuration("$PAM_FILE_PATH", 'password', control, 'pam_unix.so', "$var_password_hashing_algorithm_pam", '', '') }}} {{%- endif %}} # Ensure only the correct hashing algorithm option is used. diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/rule.yml b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/rule.yml index aa69bb5dff8..dbc370188f3 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/rule.yml @@ -90,7 +90,7 @@ ocil: |- platform: package[pam] fixtext: |- - {{% if product in ['ol9', 'rhel9', 'ubuntu2204', 'ubuntu2404'] -%}} + {{% if product in ['ol9', 'rhel9', 'rhel10', 'ubuntu2204', 'ubuntu2404'] -%}} Configure {{{ full_name }}} to use a FIPS 140-3 approved cryptographic hashing algorithm for system authentication. {{% else %}} Configure {{{ full_name }}} to use a FIPS 140-2 approved cryptographic hashing algorithm for system authentication. @@ -106,7 +106,7 @@ fixtext: |- password sufficient pam_unix.so {{{ xccdf_value("var_password_hashing_algorithm_pam") }}} {{%- endif %}} -{{% if product in ['ol9', 'rhel9'] -%}} +{{% if product in ['ol9', 'rhel9', 'rhel10'] -%}} srg_requirement: 'The {{{ full_name }}} pam_unix.so module must be configured in the system-auth file to use a FIPS 140-3 approved cryptographic hashing algorithm for system authentication.' {{%- endif %}} diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_correct_value.pass.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_correct_value.pass.sh index a665b3b10f9..264df72f1cf 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_correct_value.pass.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_correct_value.pass.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_incorrect_option.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_incorrect_option.fail.sh index c498e86dd18..c5e65c44e0e 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_incorrect_option.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_incorrect_option.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_missing_option.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_missing_option.fail.sh index 3653f7912d0..c61e9828d17 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_missing_option.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_missing_option.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_modified_pam.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_modified_pam.fail.sh index 11ed319f10e..6499ed205a7 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_modified_pam.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_modified_pam.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 # remediation = none diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_multiple_options.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_multiple_options.fail.sh index e4195021755..6b5b5767a1e 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_multiple_options.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_multiple_options.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_wrong_control.fail.sh b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_wrong_control.fail.sh index d0413404b3a..a1a9ec1ec1b 100644 --- a/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_wrong_control.fail.sh +++ b/linux_os/guide/system/accounts/accounts-pam/set_password_hashing_algorithm/set_password_hashing_algorithm_systemauth/tests/authselect_wrong_control.fail.sh @@ -1,6 +1,6 @@ #!/bin/bash # packages = authselect -# platform = Oracle Linux 8,Oracle Linux 9,Red Hat Enterprise Linux 8,Red Hat Enterprise Linux 9,multi_platform_fedora +# platform = Oracle Linux 8,Oracle Linux 9,multi_platform_rhel,multi_platform_fedora # variables = var_password_hashing_algorithm_pam=sha512 authselect create-profile hardening -b sssd diff --git a/linux_os/guide/system/permissions/files/file_permissions_ungroupowned/oval/shared.xml b/linux_os/guide/system/permissions/files/file_permissions_ungroupowned/oval/shared.xml index 2abf8f046d9..9c7e307c69a 100644 --- a/linux_os/guide/system/permissions/files/file_permissions_ungroupowned/oval/shared.xml +++ b/linux_os/guide/system/permissions/files/file_permissions_ungroupowned/oval/shared.xml @@ -92,22 +92,7 @@ state_file_permissions_ungroupowned_sysroot - - - - - - - /etc/nsswitch.conf - ^\s*group:\s+(.*)$ - 1 - - - - altfiles - +{{{ oval_test_nsswitch_uses_altfiles() }}} =23.2.0 diff --git a/shared/macros/10-oval.jinja b/shared/macros/10-oval.jinja index 53c187e404e..4570f252ec5 100644 --- a/shared/macros/10-oval.jinja +++ b/shared/macros/10-oval.jinja @@ -1702,3 +1702,25 @@ Generates an OVAL check that checks a particular field in the "/etc/shadow" file {{%- endif %}} {{%- endif %}} {{%- endmacro -%}} + +{{# +Generate OVAL test that tests if the system is configured to use nss-altfiles +by checking if '/etc/nssswitch.conf' contains 'altfiles' in 'group' key. +The macros generates the OVAL test including the dependent OVAL object and OVAL state. +#}} +{{%- macro oval_test_nsswitch_uses_altfiles() -%}} + + + + + + /etc/nsswitch.conf + ^\s*group:\s+(.*)$ + 1 + + + altfiles + +{{%- endmacro -%}} diff --git a/ssg/profiles.py b/ssg/profiles.py new file mode 100644 index 00000000000..d2a1af208d7 --- /dev/null +++ b/ssg/profiles.py @@ -0,0 +1,349 @@ +from __future__ import absolute_import +from __future__ import print_function + +import os +import sys +import yaml + +from .controls import ControlsManager, Policy +from .products import ( + get_profile_files_from_root, + load_product_yaml, + product_yaml_path, +) + + +if sys.version_info >= (3, 9): + dict_type = dict # Python 3.9+ supports built-in generics + list_type = list + tuple_type = tuple +else: + from typing import Dict as dict_type # Fallback for older versions + from typing import List as list_type + from typing import Tuple as tuple_type + + +class ProfileSelections: + """ + A class to represent profile with sections of rules and variables. + + Attributes: + ----------- + profile_id : str + The unique identifier for the profile. + profile_title : str + The profile title associated with the profile id. + product_id : str + The product id associated with the profile. + product_title : str + The product title associated with the product id. + """ + def __init__(self, profile_id, profile_title, product_id, product_title): + self.profile_id = profile_id + self.profile_title = profile_title + self.product_id = product_id + self.product_title = product_title + self.rules = [] + self.unselected_rules = [] + self.variables = {} + + +def _load_product_yaml(content_dir: str, product: str) -> object: + """ + Load the product YAML file and return its content as a Python object. + + Args: + content_dir (str): The directory where the content is stored. + product (str): The name of the product. + + Returns: + object: The loaded YAML content as a Python object. + """ + file_yaml_path = product_yaml_path(content_dir, product) + return load_product_yaml(file_yaml_path) + + +def _load_yaml_profile_file(file_path: str) -> dict_type: + """ + Load the content of a YAML file intended to profiles definitions. + + It is not necessary to process macros in this case. + + Args: + file_path (str): The path to the YAML file. + + Returns: + dict: The content of the YAML file as a dictionary. + """ + with open(file_path, 'r') as file: + try: + return yaml.safe_load(file) + except yaml.YAMLError as e: + print(f"Error loading YAML profile file {file_path}: {e}") + return {} + + +def _get_extended_profile_path(profiles_files: list, profile_name: str) -> str: + """ + Retrieve the full path of a profile file from a list of profile file paths. + + Args: + profiles_files (list of str): A list of file paths where profile files are located. + profile_name (str): The name of the profile to search for. + + Returns: + str: The full path of the profile file if found, otherwise None. + """ + profile_file = f"{profile_name}.profile" + profile_path = next((path for path in profiles_files if profile_file in path), None) + return profile_path + + +def _process_profile_extension(profile: ProfileSelections, profile_yaml: dict, + profiles_files: list, policies: dict) -> ProfileSelections: + """ + Processes the extension of a profile by recursively checking if the profile extends another + profile and updating the profile selections accordingly. + + Args: + profile (ProfileSelections): The profile object to be processed. + profile_yaml (dict): The YAML content of the current profile. + profiles_files (list): List of profile file paths. + policies (dict): The policies defined in the current profile. + + Returns: + ProfileSelections: The updated profile object. + """ + extended_profile = profile_yaml.get("extends") + if isinstance(extended_profile, str): + extended_profile = _get_extended_profile_path(profiles_files, extended_profile) + if extended_profile is not None: + profile_yaml = _load_yaml_profile_file(extended_profile) + return _process_profile(profile, profile_yaml, profiles_files, policies) + return profile + + +def _parse_control_line(control_line: str) -> tuple_type[str, str]: + """ + Parses a control line string and returns a tuple containing the first and third parts of the + string, separated by a colon. If the string does not contain three parts, the second element + of the tuple defaults to 'all'. + + Args: + control_line (str): The control line string to be parsed. + + Returns: + tuple[str, str]: A tuple containing the first part of the control line and either the + third part or 'all' if the third part is not present. + """ + parts = control_line.split(":") + if len(parts) == 3: + return parts[0], parts[2] + return parts[0], 'all' + + +def _process_selected_variable(profile: ProfileSelections, variable: str) -> None: + """ + Processes a selected variable and updates the profile's variables. + + Args: + profile (ProfileSelections): The profile object containing variables. + variable (str): The variable in the format 'name=value'. + + Raises: + ValueError: If the variable is not in the correct format. + """ + variable_name, variable_value = variable.split('=', 1) + if variable_name not in profile.variables: + profile.variables[variable_name] = variable_value + + +def _process_selected_rule(profile: ProfileSelections, rule: str) -> None: + """ + Adds a rule to the profile's selected rules if it is not already selected or unselected. + + Args: + profile (ProfileSelections): The profile containing selected and unselected rules. + rule (str): The rule to be added to the profile's selected rules. + + Returns: + None + """ + if rule not in profile.unselected_rules and rule not in profile.rules: + profile.rules.append(rule) + + +def _process_control(profile: ProfileSelections, control: object) -> None: + """ + Processes a control by iterating through its rules and applying the appropriate processing + function. Not that at this level rules list in control can include both variables and rules. + The function distinguishes between variable and rules based on the presence of an '=' + character in the rule. + + Args: + profile (ProfileSelections): The profile selections to be processed. + control: The control object containing rules to be processed. + """ + for rule in control.rules: + if "=" in rule: + _process_selected_variable(profile, rule) + else: + _process_selected_rule(profile, rule) + + +def _update_profile_with_policy(profile: ProfileSelections, policy: Policy, level: str) -> None: + """ + Updates the given profile with controls from the specified policy based on the provided level. + + Args: + profile (ProfileSelections): The profile to be updated. + policy (Policy): The policy containing controls to update the profile with. + level (str): The level of controls to be processed. If 'all', all controls are processed. + Otherwise, only controls matching the specified level are processed. + + Returns: + None + """ + for control in policy.controls: + if level == 'all' or level in control.levels: + _process_control(profile, control) + + +def _process_controls(profile: ProfileSelections, control_line: str, + policies: dict) -> ProfileSelections: + """ + Process a control file inheritance to update profile selections based on the given policies. + + Args: + profile (ProfileSelections): The profile object to be processed. + control_line (str): A string representing the control line, which contains a policy ID and + optionally a level, separated by colons. + policies (dict): A dictionary of policies, where each key is a policy ID and each value is + a policy object containing the controls. + + Returns: + ProfileSelections: The updated profile object. + """ + policy_id, level = _parse_control_line(control_line) + policy = policies.get(policy_id) + + if policy is None: + print(f"Policy {policy_id} not found") + return profile + + _update_profile_with_policy(profile, policy, level) + return profile + + +def _process_selections(profile: ProfileSelections, profile_yaml: dict, + policies: dict) -> ProfileSelections: + """ + Processes the selections from the profile YAML and updates the profile accordingly. + + Args: + profile (ProfileSelections): The profile object to be processed. + profile_yaml (dict): A dictionary containing the profile YAML data. + policies (dict): A dictionary containing policy information. + + Returns: + profile: The updated profile object. + """ + selections = profile_yaml.get("selections", []) + for selected in selections: + if selected.startswith("!"): + profile.unselected_rules.append(selected[1:]) + elif "=" in selected: + variable_name, variable_value = selected.split('=', 1) + profile.variables[variable_name] = variable_value + elif ":" in selected: + profile = _process_controls(profile, selected, policies) + else: + profile.rules.append(selected) + return profile + + +def _process_profile(profile: ProfileSelections, profile_yaml: dict, profiles_files: list, + policies: dict) -> ProfileSelections: + """ + Processes a profile by handling profile extensions, and processing selections. + + Args: + profile (ProfileSelections): The profile object to be processed. + profile_yaml (dict): The YAML content of the profile. + profiles_files (list): A list of profile file paths. + policies (dict): A dictionary of policies defined by control files. + + Returns: + ProfileSelections: The processed profile object. + """ + profile = _process_profile_extension(profile, profile_yaml, profiles_files, policies) + profile = _process_selections(profile, profile_yaml, policies) + return profile + + +def _load_controls_manager(controls_dir: str, product_yaml: dict) -> object: + """ + Loads and initializes a ControlsManager instance. + + Args: + controls_dir (str): The directory containing control files. + product_yaml (dict): The product configuration in YAML format. + + Returns: + object: An instance of ControlsManager with loaded controls. + """ + control_mgr = ControlsManager(controls_dir, product_yaml) + control_mgr.load() + return control_mgr + + +def _sort_profiles_selections(profiles: list) -> ProfileSelections: + """ + Sorts profiles selections (rules and variables) by selections ids. + + Args: + profiles (list): A list of ProfileSelections objects to be sorted. + + Returns: + ProfileSelections: The sorted list of ProfileSelections objects. + """ + for profile in profiles: + profile.rules = sorted(profile.rules) + profile.unselected_rules = sorted(profile.unselected_rules) + profile.variables = dict(sorted(profile.variables.items())) + return profiles + + +def get_profiles_from_products(content_dir: str, products: list, + sorted: bool = False) -> list_type: + """ + Retrieves profiles with respective variables from the given products. + + Args: + content_dir (str): The directory containing the content. + products (list): A list of product names to retrieve profiles from. + + Returns: + list: A list of ProfileVariables objects containing profile variables for each product. + """ + profiles = [] + controls_dir = os.path.join(content_dir, 'controls') + + for product in products: + product_yaml = _load_product_yaml(content_dir, product) + product_title = product_yaml.get("full_name") + profiles_files = get_profile_files_from_root(product_yaml, product_yaml) + controls_manager = _load_controls_manager(controls_dir, product_yaml) + for file in profiles_files: + profile_id = os.path.basename(file).split('.profile')[0] + profile_yaml = _load_yaml_profile_file(file) + profile_title = profile_yaml.get("title") + profile = ProfileSelections(profile_id, profile_title, product, product_title) + profile = _process_profile(profile, profile_yaml, profiles_files, + controls_manager.policies) + profiles.append(profile) + + if sorted: + profiles = _sort_profiles_selections(profiles) + + return profiles diff --git a/ssg/variables.py b/ssg/variables.py index 66757e01df1..3196ad9acf5 100644 --- a/ssg/variables.py +++ b/ssg/variables.py @@ -4,16 +4,10 @@ import glob import os import sys -import yaml from collections import defaultdict from .constants import BENCHMARKS -from .controls import ControlsManager -from .products import ( - get_profile_files_from_root, - load_product_yaml, - product_yaml_path, -) +from .profiles import get_profiles_from_products from .yaml import open_and_macro_expand @@ -25,8 +19,9 @@ from typing import Dict as dict_type -# Cache variable files to avoid multiple reads +# Cache variable files and respective content to avoid multiple reads _var_files_cache = {} +_vars_content_cache = {} def get_variable_files_in_folder(content_dir: str, subfolder: str) -> list_type[str]: @@ -69,26 +64,23 @@ def get_variable_files(content_dir: str) -> list_type[str]: return variable_files -def get_variable_options(content_dir: str, variable_id: str = None) -> dict_type: +def _get_variables_content(content_dir: str) -> dict_type: """ - Retrieve the options for specific or all variables from the content root directory. - - If `variable_id` is provided, returns options for that variable only. - If `variable_id` is not provided, returns a dictionary of all variable options. + Retrieve the content of all variable files from the specified content root directory. Args: content_dir (str): The root directory containing benchmark directories. - variable_id (str, optional): The ID of the variable to retrieve options for. - Defaults to None. Returns: - dict: If `variable_id` is None, a dictionary where keys are variable IDs and values are - their options. Otherwise, a dictionary of options for the specified variable. + dict: A dictionary where keys are variable IDs and values are the content of the variable + files. """ - all_variable_files = get_variable_files(content_dir) - all_options = {} + if content_dir in _vars_content_cache: + return _vars_content_cache[content_dir] - for var_file in all_variable_files: + variables_content = {} + + for var_file in get_variable_files(content_dir): try: yaml_content = open_and_macro_expand(var_file) except Exception as e: @@ -96,250 +88,70 @@ def get_variable_options(content_dir: str, variable_id: str = None) -> dict_type continue var_id = os.path.basename(var_file).split('.var')[0] - options = yaml_content.get("options", {}) - - if variable_id: - if var_id == variable_id: - return options - else: - all_options[var_id] = options - - if variable_id: - print(f"Variable {variable_id} not found") - return {} - - return all_options - - -class ProfileVariables: - """ - A class to represent profile variables. - - Attributes: - ----------- - profile_id : str - The unique identifier for the profile. - product : str - The product associated with the profile. - variables : dict - A dictionary containing the variables for the profile. - """ - def __init__(self, profile_id, product, variables): - self.profile_id = profile_id - self.product = product - self.variables = variables - - -def _load_product_yaml(content_dir: str, product: str) -> object: - """ - Load the product YAML file and return its content as a Python object. - - Args: - content_dir (str): The directory where the content is stored. - product (str): The name of the product. - - Returns: - object: The loaded YAML content as a Python object. - """ - file_yaml_path = product_yaml_path(content_dir, product) - return load_product_yaml(file_yaml_path) - - -def _load_yaml_profile_file(file_path: str) -> dict_type: - """ - Load the content of a YAML file intended to profiles definitions. - - It is not necessary to process macros in this case. - - Args: - file_path (str): The path to the YAML file. - - Returns: - dict: The content of the YAML file as a dictionary. - """ - with open(file_path, 'r') as file: - try: - return yaml.safe_load(file) - except yaml.YAMLError as e: - print(f"Error loading YAML profile file {file_path}: {e}") - return {} - - -def _get_extended_profile_path(profiles_files: list, profile_name: str) -> str: - """ - Retrieve the full path of a profile file from a list of profile file paths. - - Args: - profiles_files (list of str): A list of file paths where profile files are located. - profile_name (str): The name of the profile to search for. - - Returns: - str: The full path of the profile file if found, otherwise None. - """ - profile_file = f"{profile_name}.profile" - profile_path = next((path for path in profiles_files if profile_file in path), None) - return profile_path - - -def _process_profile_extension(profiles_files: list, profile_yaml: dict, - profile_variables: dict, policies: dict) -> dict_type: - """ - Processes the extension of a profile by recursively checking if the profile extends another - profile and updating the profile variables accordingly. - - Args: - profiles_files (list): List of profile file paths. - profile_yaml (dict): The YAML content of the current profile. - profile_variables (dict): The variables already defined in the current profile. - policies (dict): The policies defined in the current profile. - - Returns: - dict: The updated profile variables after processing the extended profile, - or the original profile variables if no extension is found. - """ - extended_profile = profile_yaml.get("extends") - if isinstance(extended_profile, str): - extended_profile = _get_extended_profile_path(profiles_files, extended_profile) - if extended_profile is not None: - return _process_profile(profiles_files, extended_profile, policies, profile_variables) - return profile_variables + variables_content[var_id] = yaml_content + _vars_content_cache[content_dir] = variables_content + return variables_content -def _process_controls(control_line: str, profile_variables: dict, policies: dict) -> dict_type: - """ - Process a control file inheritance to update profile variables based on the given policies. - - Args: - control_line (str): A string representing the control line, which contains a policy ID and - optionally a level, separated by colons. - profile_variables (dict): A dictionary of profile variables to be updated. - policies (dict): A dictionary of policies, where each key is a policy ID and each value is - a policy object containing the controls. - Returns: - dict: The updated profile variables dictionary. - - Raises: - KeyError: If the policy ID from the control line is not found in the policies dictionary. - """ - if control_line.count(":") == 2: - policy_id, _, level = control_line.split(":") - else: - policy_id, _ = control_line.split(":") - level = None - - try: - policy = policies[policy_id] - except KeyError: - print(f"Policy {policy_id} not found") - return profile_variables - - for control in policy.controls: - if level in control.levels: - for rule in control.rules: - if "=" in rule: - variable_name, variable_value = rule.split('=', 1) - # When a profile extends a control file, the variables explicitly defined in - # profiles files must be honored, so don't update variables already defined. - if variable_name not in profile_variables: - profile_variables[variable_name] = variable_value - return profile_variables - - -def _process_selections(profile_yaml: dict, profile_variables: dict, policies: dict) -> dict_type: +def get_variable_property(content_dir: str, variable_id: str, property_name: str) -> str: """ - Processes the selections from the profile YAML and updates the profile variables accordingly. + Retrieve a specific property of a variable from the content root directory. Args: - profile_yaml (dict): A dictionary containing the profile YAML data. - profile_variables (dict): A dictionary to store the profile variables. - policies (dict): A dictionary containing policy information. + content_dir (str): The root directory containing benchmark directories. + variable_id (str): The ID of the variable to retrieve the property for. + property_name (str): The name of the property to retrieve. Returns: - dict: The updated profile variables dictionary. - """ - selections = profile_yaml.get("selections") - for selected in selections: - if "=" in selected and "!" not in selected: - variable_name, variable_value = selected.split('=', 1) - profile_variables[variable_name] = variable_value - elif ":" in selected: - profile_variables = _process_controls(selected, profile_variables, policies) - return profile_variables - - -def _process_profile(profiles_files: list, file: str, policies: dict, - profile_variables={}) -> dict_type: + str: The value of the specified property for the variable. """ - Processes a profile by loading its YAML file, handling profile extensions, and processing - selections. + variables_content = _get_variables_content(content_dir) + variable_content = variables_content.get(variable_id, {}) + return variable_content.get(property_name, '') - Args: - profiles_files (list): A list of profile file paths. - file (str): The path to the profile file to be processed. - policies (dict): A dictionary of policies defined by control files. - profile_variables (dict, optional): A dictionary of profile variables. Defaults to empty. - Returns: - dict: A dictionary containing the processed profile variables. +def get_variable_options(content_dir: str, variable_id: str = None) -> dict_type: """ - profile_yaml = _load_yaml_profile_file(file) - profile_variables = _process_profile_extension(profiles_files, profile_yaml, - profile_variables, policies) - profile_variables = _process_selections(profile_yaml, profile_variables, policies) - return profile_variables - + Retrieve the options for specific or all variables from the content root directory. -def _load_controls_manager(controls_dir: str, product_yaml: dict) -> object: - """ - Loads and initializes a ControlsManager instance. + If `variable_id` is provided, returns options for that variable only. + If `variable_id` is not provided, returns a dictionary of all variables with their options. Args: - controls_dir (str): The directory containing control files. - product_yaml (dict): The product configuration in YAML format. + content_dir (str): The root directory containing benchmark directories. + variable_id (str, optional): The ID of the variable to retrieve options for. + Defaults to None. Returns: - object: An instance of ControlsManager with loaded controls. - """ - control_mgr = ControlsManager(controls_dir, product_yaml) - control_mgr.load() - return control_mgr - - -def _get_profiles_from_products(content_dir: str, products: list) -> list_type: + dict: If `variable_id` is None, a dictionary where keys are variable IDs and values are + their options. Otherwise, a dictionary of options for the specified variable. """ - Retrieves profiles with respective variables from the given products. + variables_content = _get_variables_content(content_dir) + all_options = {} - Args: - content_dir (str): The directory containing the content. - products (list): A list of product names to retrieve profiles from. + for var_id, var_yaml in variables_content.items(): + options = var_yaml.get("options", {}) - Returns: - list: A list of ProfileVariables objects containing profile variables for each product. - """ - profiles = [] - controls_dir = os.path.join(content_dir, 'controls') + if variable_id: + if var_id == variable_id: + return options + else: + all_options[var_id] = options - for product in products: - product_yaml = _load_product_yaml(content_dir, product) - profiles_files = get_profile_files_from_root(product_yaml, product_yaml) - controls_manager = _load_controls_manager(controls_dir, product_yaml) - for file in profiles_files: - profile_id = os.path.basename(file).split('.profile')[0] - profile_variables = _process_profile(profiles_files, file, controls_manager.policies) - profile = ProfileVariables(profile_id, product, profile_variables) - profiles.append(profile) + if variable_id: + print(f"Variable {variable_id} not found") + return {} - return profiles + return all_options -def _get_variables_from_profiles(profiles: list) -> dict_type: +def get_variables_from_profiles(profiles: list) -> dict_type: """ Extracts variables from a list of profiles and organizes them into a nested dictionary. Args: - profiles (list): A list of profile objects, each containing variables, product, and id - attributes. + profiles (list): A list of profile objects, each containing selections and id attributes. Returns: dict: A nested dictionary where the first level keys are variable names, the second level @@ -349,8 +161,8 @@ def _get_variables_from_profiles(profiles: list) -> dict_type: variables = defaultdict(lambda: defaultdict(dict)) for profile in profiles: for variable, value in profile.variables.items(): - variables[variable][profile.product][profile.profile_id] = value - return variables + variables[variable][profile.product_id][profile.profile_id] = value + return _convert_defaultdict_to_dict(variables) def _convert_defaultdict_to_dict(dictionary: defaultdict) -> dict_type: @@ -373,7 +185,8 @@ def get_variables_by_products(content_dir: str, products: list) -> dict_type[str Retrieve variables by products from the specified content root directory. This function collects profiles for the given products and extracts variables from these - profiles. + profiles. If you already have a list of Profiles obtained by get_profiles_from_products() + defined in profiles.py, consider to use get_variables_from_profiles() instead. Args: content_dir (str): The root directory of the content. @@ -383,8 +196,8 @@ def get_variables_by_products(content_dir: str, products: list) -> dict_type[str dict: A dictionary where keys are variable names and values are dictionaries of product-profile pairs. """ - profiles = _get_profiles_from_products(content_dir, products) - profiles_variables = _get_variables_from_profiles(profiles) + profiles = get_profiles_from_products(content_dir, products) + profiles_variables = get_variables_from_profiles(profiles) return _convert_defaultdict_to_dict(profiles_variables) diff --git a/tests/data/profile_stability/rhel8/stig.profile b/tests/data/profile_stability/rhel8/stig.profile index 7090a294f4d..eba79eae8f5 100644 --- a/tests/data/profile_stability/rhel8/stig.profile +++ b/tests/data/profile_stability/rhel8/stig.profile @@ -53,8 +53,6 @@ selections: - accounts_password_pam_minclass - accounts_password_pam_minlen - accounts_password_pam_ocredit -- accounts_password_pam_pwhistory_remember_password_auth -- accounts_password_pam_pwhistory_remember_system_auth - accounts_password_pam_pwquality_password_auth - accounts_password_pam_pwquality_system_auth - accounts_password_pam_retry diff --git a/tests/data/profile_stability/rhel8/stig_gui.profile b/tests/data/profile_stability/rhel8/stig_gui.profile index fcbe059e49b..c98305dc5a5 100644 --- a/tests/data/profile_stability/rhel8/stig_gui.profile +++ b/tests/data/profile_stability/rhel8/stig_gui.profile @@ -64,8 +64,6 @@ selections: - accounts_password_pam_minclass - accounts_password_pam_minlen - accounts_password_pam_ocredit -- accounts_password_pam_pwhistory_remember_password_auth -- accounts_password_pam_pwhistory_remember_system_auth - accounts_password_pam_pwquality_password_auth - accounts_password_pam_pwquality_system_auth - accounts_password_pam_retry diff --git a/tests/unit/ssg-module/test_profiles.py b/tests/unit/ssg-module/test_profiles.py new file mode 100644 index 00000000000..c1ac79afd64 --- /dev/null +++ b/tests/unit/ssg-module/test_profiles.py @@ -0,0 +1,40 @@ +import os +import pytest + +from ssg.products import ( + get_all, + get_profile_files_from_root, +) +from ssg.profiles import ( + get_profiles_from_products, + _load_product_yaml, +) + + +# The get_profiles_from_products function interacts with many objects and other functions that +# would be complex to mock. So it will be tested with a real content directory. To make it +# predictable, all existing products will be collected and the first rhel product will be used +# for testing. The decision to use a rhel product is that I am more used to them and I know their +# profiles also use control files. +content_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) + +def get_first_rhel_product_from_products_dir(): + products = get_all(content_dir) + rhel_products = [product for product in products.linux if "rhel" in product] + return rhel_products[0] + + +def count_profiles_in_products_dir(product): + product_yaml = _load_product_yaml(content_dir, product) + profiles_files = get_profile_files_from_root(product_yaml, product_yaml) + return len(profiles_files) + + +def test_get_profiles_from_products(): + products = [get_first_rhel_product_from_products_dir()] + profiles = get_profiles_from_products(content_dir, products, sorted=True) + + assert len(profiles) == count_profiles_in_products_dir(products[0]) + assert 'rhel' in profiles[0].product_id + assert len(profiles[0].rules) > 0 + assert len(profiles[0].variables) > 0 diff --git a/tests/unit/ssg-module/test_variables.py b/tests/unit/ssg-module/test_variables.py index 89e8c77a70b..1858b5d36bd 100644 --- a/tests/unit/ssg-module/test_variables.py +++ b/tests/unit/ssg-module/test_variables.py @@ -6,7 +6,9 @@ get_variable_files_in_folder, get_variable_files, get_variable_options, + get_variable_property, get_variables_by_products, + get_variables_from_profiles, get_variable_values, ) @@ -24,7 +26,10 @@ def setup_test_files(base_dir, benchmark_dirs, create_txt_file=False): path = base_dir / benchmark_dir os.makedirs(path, exist_ok=True) var_file = path / "test.var" - var_file.write_text("options:\n default: value\n option1: value1\n option2: value2\n") + var_file.write_text( + "options:\n default: value\n option1: value1\n option2: value2\n" + "title: Test Title\ndescription: Test Description\n" + ) if create_txt_file: txt_file = path / "test.txt" txt_file.write_text("options:\n option: value\n") @@ -79,3 +84,53 @@ def test_get_variable_values(tmp_path): result = get_variable_values(str(content_dir), profiles_variables) assert result["test"]["product1"]["profile1"] == "value1" assert result["test"]["product2"]["profile2"] == "value2" + + +def test_get_variables_from_profiles(): + class MockProfile: + def __init__(self, product_id, profile_id, variables): + self.product_id = product_id + self.profile_id = profile_id + self.variables = variables + + profiles = [ + MockProfile("product1", "profile1", {"var1": "value1", "var2": "value2"}), + MockProfile("product1", "profile2", {"var1": "value3"}), + MockProfile("product2", "profile1", {"var2": "value4"}), + ] + + expected_result = { + "var1": { + "product1": { + "profile1": "value1", + "profile2": "value3", + } + }, + "var2": { + "product1": { + "profile1": "value2", + }, + "product2": { + "profile1": "value4", + } + } + } + + result = get_variables_from_profiles(profiles) + assert result == expected_result + +def test_get_variable_property(tmp_path): + content_dir = tmp_path / "content" + benchmark_dirs = ["app", "app/rules"] + setup_test_files(content_dir, benchmark_dirs) + + result = get_variable_property(str(content_dir), "test", "title") + assert result == "Test Title" + + # Test for a non-existent property + result = get_variable_property(str(content_dir), "test", "non_existent_property") + assert result == "" + + # Test for a non-existent variable + result = get_variable_property(str(content_dir), "non_existent_variable", "property_name") + assert result == ""