diff --git a/CMakeLists.txt b/CMakeLists.txt index c58ed4c74078..69628878da70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,7 +151,7 @@ find_program(XMLLINT_EXECUTABLE NAMES xmllint REQUIRED) find_program(XSLTPROC_EXECUTABLE NAMES xsltproc REQUIRED) find_program(YAMLLINT_EXECUTABLE NAMES yamllint) -if(SSG_PRODUCT_FEDORA OR SSG_PRODUCT_RHEL8 OR SSG_PRODUCT_RHEL9 OR SSG_PRODUCT_RHEL10 OR SSG_PRODUCT_UBUNTU2004 OR SSG_PRODUCT_UBUNTU2204 OR SSG_PRODUCT_UBUNTU2404) +if(SSG_PRODUCT_FEDORA OR SSG_PRODUCT_RHEL8 OR SSG_PRODUCT_RHEL9 OR SSG_PRODUCT_RHEL10 OR SSG_PRODUCT_UBUNTU2004 OR SSG_PRODUCT_UBUNTU2204 OR SSG_PRODUCT_UBUNTU2404 OR SSG_PRODUCT_UBUNTU2604) set(SSG_SCE_ENABLED ON) endif() diff --git a/components/apt.yml b/components/apt.yml index d8afbd8eb65d..f8be65f70e23 100644 --- a/components/apt.yml +++ b/components/apt.yml @@ -7,7 +7,13 @@ rules: - apt_conf_disallow_unauthenticated - apt_conf_disable_recommends - apt_conf_disable_suggests +- apt_configure_allow_downgrade_to_insecure_repositories +- apt_configure_allow_insecure_repositories +- apt_configure_allow_weak_repositories +- apt_configure_check_date - apt_disable_weak_dependencies +- apt_sources_list_d_https +- apt_sources_list_https - apt_sources_list_official - directory_groupowner_apt_auth_conf_d - directory_groupowner_apt_sources_list_d @@ -30,4 +36,3 @@ rules: - file_permissions_apt_auth_conf_d - file_permissions_apt_gpg_keys - file_permissions_apt_sources_list_d - diff --git a/components/coreutils.yml b/components/coreutils.yml index 45709b5c8b33..999fccf8ed42 100644 --- a/components/coreutils.yml +++ b/components/coreutils.yml @@ -2,6 +2,9 @@ name: coreutils packages: - coreutils rules: +- accounts_user_dot_dirs_groupownership +- accounts_user_dot_dirs_ownership +- accounts_user_dot_dirs_permissions - accounts_user_dot_group_ownership - accounts_user_dot_no_world_writable_programs - accounts_user_dot_user_ownership diff --git a/components/cracklib-runtime.yml b/components/cracklib-runtime.yml new file mode 100644 index 000000000000..212841831690 --- /dev/null +++ b/components/cracklib-runtime.yml @@ -0,0 +1,5 @@ +name: cracklib-runtime +packages: +- cracklib-runtime +rules: +- package_cracklib-runtime_installed diff --git a/components/openssh.yml b/components/openssh.yml index b8e08696d8b5..96d33debd502 100644 --- a/components/openssh.yml +++ b/components/openssh.yml @@ -51,6 +51,7 @@ rules: - sshd_allow_only_protocol2 - sshd_disable_compression - sshd_disable_empty_passwords +- sshd_disable_empty_passwords_default - sshd_disable_forwarding - sshd_disable_gssapi_auth - sshd_disable_kerb_auth @@ -77,6 +78,7 @@ rules: - sshd_set_idle_timeout - sshd_set_keepalive - sshd_set_keepalive_0 +- sshd_set_keepalive_positive - sshd_set_login_grace_time - sshd_set_loglevel_info - sshd_set_loglevel_verbose @@ -92,6 +94,7 @@ rules: - sshd_use_priv_separation - sshd_use_strong_ciphers - sshd_use_strong_kex +- sshd_use_strong_kex_ubuntu2604 - sshd_use_strong_macs - sshd_use_strong_rng - sshd_x11_use_localhost diff --git a/components/pam.yml b/components/pam.yml index 5db9f0134893..b4ccf2781804 100644 --- a/components/pam.yml +++ b/components/pam.yml @@ -126,6 +126,7 @@ rules: - file_permissions_etc_issue - file_permissions_etc_issue_net - file_permissions_etc_motd +- file_permissions_pam_motd - file_permissions_lastlog - group_unique_id - group_unique_name @@ -157,6 +158,7 @@ rules: - package_pcsc-lite_installed - package_screen_installed - pam_disable_automatic_configuration +- pam_motd_configured - passwd_system-auth_substack - policy_temp_passwords_immediate_change - prevent_direct_root_logins diff --git a/components/sudo.yml b/components/sudo.yml index 752c426ef8e2..1ea3c758cdd6 100644 --- a/components/sudo.yml +++ b/components/sudo.yml @@ -19,6 +19,7 @@ rules: - sudo_add_umask - sudo_add_use_pty - sudo_custom_logfile +- sudo_logging_ubuntu2604 - sudo_dedicated_group - sudo_remove_no_authenticate - sudo_remove_nopasswd diff --git a/components/update-notifier-common.yml b/components/update-notifier-common.yml new file mode 100644 index 000000000000..e962aff2d504 --- /dev/null +++ b/components/update-notifier-common.yml @@ -0,0 +1,6 @@ +name: update-notifier-common +packages: +- update-notifier-common +rules: +- service_update-notifier-motd_disabled +- timer_update-notifier-motd_disabled diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/bash/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/bash/shared.sh new file mode 100644 index 000000000000..b04f33f0124b --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/bash/shared.sh @@ -0,0 +1,23 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +config_file=/etc/apt/apt.conf.d/99-cis-repository-security +option='Acquire::AllowDowngradeToInsecureRepositories' +option_pattern='(Acquire::)?AllowDowngradeToInsecureRepositories' +setting='Acquire::AllowDowngradeToInsecureRepositories "0";' + +touch "$config_file" + +# Remove existing definitions before writing the required value. Otherwise, +# APT file precedence could leave a conflicting definition in effect. +while IFS= read -r -d '' apt_conf_file; do + sed -ri "/^[[:space:]]*${option_pattern}[[:space:]]+/Id" "$apt_conf_file" +done < <(find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -print0 2>/dev/null) + +if [ -s "$config_file" ] && [ -n "$(tail -c 1 "$config_file")" ]; then + printf '\n' >> "$config_file" +fi +printf '%s\n' "$setting" >> "$config_file" diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/rule.yml b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/rule.yml new file mode 100644 index 000000000000..2d403339f0d8 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/rule.yml @@ -0,0 +1,12 @@ +documentation_complete: true + +title: 'Disable APT AllowDowngradeToInsecureRepositories' + +description: |- + Configure the effective APT setting with + Acquire::AllowDowngradeToInsecureRepositories "0";. + +rationale: |- + A repository that loses authentication must not silently remain usable. + +severity: high diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/sce/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/sce/shared.sh new file mode 100644 index 000000000000..b0f07ad6ec38 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/sce/shared.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# platform = multi_platform_ubuntu +# check-import = stdout + +explicit=false +for config_file in /etc/apt/apt.conf /etc/apt/apt.conf.d/*; do + [[ -f "$config_file" ]] || continue + [[ "$config_file" == /etc/apt/apt.conf || ${config_file##*/} =~ ^[A-Za-z0-9_-]+$ ]] || continue + grep -Piq '^[\h]*(Acquire::)?AllowDowngradeToInsecureRepositories\h+' "$config_file" && explicit=true +done + +if [[ "$explicit" == true ]] && apt-config dump 2>/dev/null | grep -Piq '^Acquire::AllowDowngradeToInsecureRepositories\s+"?(0|false|no|without|off)"?;$'; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'Acquire::AllowDowngradeToInsecureRepositories is not explicitly configured with a disabled value.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/commented_only.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/commented_only.fail.sh new file mode 100644 index 000000000000..6e9f4ab5ef06 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/commented_only.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowDowngradeToInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true +echo '# Acquire::AllowDowngradeToInsecureRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/correct_value.pass.sh new file mode 100644 index 000000000000..0c9c1bcfaf9d --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/correct_value.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowDowngradeToInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowDowngradeToInsecureRepositories "0";' >> /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/missing.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/missing.fail.sh new file mode 100644 index 000000000000..9991fca61917 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/missing.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowDowngradeToInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_correct_value.pass.sh new file mode 100644 index 000000000000..71756189a850 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_correct_value.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowDowngradeToInsecureRepositories "0";' '};' > /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_wrong_value.fail.sh new file mode 100644 index 000000000000..828edcb25933 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/nested_wrong_value.fail.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowDowngradeToInsecureRepositories "1";' '};' > /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value.fail.sh new file mode 100644 index 000000000000..c97c45ae0c6c --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value.fail.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowDowngradeToInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowDowngradeToInsecureRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh new file mode 100644 index 000000000000..efd08c782bfa --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_downgrade_to_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowDowngradeToInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +# APT accepts case-insensitive option names and unquoted values. +echo 'acquire::allowdowngradetoinsecurerepositories 1;' >> /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/bash/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/bash/shared.sh new file mode 100644 index 000000000000..bcc66984ba25 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/bash/shared.sh @@ -0,0 +1,23 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +config_file=/etc/apt/apt.conf.d/99-cis-repository-security +option='Acquire::AllowInsecureRepositories' +option_pattern='(Acquire::)?AllowInsecureRepositories' +setting='Acquire::AllowInsecureRepositories "0";' + +touch "$config_file" + +# Remove existing definitions before writing the required value. Otherwise, +# APT file precedence could leave a conflicting definition in effect. +while IFS= read -r -d '' apt_conf_file; do + sed -ri "/^[[:space:]]*${option_pattern}[[:space:]]+/Id" "$apt_conf_file" +done < <(find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -print0 2>/dev/null) + +if [ -s "$config_file" ] && [ -n "$(tail -c 1 "$config_file")" ]; then + printf '\n' >> "$config_file" +fi +printf '%s\n' "$setting" >> "$config_file" diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/rule.yml b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/rule.yml new file mode 100644 index 000000000000..cf0065dcd87d --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/rule.yml @@ -0,0 +1,12 @@ +documentation_complete: true + +title: 'Disable APT AllowInsecureRepositories' + +description: |- + Configure the effective APT setting with + Acquire::AllowInsecureRepositories "0";. + +rationale: |- + Insecure repositories do not provide authenticated package metadata. + +severity: high diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/sce/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/sce/shared.sh new file mode 100644 index 000000000000..e8c2df4f5cb8 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/sce/shared.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# platform = multi_platform_ubuntu +# check-import = stdout + +explicit=false +for config_file in /etc/apt/apt.conf /etc/apt/apt.conf.d/*; do + [[ -f "$config_file" ]] || continue + [[ "$config_file" == /etc/apt/apt.conf || ${config_file##*/} =~ ^[A-Za-z0-9_-]+$ ]] || continue + grep -Piq '^[\h]*(Acquire::)?AllowInsecureRepositories\h+' "$config_file" && explicit=true +done + +if [[ "$explicit" == true ]] && apt-config dump 2>/dev/null | grep -Piq '^Acquire::AllowInsecureRepositories\s+"?(0|false|no|without|off)"?;$'; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'Acquire::AllowInsecureRepositories is not explicitly configured with a disabled value.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/commented_only.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/commented_only.fail.sh new file mode 100644 index 000000000000..6e1715d1b419 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/commented_only.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true +echo '# Acquire::AllowInsecureRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/correct_value.pass.sh new file mode 100644 index 000000000000..ae355514373d --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/correct_value.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowInsecureRepositories "0";' >> /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/missing.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/missing.fail.sh new file mode 100644 index 000000000000..10af695c55c8 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/missing.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_correct_value.pass.sh new file mode 100644 index 000000000000..7ea9a0c1e48a --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_correct_value.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowInsecureRepositories "0";' '};' > /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_wrong_value.fail.sh new file mode 100644 index 000000000000..fc7c79d4989b --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/nested_wrong_value.fail.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowInsecureRepositories "1";' '};' > /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value.fail.sh new file mode 100644 index 000000000000..77c0f2a0164e --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value.fail.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowInsecureRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh new file mode 100644 index 000000000000..6eebf5aa27e2 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_insecure_repositories/tests/wrong_value_unquoted_dir.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowInsecureRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +# APT accepts case-insensitive option names and unquoted values. +echo 'acquire::allowinsecurerepositories 1;' >> /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/bash/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/bash/shared.sh new file mode 100644 index 000000000000..524a53a1c627 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/bash/shared.sh @@ -0,0 +1,23 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +config_file=/etc/apt/apt.conf.d/99-cis-repository-security +option='Acquire::AllowWeakRepositories' +option_pattern='(Acquire::)?AllowWeakRepositories' +setting='Acquire::AllowWeakRepositories "0";' + +touch "$config_file" + +# Remove existing definitions before writing the required value. Otherwise, +# APT file precedence could leave a conflicting definition in effect. +while IFS= read -r -d '' apt_conf_file; do + sed -ri "/^[[:space:]]*${option_pattern}[[:space:]]+/Id" "$apt_conf_file" +done < <(find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -print0 2>/dev/null) + +if [ -s "$config_file" ] && [ -n "$(tail -c 1 "$config_file")" ]; then + printf '\n' >> "$config_file" +fi +printf '%s\n' "$setting" >> "$config_file" diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/rule.yml b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/rule.yml new file mode 100644 index 000000000000..1087436a9cf1 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/rule.yml @@ -0,0 +1,13 @@ +documentation_complete: true + +title: 'Disable APT AllowWeakRepositories' + +description: |- + Configure the effective APT setting with + Acquire::AllowWeakRepositories "0";. + +rationale: |- + Weak repositories use package metadata authentication that does not meet + current cryptographic requirements. + +severity: high diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/sce/shared.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/sce/shared.sh new file mode 100644 index 000000000000..45174e7a8fe8 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/sce/shared.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# platform = multi_platform_ubuntu +# check-import = stdout + +explicit=false +for config_file in /etc/apt/apt.conf /etc/apt/apt.conf.d/*; do + [[ -f "$config_file" ]] || continue + [[ "$config_file" == /etc/apt/apt.conf || ${config_file##*/} =~ ^[A-Za-z0-9_-]+$ ]] || continue + grep -Piq '^[\h]*(Acquire::)?AllowWeakRepositories\h+' "$config_file" && explicit=true +done + +if [[ "$explicit" == true ]] && apt-config dump 2>/dev/null | grep -Piq '^Acquire::AllowWeakRepositories\s+"?(0|false|no|without|off)"?;$'; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'Acquire::AllowWeakRepositories is not explicitly configured with a disabled value.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/commented_only.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/commented_only.fail.sh new file mode 100644 index 000000000000..7b9bdc9061bf --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/commented_only.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowWeakRepositories[[:space:]]+/Id' {} + 2>/dev/null || true +echo '# Acquire::AllowWeakRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/correct_value.pass.sh new file mode 100644 index 000000000000..363d600e25aa --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/correct_value.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowWeakRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowWeakRepositories "0";' >> /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/missing.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/missing.fail.sh new file mode 100644 index 000000000000..4626fe1d1867 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/missing.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?AllowWeakRepositories[[:space:]]+/Id' {} + 2>/dev/null || true diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_correct_value.pass.sh new file mode 100644 index 000000000000..eb5b45511e7e --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_correct_value.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowWeakRepositories "0";' '};' > /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_wrong_value.fail.sh new file mode 100644 index 000000000000..d11edbf0e15b --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/nested_wrong_value.fail.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' AllowWeakRepositories "1";' '};' > /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value.fail.sh new file mode 100644 index 000000000000..17f2d12aa1f7 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value.fail.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowWeakRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::AllowWeakRepositories "1";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value_unquoted_dir.fail.sh b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value_unquoted_dir.fail.sh new file mode 100644 index 000000000000..d3e7cc676955 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_allow_weak_repositories/tests/wrong_value_unquoted_dir.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::AllowWeakRepositories[[:space:]]+/Id' {} + 2>/dev/null || true + +# APT accepts case-insensitive option names and unquoted values. +echo 'acquire::allowweakrepositories 1;' >> /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_check_date/bash/shared.sh b/linux_os/guide/services/apt/apt_configure_check_date/bash/shared.sh new file mode 100644 index 000000000000..126f6bda5312 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/bash/shared.sh @@ -0,0 +1,23 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +config_file=/etc/apt/apt.conf.d/99-cis-repository-security +option='Acquire::Check-Date' +option_pattern='(Acquire::)?Check-Date' +setting='Acquire::Check-Date "true";' + +touch "$config_file" + +# Remove existing definitions before writing the required value. Otherwise, +# APT file precedence could leave a conflicting definition in effect. +while IFS= read -r -d '' apt_conf_file; do + sed -ri "/^[[:space:]]*${option_pattern}[[:space:]]+/Id" "$apt_conf_file" +done < <(find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -print0 2>/dev/null) + +if [ -s "$config_file" ] && [ -n "$(tail -c 1 "$config_file")" ]; then + printf '\n' >> "$config_file" +fi +printf '%s\n' "$setting" >> "$config_file" diff --git a/linux_os/guide/services/apt/apt_configure_check_date/rule.yml b/linux_os/guide/services/apt/apt_configure_check_date/rule.yml new file mode 100644 index 000000000000..ee50a796d917 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/rule.yml @@ -0,0 +1,13 @@ +documentation_complete: true + +title: 'Enable APT Check-Date' + +description: |- + Configure the effective APT setting with + Acquire::Check-Date "true";. + +rationale: |- + APT must validate repository metadata dates to reject stale or not-yet-valid + repository metadata. + +severity: high diff --git a/linux_os/guide/services/apt/apt_configure_check_date/sce/shared.sh b/linux_os/guide/services/apt/apt_configure_check_date/sce/shared.sh new file mode 100644 index 000000000000..b0ac05efe5fd --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/sce/shared.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# platform = multi_platform_ubuntu +# check-import = stdout + +explicit=false +for config_file in /etc/apt/apt.conf /etc/apt/apt.conf.d/*; do + [[ -f "$config_file" ]] || continue + [[ "$config_file" == /etc/apt/apt.conf || ${config_file##*/} =~ ^[A-Za-z0-9_-]+$ ]] || continue + grep -Piq '^[\h]*(Acquire::)?Check-Date\h+' "$config_file" && explicit=true +done + +if [[ "$explicit" == true ]] && apt-config dump 2>/dev/null | grep -Piq '^Acquire::Check-Date\s+"?(1|true|yes|with|on)"?;$'; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'Acquire::Check-Date is not explicitly configured with an enabled value.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/commented_only.fail.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/commented_only.fail.sh new file mode 100644 index 000000000000..a015133408de --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/commented_only.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?Check-Date[[:space:]]+/Id' {} + 2>/dev/null || true +echo '# Acquire::Check-Date "false";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/correct_value.pass.sh new file mode 100644 index 000000000000..25bb94b98d42 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/correct_value.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::Check-Date[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::Check-Date "true";' >> /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/missing.fail.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/missing.fail.sh new file mode 100644 index 000000000000..ad6a4cd63b03 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/missing.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*(Acquire::)?Check-Date[[:space:]]+/Id' {} + 2>/dev/null || true diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_correct_value.pass.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_correct_value.pass.sh new file mode 100644 index 000000000000..146537538fda --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_correct_value.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' Check-Date "true";' '};' > /etc/apt/apt.conf.d/99-cis-repository-security diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_wrong_value.fail.sh new file mode 100644 index 000000000000..e24ca1819772 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/nested_wrong_value.fail.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +printf '%s\n' 'Acquire {' ' Check-Date "false";' '};' > /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value.fail.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value.fail.sh new file mode 100644 index 000000000000..f7a7078c6625 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value.fail.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::Check-Date[[:space:]]+/Id' {} + 2>/dev/null || true + +echo 'Acquire::Check-Date "false";' >> /etc/apt/apt.conf diff --git a/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value_unquoted_dir.fail.sh b/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value_unquoted_dir.fail.sh new file mode 100644 index 000000000000..8a56e23ca6a8 --- /dev/null +++ b/linux_os/guide/services/apt/apt_configure_check_date/tests/wrong_value_unquoted_dir.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +mkdir -p /etc/apt/apt.conf.d +touch /etc/apt/apt.conf +find /etc/apt/apt.conf /etc/apt/apt.conf.d -maxdepth 1 -type f -exec sed -ri '/^[[:space:]]*Acquire::Check-Date[[:space:]]+/Id' {} + 2>/dev/null || true + +# APT accepts case-insensitive option names and unquoted values. +echo 'acquire::check-date false;' >> /etc/apt/apt.conf.d/70-cac-test diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/bash/shared.sh b/linux_os/guide/services/apt/apt_sources_list_d_https/bash/shared.sh new file mode 100644 index 000000000000..3ff6c5c42b75 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/bash/shared.sh @@ -0,0 +1,13 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = high + +while IFS= read -r -d '' file; do + sed -ri '/^[[:space:]]*deb(-src)?[[:space:]]+(\[[^]]*\][[:space:]]+)?http:\/\// s#http://#https://#' "$file" +done < <(find /etc/apt/sources.list.d -maxdepth 1 -type f -name '*.list' -print0 2>/dev/null) + +while IFS= read -r -d '' file; do + sed -ri '/^[[:space:]]*URIs:[[:space:]]/I s#http://#https://#g' "$file" +done < <(find /etc/apt/sources.list.d -maxdepth 1 -type f -name '*.sources' -print0 2>/dev/null) diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/oval/shared.xml b/linux_os/guide/services/apt/apt_sources_list_d_https/oval/shared.xml new file mode 100644 index 000000000000..ac8e2877df18 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/oval/shared.xml @@ -0,0 +1,35 @@ + + + {{{ oval_metadata("APT repositories in /etc/apt/sources.list.d use HTTPS.", rule_title=rule_title) }}} + + + + + + + + + + + /etc/apt/sources.list.d + .*\.list$ + ^[\s]*deb(-src)?[\s]+(\[[^\]]*\][\s]+)?http://.*$ + 1 + + + + + + + /etc/apt/sources.list.d + .*\.sources$ + ^[\s]*(?i)URIs(?-i):[\s]+[^#\n]*http://.*$ + 1 + + diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/rule.yml b/linux_os/guide/services/apt/apt_sources_list_d_https/rule.yml new file mode 100644 index 000000000000..2bed5e6564c0 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/rule.yml @@ -0,0 +1,30 @@ +documentation_complete: true + +title: 'Use HTTPS for Repositories in /etc/apt/sources.list.d' + +description: |- + Active legacy and deb822 APT repository entries below + /etc/apt/sources.list.d must use HTTPS rather than HTTP. + +rationale: |- + HTTPS protects repository metadata and package downloads from network + observation and modification in transit. + +severity: high + +warnings: + - general: |- + Ubuntu ships /etc/apt/sources.list.d/ubuntu.sources using + http://archive.ubuntu.com and http://security.ubuntu.com, + so this rule fails on a default installation. The remediation rewrites + every active http:// entry to https://, including entries + for third-party repositories. A repository that does not serve HTTPS will + become unreachable and apt operations will fail until the entry is + corrected by hand. Review the repositories in use before remediating. + +ocil_clause: 'an active repository entry uses HTTP' + +ocil: |- + Search *.list files for active deb http:// entries and + *.sources files for active URIs: http:// entries. Neither + search may produce output. diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822.fail.sh b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822.fail.sh new file mode 100644 index 000000000000..a9d75e8ac706 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822.fail.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +find /etc/apt/sources.list.d -maxdepth 1 -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -ri 's#http://#https://#g' {} + 2>/dev/null || true +cat > /etc/apt/sources.list.d/cac-test.sources <<'EOF' +Types: deb +URIs: http://archive.ubuntu.com/ubuntu +Suites: resolute +Components: main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822_second_uri.fail.sh b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822_second_uri.fail.sh new file mode 100644 index 000000000000..d310217a9d93 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_deb822_second_uri.fail.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +find /etc/apt/sources.list.d -maxdepth 1 -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -ri 's#http://#https://#g' {} + 2>/dev/null || true +# Check every URL on a URIs line, including HTTP URLs after an HTTPS URL. +cat > /etc/apt/sources.list.d/cac-test.sources <<'EOF' +Types: deb +URIs: https://archive.ubuntu.com/ubuntu http://security.ubuntu.com/ubuntu +Suites: resolute +Components: main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_legacy_list.fail.sh b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_legacy_list.fail.sh new file mode 100644 index 000000000000..2cd769146e6f --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/http_legacy_list.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +find /etc/apt/sources.list.d -maxdepth 1 -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -ri 's#http://#https://#g' {} + 2>/dev/null || true +cat > /etc/apt/sources.list.d/cac-test.list <<'EOF' +deb [arch=amd64] http://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_d_https/tests/https_only.pass.sh b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/https_only.pass.sh new file mode 100644 index 000000000000..d0b3e1f49e01 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_d_https/tests/https_only.pass.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +find /etc/apt/sources.list.d -maxdepth 1 -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -ri 's#http://#https://#g' {} + 2>/dev/null || true +cat > /etc/apt/sources.list.d/cac-test.sources <<'EOF' +Types: deb +URIs: https://archive.ubuntu.com/ubuntu +Suites: resolute +Components: main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_https/bash/shared.sh b/linux_os/guide/services/apt/apt_sources_list_https/bash/shared.sh new file mode 100644 index 000000000000..a508d9c70a68 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/bash/shared.sh @@ -0,0 +1,9 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = restrict +# complexity = low +# disruption = medium + +if [[ -f /etc/apt/sources.list ]]; then + sed -ri '/^[[:space:]]*deb(-src)?[[:space:]]+(\[[^]]*\][[:space:]]+)?http:\/\// s#http://#https://#' /etc/apt/sources.list +fi diff --git a/linux_os/guide/services/apt/apt_sources_list_https/oval/shared.xml b/linux_os/guide/services/apt/apt_sources_list_https/oval/shared.xml new file mode 100644 index 000000000000..4d41e9ec9ef5 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/oval/shared.xml @@ -0,0 +1,20 @@ + + + {{{ oval_metadata("APT repositories in /etc/apt/sources.list use HTTPS.", rule_title=rule_title) }}} + + + + + + + + + + /etc/apt/sources.list + ^[\s]*deb(-src)?[\s]+(\[[^\]]*\][\s]+)?http://.*$ + 1 + + diff --git a/linux_os/guide/services/apt/apt_sources_list_https/rule.yml b/linux_os/guide/services/apt/apt_sources_list_https/rule.yml new file mode 100644 index 000000000000..0edc45d10062 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/rule.yml @@ -0,0 +1,20 @@ +documentation_complete: true + +title: 'Use HTTPS for Repositories in /etc/apt/sources.list' + +description: |- + Active APT repository entries in /etc/apt/sources.list, including + deb-src entries and entries carrying an options block such as + [signed-by=...], must use HTTPS rather than HTTP. + +rationale: |- + HTTPS protects repository metadata and package downloads from network + observation and modification in transit. + +severity: high + +ocil_clause: 'an active repository entry uses HTTP' + +ocil: |- + Run grep -Pi '^\h*deb(-src)?\h+(\[[^\]]*\]\h+)?http://' /etc/apt/sources.list. + The command must not produce output. diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/absent_sources_list.pass.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/absent_sources_list.pass.sh new file mode 100644 index 000000000000..5bbe4348362a --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/absent_sources_list.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Ubuntu 26.04 uses deb822 by default and may omit /etc/apt/sources.list. +rm -f /etc/apt/sources.list diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/commented_http.pass.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/commented_http.pass.sh new file mode 100644 index 000000000000..f723af74e86c --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/commented_http.pass.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +cat > /etc/apt/sources.list <<'EOF' +# deb http://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/http_deb_src.fail.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_deb_src.fail.sh new file mode 100644 index 000000000000..4d9600c08e24 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_deb_src.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +cat > /etc/apt/sources.list <<'EOF' +deb-src http://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry.fail.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry.fail.sh new file mode 100644 index 000000000000..5cca2064bd3f --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +cat > /etc/apt/sources.list <<'EOF' +deb http://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry_with_options.fail.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry_with_options.fail.sh new file mode 100644 index 000000000000..81ddfcf29d37 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/http_entry_with_options.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Parse the URI after an optional deb options block. +cat > /etc/apt/sources.list <<'EOF' +deb [signed-by=/usr/share/keyrings/ubuntu-archive-keyring.gpg] http://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/apt/apt_sources_list_https/tests/https_entry.pass.sh b/linux_os/guide/services/apt/apt_sources_list_https/tests/https_entry.pass.sh new file mode 100644 index 000000000000..7c429ed4bf18 --- /dev/null +++ b/linux_os/guide/services/apt/apt_sources_list_https/tests/https_entry.pass.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +cat > /etc/apt/sources.list <<'EOF' +deb https://archive.ubuntu.com/ubuntu resolute main +EOF diff --git a/linux_os/guide/services/base/service_update-notifier-motd_disabled/bash/shared.sh b/linux_os/guide/services/base/service_update-notifier-motd_disabled/bash/shared.sh new file mode 100644 index 000000000000..dc51db2f506c --- /dev/null +++ b/linux_os/guide/services/base/service_update-notifier-motd_disabled/bash/shared.sh @@ -0,0 +1,16 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = disable +# complexity = low +# disruption = low + +SYSTEMCTL_EXEC=/usr/bin/systemctl +unit=update-notifier-motd.service +if "$SYSTEMCTL_EXEC" list-unit-files "$unit" --no-legend 2>/dev/null | grep -q "^${unit}[[:space:]]"; then + if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then + "$SYSTEMCTL_EXEC" stop "$unit" + fi + "$SYSTEMCTL_EXEC" disable "$unit" + "$SYSTEMCTL_EXEC" mask "$unit" + "$SYSTEMCTL_EXEC" reset-failed "$unit" || true +fi diff --git a/linux_os/guide/services/base/service_update-notifier-motd_disabled/oval/shared.xml b/linux_os/guide/services/base/service_update-notifier-motd_disabled/oval/shared.xml new file mode 100644 index 000000000000..a1673df39456 --- /dev/null +++ b/linux_os/guide/services/base/service_update-notifier-motd_disabled/oval/shared.xml @@ -0,0 +1,41 @@ + + + {{{ oval_metadata("The update-notifier MOTD service is neither enabled nor active.", rule_title=rule_title) }}} + + + + + + + + + + + + + + + + + + + + + update-notifier-motd.service + ActiveState + + + update-notifier-motd.service + UnitFileState + + + active + + + ^(?!enabled).*$ + + diff --git a/linux_os/guide/services/base/service_update-notifier-motd_disabled/rule.yml b/linux_os/guide/services/base/service_update-notifier-motd_disabled/rule.yml new file mode 100644 index 000000000000..5f07963aa979 --- /dev/null +++ b/linux_os/guide/services/base/service_update-notifier-motd_disabled/rule.yml @@ -0,0 +1,23 @@ +documentation_complete: true + +title: 'Disable and Mask update-notifier-motd Service' + +description: |- + The update-notifier-motd service generates dynamic message-of-the-day + content. {{{ describe_service_disable(service="update-notifier-motd") }}} + +rationale: |- + Disabling dynamic MOTD updates prevents unaudited network and system data + from being added to login banners. + +severity: medium + +platform: system_with_kernel + +warnings: + - general: |- + update-notifier-motd.service is a static, oneshot unit triggered by + update-notifier-motd.timer. A static unit cannot be enabled, and a oneshot + unit is inactive except while it runs, so this check reports the same result + whether or not the unit has been masked. The timer rule is what detects the + MOTD updates actually being in use. diff --git a/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/service_masked.pass.sh b/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/service_masked.pass.sh new file mode 100644 index 000000000000..d32709976e2d --- /dev/null +++ b/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/service_masked.pass.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# packages = update-notifier-common + +SYSTEMCTL_EXEC='/usr/bin/systemctl' +if "$SYSTEMCTL_EXEC" -q list-unit-files 'update-notifier-motd.service'; then + "$SYSTEMCTL_EXEC" stop 'update-notifier-motd.service' + "$SYSTEMCTL_EXEC" mask 'update-notifier-motd.service' +fi +"$SYSTEMCTL_EXEC" reset-failed 'update-notifier-motd.service' || true diff --git a/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/unit_absent.pass.sh b/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/unit_absent.pass.sh new file mode 100644 index 000000000000..eabe1e59ffa1 --- /dev/null +++ b/linux_os/guide/services/base/service_update-notifier-motd_disabled/tests/unit_absent.pass.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +{{{ bash_package_remove("update-notifier-common") }}} diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/bash/shared.sh b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/bash/shared.sh new file mode 100644 index 000000000000..bcf632d4b7e1 --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/bash/shared.sh @@ -0,0 +1,16 @@ +# platform = multi_platform_ubuntu +# reboot = false +# strategy = disable +# complexity = low +# disruption = low + +SYSTEMCTL_EXEC=/usr/bin/systemctl +unit=update-notifier-motd.timer +if "$SYSTEMCTL_EXEC" list-unit-files "$unit" --no-legend 2>/dev/null | grep -q "^${unit}[[:space:]]"; then + if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then + "$SYSTEMCTL_EXEC" stop "$unit" + fi + "$SYSTEMCTL_EXEC" disable "$unit" + "$SYSTEMCTL_EXEC" mask "$unit" + "$SYSTEMCTL_EXEC" reset-failed "$unit" || true +fi diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/oval/shared.xml b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/oval/shared.xml new file mode 100644 index 000000000000..b339dc41e97c --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/oval/shared.xml @@ -0,0 +1,44 @@ + + + {{{ oval_metadata("The update-notifier MOTD timer is disabled and masked.", rule_title=rule_title) }}} + + + + + + + + + + + + + + + + + + + + + + + update-notifier-motd.timer + ActiveState + + + update-notifier-motd.timer + UnitFileState + + + + active + + + ^(?!enabled).*$ + + diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/rule.yml b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/rule.yml new file mode 100644 index 000000000000..460ff391533d --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/rule.yml @@ -0,0 +1,23 @@ +documentation_complete: true + +title: 'Disable and Mask update-notifier-motd Timer' + +description: |- + The update-notifier-motd.timer unit must be inactive and not enabled when + the update-notifier-common package is installed. Masking is the preferred + remediation because it also prevents transient activation. + +rationale: |- + Disabling the timer prevents periodic regeneration of dynamic login + message content. + +severity: medium + +platform: system_with_kernel + +ocil_clause: 'update-notifier-motd.timer is active or enabled' + +ocil: |- + Run systemctl is-active update-notifier-motd.timer and + systemctl is-enabled update-notifier-motd.timer. The timer must be + inactive and not enabled, or the update-notifier-common package must be absent. diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_disabled.pass.sh b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_disabled.pass.sh new file mode 100644 index 000000000000..c24cddcbed9f --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_disabled.pass.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = update-notifier-common + +systemctl stop update-notifier-motd.timer 2>/dev/null || true +systemctl disable update-notifier-motd.timer 2>/dev/null || true +systemctl unmask update-notifier-motd.timer 2>/dev/null || true diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_enabled.fail.sh b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_enabled.fail.sh new file mode 100644 index 000000000000..33792a99cfc2 --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_enabled.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# packages = update-notifier-common + +SYSTEMCTL_EXEC='/usr/bin/systemctl' +if "$SYSTEMCTL_EXEC" -q list-unit-files 'update-notifier-motd.timer'; then + "$SYSTEMCTL_EXEC" unmask 'update-notifier-motd.timer' + "$SYSTEMCTL_EXEC" enable 'update-notifier-motd.timer' + "$SYSTEMCTL_EXEC" start 'update-notifier-motd.timer' +fi diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_masked.pass.sh b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_masked.pass.sh new file mode 100644 index 000000000000..9abf51f2503a --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/timer_masked.pass.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# packages = update-notifier-common + +SYSTEMCTL_EXEC='/usr/bin/systemctl' +if "$SYSTEMCTL_EXEC" -q list-unit-files 'update-notifier-motd.timer'; then + "$SYSTEMCTL_EXEC" stop 'update-notifier-motd.timer' + "$SYSTEMCTL_EXEC" disable 'update-notifier-motd.timer' + "$SYSTEMCTL_EXEC" mask 'update-notifier-motd.timer' +fi +"$SYSTEMCTL_EXEC" reset-failed 'update-notifier-motd.timer' || true diff --git a/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/unit_absent.pass.sh b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/unit_absent.pass.sh new file mode 100644 index 000000000000..eabe1e59ffa1 --- /dev/null +++ b/linux_os/guide/services/base/timer_update-notifier-motd_disabled/tests/unit_absent.pass.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +{{{ bash_package_remove("update-notifier-common") }}} diff --git a/linux_os/guide/services/ntp/chronyd_specify_remote_server/bash/shared.sh b/linux_os/guide/services/ntp/chronyd_specify_remote_server/bash/shared.sh index 5362980d00be..ed11af52a90f 100644 --- a/linux_os/guide/services/ntp/chronyd_specify_remote_server/bash/shared.sh +++ b/linux_os/guide/services/ntp/chronyd_specify_remote_server/bash/shared.sh @@ -4,6 +4,28 @@ config_file="{{{ chrony_conf_path }}}" -if ! grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$config_file" ; then +has_remote_source=false +if grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$config_file" 2>/dev/null; then + has_remote_source=true +fi + +while [[ "$has_remote_source" == false ]] && read -r directive location _; do + [[ "$directive" == sourcedir || "$directive" == confdir ]] || continue + extension='*.conf' + [[ "$directive" == sourcedir ]] && extension='*.sources' + while IFS= read -r -d '' include_file; do + if grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$include_file"; then + has_remote_source=true + break + fi + done < <(find -L "$location" -maxdepth 1 -type f -name "$extension" -print0 2>/dev/null) +done < "$config_file" + +if [[ "$has_remote_source" == false ]]; then + sourcedir=$(awk '$1 == "sourcedir" { print $2; exit }' "$config_file") + if [[ -n "$sourcedir" ]]; then + mkdir -p "$sourcedir" + config_file="$sourcedir/60-cis.sources" + fi {{{ bash_ensure_there_are_servers_in_ntp_compatible_config_file("$config_file", "$var_multiple_time_servers") | indent(2) }}} fi diff --git a/linux_os/guide/services/ntp/chronyd_specify_remote_server/oval/ubuntu2604.xml b/linux_os/guide/services/ntp/chronyd_specify_remote_server/oval/ubuntu2604.xml new file mode 100644 index 000000000000..209a5e4369a8 --- /dev/null +++ b/linux_os/guide/services/ntp/chronyd_specify_remote_server/oval/ubuntu2604.xml @@ -0,0 +1,99 @@ + + + {{{ oval_metadata("A remote NTP Server for time synchronization should be + specified (and dependencies are met)", rule_title=rule_title) }}} + + + + + + + + + + + + + {{{ chrony_conf_path }}} + ^[\s]*(?:server|pool)[\s]+.+$ + 1 + + + + {{{ chrony_conf_path }}} + ^[\s]*sourcedir[\s]+(\S+)[\s]*(?:#.*)?$ + 1 + + + + + + /*.sources + + + + + + + + + + + + + + + + ^[\s]*(?:server|pool)[\s]+.+$ + 1 + + + + {{{ chrony_conf_path }}} + ^[\s]*confdir[\s]+(\S+)[\s]*(?:#.*)?$ + 1 + + + + + + /*.conf + + + + + + + + + + + + + + + + ^[\s]*(?:server|pool)[\s]+.+$ + 1 + + diff --git a/linux_os/guide/services/ntp/chronyd_specify_remote_server/tests/ubuntu2604_default_sources.pass.sh b/linux_os/guide/services/ntp/chronyd_specify_remote_server/tests/ubuntu2604_default_sources.pass.sh new file mode 100644 index 000000000000..6f3b68533e1c --- /dev/null +++ b/linux_os/guide/services/ntp/chronyd_specify_remote_server/tests/ubuntu2604_default_sources.pass.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# packages = chrony +# platform = Ubuntu 26.04 + +SOURCES_DIR="/etc/chrony/sources.d" + +rm -rf /etc/chrony/conf.d +rm -rf "$SOURCES_DIR" + +cat > {{{ chrony_conf_path }}} << EOF +sourcedir $SOURCES_DIR +EOF + +mkdir -p "$SOURCES_DIR" +cat > "$SOURCES_DIR/ubuntu-ntp-pools.sources" << EOF +pool 1.ntp.ubuntu.com iburst maxsources 1 nts prefer +pool 2.ntp.ubuntu.com iburst maxsources 1 nts prefer +EOF diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/bash/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/bash/shared.sh new file mode 100644 index 000000000000..681aa6decf04 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/bash/shared.sh @@ -0,0 +1,14 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +{{{ + bash_sshd_remediation( + parameter="PermitEmptyPasswords", + value="no", + config_is_distributed=sshd_distributed_config, + rule_id=rule_id + ) +}}} diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/rule.yml b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/rule.yml new file mode 100644 index 000000000000..cecda821bbd8 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/rule.yml @@ -0,0 +1,21 @@ +documentation_complete: true + +title: 'Disable SSH Access Through Empty Passwords, Including the Secure Default' + +description: |- + The effective SSH daemon configuration must set + PermitEmptyPasswords to no. An omitted directive is + compliant because the OpenSSH default is no. + +rationale: |- + Accounts with empty passwords must not be usable for remote SSH access. + +severity: high + +platform: package[openssh-server] + +ocil_clause: 'the effective PermitEmptyPasswords value is not no' + +ocil: |- + Run sshd -T | grep permitemptypasswords and verify the result is + permitemptypasswords no. diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/sce/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/sce/shared.sh new file mode 100644 index 000000000000..f4007cd24ab3 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/sce/shared.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +if /usr/sbin/sshd -T 2>/dev/null | grep -Piq '^permitemptypasswords\s+no$'; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'The effective PermitEmptyPasswords value is not no.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_no.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_no.pass.sh new file mode 100644 index 000000000000..c4e1490999f4 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_no.pass.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'PermitEmptyPasswords no' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_yes.fail.sh b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_yes.fail.sh new file mode 100644 index 000000000000..d76e0a14fa8a --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/explicit_yes.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'PermitEmptyPasswords yes' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/missing_uses_default.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/missing_uses_default.pass.sh new file mode 100644 index 000000000000..2f13951e9a9c --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_disable_empty_passwords_default/tests/missing_uses_default.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +sed -ri '/^[[:space:]]*PermitEmptyPasswords[[:space:]]+/Id' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_limit_user_access/rule.yml b/linux_os/guide/services/ssh/ssh_server/sshd_limit_user_access/rule.yml index 45268c1823fc..20a00d1fc2e0 100644 --- a/linux_os/guide/services/ssh/ssh_server/sshd_limit_user_access/rule.yml +++ b/linux_os/guide/services/ssh/ssh_server/sshd_limit_user_access/rule.yml @@ -34,6 +34,10 @@ rationale: |- severity: unknown +{{% if product == "ubuntu2604" %}} +platform: package[openssh-server] +{{% endif %}} + ocil_clause: 'sshd does not limit the users who can log in' ocil: |- diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_idle_timeout/rule.yml b/linux_os/guide/services/ssh/ssh_server/sshd_set_idle_timeout/rule.yml index b3129efba1b9..a027e92cd4a8 100644 --- a/linux_os/guide/services/ssh/ssh_server/sshd_set_idle_timeout/rule.yml +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_idle_timeout/rule.yml @@ -55,7 +55,11 @@ references: stigid@sle12: SLES-12-030190 requires: +{{% if product == "ubuntu2604" %}} + - sshd_set_keepalive_positive +{{% else %}} - sshd_set_keepalive +{{% endif %}} ocil_clause: 'it is commented out or not configured properly' diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/bash/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/bash/shared.sh new file mode 100644 index 000000000000..2a67ad4824c4 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/bash/shared.sh @@ -0,0 +1,15 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = configure +# complexity = low +# disruption = low + +{{{ bash_instantiate_variables("var_sshd_set_keepalive") }}} +{{{ + bash_sshd_remediation( + parameter="ClientAliveCountMax", + value="$var_sshd_set_keepalive", + config_is_distributed=sshd_distributed_config, + rule_id=rule_id + ) +}}} diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/rule.yml b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/rule.yml new file mode 100644 index 000000000000..583a65c1092b --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/rule.yml @@ -0,0 +1,24 @@ +documentation_complete: true + +title: 'Set SSH Client Alive Count Max Greater Than Zero' + +description: |- + The effective ClientAliveCountMax value must be greater than zero. + An omitted directive is compliant because the OpenSSH default is 3. + +rationale: |- + A positive count allows the SSH daemon to terminate an unresponsive + connection after the configured client-alive interval. + +severity: medium + +platform: package[openssh-server] + +requires: + - sshd_set_idle_timeout + +ocil_clause: 'the effective ClientAliveCountMax value is zero' + +ocil: |- + Run sshd -T | grep clientalivecountmax and verify the value is + greater than zero. diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/sce/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/sce/shared.sh new file mode 100644 index 000000000000..b703fde3e628 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/sce/shared.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +value=$(/usr/sbin/sshd -T 2>/dev/null | awk '$1 == "clientalivecountmax" { print $2; exit }') +if [[ "$value" =~ ^[0-9]+$ ]] && (( value > 0 )); then + exit "$XCCDF_RESULT_PASS" +fi + +echo "The effective ClientAliveCountMax value is '${value:-unavailable}', not greater than zero." +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/missing_default.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/missing_default.pass.sh new file mode 100644 index 000000000000..a2ed2770f5cc --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/missing_default.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +sed -ri '/^[[:space:]]*ClientAliveCountMax[[:space:]]+/Id' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/positive.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/positive.pass.sh new file mode 100644 index 000000000000..1b24772ef50c --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/positive.pass.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'ClientAliveCountMax 1' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/zero.fail.sh b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/zero.fail.sh new file mode 100644 index 000000000000..c8e743e11d12 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_set_keepalive_positive/tests/zero.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'ClientAliveCountMax 0' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/bash/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/bash/shared.sh new file mode 100644 index 000000000000..5d7d51dc95cf --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/bash/shared.sh @@ -0,0 +1,15 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +weak_kex='-diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1' +{{{ + bash_sshd_remediation( + parameter="KexAlgorithms", + value="$weak_kex", + config_is_distributed=sshd_distributed_config, + rule_id=rule_id + ) +}}} diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/rule.yml b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/rule.yml new file mode 100644 index 000000000000..f5da5f7e8db0 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/rule.yml @@ -0,0 +1,23 @@ +documentation_complete: true + +title: 'Disable SSH Key Exchange Algorithms Prohibited by CIS Ubuntu 26.04' + +description: |- + The effective SSH key-exchange list must not contain + diffie-hellman-group1-sha1, + diffie-hellman-group14-sha1, or + diffie-hellman-group-exchange-sha1. + +rationale: |- + SHA-1 key-exchange algorithms do not provide adequate protection against + modern cryptographic attacks. + +severity: medium + +platform: package[openssh-server] + +ocil_clause: 'the effective SSH key-exchange list contains a prohibited algorithm' + +ocil: |- + Run sshd -T | grep kexalgorithms. The effective list must not + contain any of the three prohibited SHA-1 algorithms. diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/sce/shared.sh b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/sce/shared.sh new file mode 100644 index 000000000000..2170bd75c611 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/sce/shared.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +kex=$(/usr/sbin/sshd -T 2>/dev/null | awk '$1 == "kexalgorithms" { print $2; exit }') +if [[ -n "$kex" ]] && ! grep -Piq '(^|,)(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group-exchange-sha1)(,|$)' <<< "$kex"; then + exit "$XCCDF_RESULT_PASS" +fi + +echo 'The effective KexAlgorithms list contains a CIS-prohibited SHA-1 algorithm or could not be read.' +exit "$XCCDF_RESULT_FAIL" diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/default_algorithms.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/default_algorithms.pass.sh new file mode 100644 index 000000000000..fe71a2b79b86 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/default_algorithms.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +sed -ri '/^[[:space:]]*KexAlgorithms[[:space:]]+/Id' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/subtractive_denylist.pass.sh b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/subtractive_denylist.pass.sh new file mode 100644 index 000000000000..c6242b577b14 --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/subtractive_denylist.pass.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'KexAlgorithms -diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/weak_algorithm.fail.sh b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/weak_algorithm.fail.sh new file mode 100644 index 000000000000..861317d5e5fc --- /dev/null +++ b/linux_os/guide/services/ssh/ssh_server/sshd_use_strong_kex_ubuntu2604/tests/weak_algorithm.fail.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = openssh-server + +mkdir -p /etc/ssh/sshd_config.d +echo 'KexAlgorithms +diffie-hellman-group14-sha1' > /etc/ssh/sshd_config.d/00-cis-test.conf diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/bash/shared.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/bash/shared.sh new file mode 100644 index 000000000000..297ea25fbb4d --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/bash/shared.sh @@ -0,0 +1,13 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +while IFS= read -r motd_path; do + motd_path=${motd_path#\"}; motd_path=${motd_path%\"} + motd_path=${motd_path#\'}; motd_path=${motd_path%\'} + [[ -e "$motd_path" ]] || continue + chown root:root "$motd_path" + chmod u-x,go-wx "$motd_path" +done < <(grep -hPoi '^\h*session\h+(required|optional)\h+pam_motd\.so\b.*\bmotd=\K("[^"]+"|'"'"'[^'"'"']+'"'"'|\S+)' /etc/pam.d/* 2>/dev/null | sort -u) diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/rule.yml b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/rule.yml new file mode 100644 index 000000000000..03f8bdd5bc83 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/rule.yml @@ -0,0 +1,24 @@ +documentation_complete: true + +title: 'Configure Access to pam_motd Message Files' + +description: |- + Files selected by active pam_motd.so motd=PATH arguments must be + owned by root, group-owned by root, and mode 0644 or more restrictive. + +rationale: |- + Restrictive ownership and permissions prevent unauthorized modification of + text displayed to users at login. + +severity: medium + +platform: package[pam] + +requires: + - pam_motd_configured + +ocil_clause: 'a referenced pam_motd file is not root-owned or is more permissive than 0644' + +ocil: |- + Identify paths in active pam_motd.so motd=PATH PAM entries and + verify each path is owned by root:root with mode 0644 or more restrictive. diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/sce/shared.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/sce/shared.sh new file mode 100644 index 000000000000..bba49203467e --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/sce/shared.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +result=$XCCDF_RESULT_PASS +found_path=false + +while IFS= read -r motd_path; do + motd_path=${motd_path#\"}; motd_path=${motd_path%\"} + motd_path=${motd_path#\'}; motd_path=${motd_path%\'} + found_path=true + + if [[ ! -e "$motd_path" ]]; then + echo "$motd_path does not exist." + result=$XCCDF_RESULT_FAIL + continue + fi + + read -r mode uid gid < <(stat -Lc '%a %u %g' "$motd_path") + if (( 8#$mode & 0133 )) || [[ "$uid" != 0 || "$gid" != 0 ]]; then + echo "$motd_path has mode $mode and owner $uid:$gid; expected root:root and 0644 or more restrictive." + result=$XCCDF_RESULT_FAIL + fi +done < <(grep -hPoi '^\h*session\h+(required|optional)\h+pam_motd\.so\b.*\bmotd=\K("[^"]+"|'"'"'[^'"'"']+'"'"'|\S+)' /etc/pam.d/* 2>/dev/null | sort -u) + +if [[ "$found_path" != true ]]; then + echo 'No explicit pam_motd message path was found in /etc/pam.d.' + result=$XCCDF_RESULT_FAIL +fi + +exit "$result" diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/correct_access.pass.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/correct_access.pass.sh new file mode 100644 index 000000000000..12e4df3369fb --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/correct_access.pass.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Remove pam_motd entries from every PAM file checked by this rule. +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +printf 'Authorized use only.\n' > /etc/motd.cac +chown root:root /etc/motd.cac +chmod 0644 /etc/motd.cac +echo 'session optional pam_motd.so motd=/etc/motd.cac' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/other_service_world_writable.fail.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/other_service_world_writable.fail.sh new file mode 100644 index 000000000000..94a84e19f300 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/other_service_world_writable.fail.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +for f in /etc/pam.d/*; do + [[ -f "$f" ]] && sed -ri '/pam_motd\.so/d' "$f" +done +printf 'Authorized use only.\n' > /etc/motd.cac +chown root:root /etc/motd.cac +chmod 0666 /etc/motd.cac +echo 'session optional pam_motd.so motd=/etc/motd.cac' > /etc/pam.d/cac-other-service diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/quoted_path.pass.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/quoted_path.pass.sh new file mode 100644 index 000000000000..879978fb98fe --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/quoted_path.pass.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +for f in /etc/pam.d/*; do + [[ -f "$f" ]] && sed -ri '/pam_motd\.so/d' "$f" +done +printf 'Authorized use only.\n' > /etc/motd.cac +chown root:root /etc/motd.cac +chmod 0644 /etc/motd.cac +echo 'session optional pam_motd.so motd="/etc/motd.cac"' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/world_writable.fail.sh b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/world_writable.fail.sh new file mode 100644 index 000000000000..f07baede1f68 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/file_permissions_pam_motd/tests/world_writable.fail.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Remove pam_motd entries from every PAM file checked by this rule. +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +printf 'Authorized use only.\n' > /etc/motd.cac +chown root:root /etc/motd.cac +chmod 0666 /etc/motd.cac +echo 'session optional pam_motd.so motd=/etc/motd.cac' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/bash/shared.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/bash/shared.sh new file mode 100644 index 000000000000..862f8e34ba58 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/bash/shared.sh @@ -0,0 +1,42 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = configure +# complexity = medium +# disruption = medium + +os_id=$(awk -F= '$1 == "ID" { gsub(/^"|"$/, "", $2); print $2; exit }' /etc/os-release 2>/dev/null) +touch /etc/motd +chown root:root /etc/motd +chmod u-x,go-wx /etc/motd + +append_canonical_entry() { + local pam_file=$1 + if [[ -s "$pam_file" && -n $(tail -c 1 "$pam_file") ]]; then + printf '\n' >> "$pam_file" + fi + printf '%s\n' 'session optional pam_motd.so motd=/etc/motd' >> "$pam_file" +} + +configured=false +for pam_file in /etc/pam.d/*; do + [[ -f "$pam_file" ]] || continue + if grep -Piq '^\h*session\h+(required|optional)\h+pam_motd\.so\b' "$pam_file"; then + sed -ri '/^[[:space:]]*session[[:space:]]+(required|optional)[[:space:]]+pam_motd\.so\b/Id' "$pam_file" + append_canonical_entry "$pam_file" + configured=true + fi +done + +if [[ "$configured" != true ]]; then + for service in sshd login su gdm-password; do + pam_file="/etc/pam.d/$service" + [[ -f "$pam_file" ]] || continue + append_canonical_entry "$pam_file" + break + done +fi + +sed -ri 's/\\[vrms]//g; s/(Ubuntu|Debian|GNU\/Linux)//Ig' /etc/motd +if [[ -n "$os_id" ]]; then + sed -ri "s/\\b${os_id}\\b//Ig" /etc/motd +fi diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/rule.yml b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/rule.yml new file mode 100644 index 000000000000..8c09c561e8c6 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/rule.yml @@ -0,0 +1,25 @@ +documentation_complete: true + +title: 'Configure Explicit pam_motd Message Files' + +description: |- + PAM login services using pam_motd.so must specify message files + with an explicit motd= argument. Referenced message files must not + disclose the operating-system release or contain system-information escape + sequences. + +rationale: |- + Explicit message paths make login banner content auditable and prevent + unintended system information disclosure. + +severity: medium + +platform: package[pam] + +ocil_clause: 'pam_motd has no explicit message file or a referenced file discloses system information' + +ocil: |- + Review active pam_motd.so entries in the sshd, login, su, and + gdm-password PAM services. At least one entry must use motd=PATH, + and referenced files must not contain \v, \r, + \m, \s, or distribution-identifying text. diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/sce/shared.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/sce/shared.sh new file mode 100644 index 000000000000..60387e58ebd4 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/sce/shared.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +result=$XCCDF_RESULT_PASS +found_entry=false +os_id=$(awk -F= '$1 == "ID" { gsub(/^"|"$/, "", $2); print $2; exit }' /etc/os-release 2>/dev/null) + +for service in sshd login su gdm-password; do + if [[ -f "/etc/pam.d/$service" ]]; then + pam_file="/etc/pam.d/$service" + elif [[ -f "/usr/lib/pam.d/$service" ]]; then + pam_file="/usr/lib/pam.d/$service" + else + continue + fi + + while IFS= read -r line; do + found_entry=true + motd_path=$(grep -oP '\bmotd=\K("[^"]+"|'"'"'[^'"'"']+'"'"'|\S+)' <<< "$line" | head -n 1) + motd_path=${motd_path#\"}; motd_path=${motd_path%\"} + motd_path=${motd_path#\'}; motd_path=${motd_path%\'} + + if [[ -z "$motd_path" || ! -r "$motd_path" ]]; then + echo "$pam_file: pam_motd does not reference a readable explicit motd path." + result=$XCCDF_RESULT_FAIL + continue + fi + + unsafe='(\\[vrms]|Ubuntu|Debian|GNU/Linux)' + [[ -n "$os_id" ]] && unsafe="(\\\\[vrms]|Ubuntu|Debian|GNU/Linux|${os_id})" + if grep -Piq "$unsafe" "$motd_path"; then + echo "$motd_path contains operating-system information or a prohibited escape sequence." + result=$XCCDF_RESULT_FAIL + fi + done < <(grep -Pi '^\h*session\h+(required|optional)\h+pam_motd\.so\b' "$pam_file" 2>/dev/null) +done + +if [[ "$found_entry" != true ]]; then + echo 'No active pam_motd entry with an explicit message file was found.' + result=$XCCDF_RESULT_FAIL +fi + +exit "$result" diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/explicit_safe_motd.pass.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/explicit_safe_motd.pass.sh new file mode 100644 index 000000000000..7773b297781b --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/explicit_safe_motd.pass.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Remove pam_motd entries from every PAM file checked by this rule. +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +printf 'Authorized use only. All activity may be monitored.\n' > /etc/motd.cac +chmod 0644 /etc/motd.cac +echo 'session optional pam_motd.so motd=/etc/motd.cac' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/missing_referenced_file.fail.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/missing_referenced_file.fail.sh new file mode 100644 index 000000000000..f4652178c53c --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/missing_referenced_file.fail.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +rm -f /run/motd.dynamic +printf '%s\n' \ + 'session optional pam_motd.so motd=/run/motd.dynamic' \ + 'session optional pam_motd.so noupdate' \ + >> /etc/pam.d/login diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/motd_discloses_os.fail.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/motd_discloses_os.fail.sh new file mode 100644 index 000000000000..9de132418c64 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/motd_discloses_os.fail.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Remove pam_motd entries from every PAM file checked by this rule. +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +printf 'Welcome to Ubuntu\n' > /etc/motd.cac +chmod 0644 /etc/motd.cac +echo 'session optional pam_motd.so motd=/etc/motd.cac' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/no_explicit_path.fail.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/no_explicit_path.fail.sh new file mode 100644 index 000000000000..fce7e768e0d8 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/no_explicit_path.fail.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# platform = multi_platform_ubuntu + +# Remove pam_motd entries from every PAM file checked by this rule. +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [ -f "$f" ] && sed -ri '/pam_motd\.so/d' "$f" +done + +# Configure pam_motd without the required explicit motd= path. +echo 'session optional pam_motd.so noupdate' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/quoted_path.pass.sh b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/quoted_path.pass.sh new file mode 100644 index 000000000000..384c335c2b69 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-banners/pam_motd_configured/tests/quoted_path.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +for f in /etc/pam.d/sshd /etc/pam.d/login /etc/pam.d/su /etc/pam.d/gdm-password; do + [[ -f "$f" ]] && sed -ri '/pam_motd\.so/d' "$f" +done +printf 'Authorized use only.\n' > /etc/motd.cac +echo 'session optional pam_motd.so motd="/etc/motd.cac"' >> /etc/pam.d/sshd diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/ansible/shared.yml b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/ansible/shared.yml new file mode 100644 index 000000000000..ab10c451d018 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/ansible/shared.yml @@ -0,0 +1,11 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = enable +# complexity = low +# disruption = low + +- name: Install the latest cracklib-runtime package + ansible.builtin.apt: + name: cracklib-runtime + state: latest + update_cache: false diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/bash/shared.sh b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/bash/shared.sh new file mode 100644 index 000000000000..95b845313fc4 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/bash/shared.sh @@ -0,0 +1,7 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = enable +# complexity = low +# disruption = low + +DEBIAN_FRONTEND=noninteractive apt-get install -y cracklib-runtime diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/rule.yml b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/rule.yml new file mode 100644 index 000000000000..5be2e4925dfe --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/rule.yml @@ -0,0 +1,18 @@ +documentation_complete: true + +title: 'Install cracklib-runtime Package' + +description: |- + {{{ describe_package_install(package="cracklib-runtime") }}} + +rationale: |- + The cracklib-runtime package supplies the password dictionary tools used by + PAM password-quality controls. + +severity: medium + +ocil_clause: 'cracklib-runtime is not installed or an upgrade is available' + +ocil: |- + Verify dpkg-query -s cracklib-runtime succeeds and + apt list --upgradable does not list cracklib-runtime. diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/sce/shared.sh b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/sce/shared.sh new file mode 100644 index 000000000000..d85036657e56 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/sce/shared.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +if ! dpkg-query -W -f='${db:Status-Status}\n' cracklib-runtime 2>/dev/null | grep -qx installed; then + echo 'cracklib-runtime is not installed.' + exit "$XCCDF_RESULT_FAIL" +fi + +if apt list --upgradable 2>/dev/null | grep -Pq '^cracklib-runtime/'; then + echo 'An upgrade is available for cracklib-runtime.' + exit "$XCCDF_RESULT_FAIL" +fi + +exit "$XCCDF_RESULT_PASS" diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/installed_latest.pass.sh b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/installed_latest.pass.sh new file mode 100644 index 000000000000..646d68d52935 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/installed_latest.pass.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# packages = cracklib-runtime + +exit 0 diff --git a/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/missing.fail.sh b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/missing.fail.sh new file mode 100644 index 000000000000..690d1c411fd6 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-pam/package_cracklib-runtime_installed/tests/missing.fail.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +{{{ bash_package_remove("cracklib-runtime") }}} diff --git a/linux_os/guide/system/accounts/accounts-pam/package_pam_pwquality_installed/rule.yml b/linux_os/guide/system/accounts/accounts-pam/package_pam_pwquality_installed/rule.yml index c9d08e4bb907..3b5609459328 100644 --- a/linux_os/guide/system/accounts/accounts-pam/package_pam_pwquality_installed/rule.yml +++ b/linux_os/guide/system/accounts/accounts-pam/package_pam_pwquality_installed/rule.yml @@ -40,6 +40,7 @@ template: pkgname: libpwquality pkgname@ubuntu2204: libpam-pwquality pkgname@ubuntu2404: libpam-pwquality + pkgname@ubuntu2604: libpam-pwquality pkgname@debian12: libpam-pwquality pkgname@debian13: libpam-pwquality diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/bash/shared.sh new file mode 100644 index 000000000000..4990c46f3847 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/bash/shared.sh @@ -0,0 +1,16 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = medium +# disruption = medium + +while IFS=: read -r user _ _ gid _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + chgrp "$gid" "$dot_dir" + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/rule.yml b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/rule.yml new file mode 100644 index 000000000000..58d6cfbea90a --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/rule.yml @@ -0,0 +1,22 @@ +documentation_complete: true + +title: 'Configure Local Interactive User Dot Directory Group Ownership' + +description: |- + Dot directories below each local interactive user's home directory must be + group-owned by that user's primary group. + +rationale: |- + Unexpected group ownership can expose private user configuration and + authentication data. + +severity: medium + +platform: system_with_kernel + +ocil_clause: 'a dot directory has an unexpected group owner' + +ocil: |- + For each local interactive user, recursively inspect dot directories on the + local filesystem below that user's home directory. Every directory must be + group-owned by that user's primary group. diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/sce/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/sce/shared.sh new file mode 100644 index 000000000000..0480e42386b9 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/sce/shared.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +result=$XCCDF_RESULT_PASS +while IFS=: read -r user _ _ gid _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + owner_gid=$(stat -Lc '%g' "$dot_dir") + if [[ "$owner_gid" != "$gid" ]]; then + echo "$dot_dir is group-owned by GID $owner_gid; expected $gid for $user." + result=$XCCDF_RESULT_FAIL + fi + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd +exit "$result" diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/expected_owner.pass.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/expected_owner.pass.sh new file mode 100644 index 000000000000..f90b8303d084 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/expected_owner.pass.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config +chgrp -R "$(id -g $USER)" /home/$USER/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/swapped_group_owners.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/swapped_group_owners.fail.sh new file mode 100644 index 000000000000..bb7d00b8fdaf --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/swapped_group_owners.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER1="cac_user1" +USER2="cac_user2" +useradd -m $USER1 +useradd -m $USER2 +mkdir -p /home/$USER1/.config +chgrp -R "$(id -g $USER2)" /home/$USER1/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/unexpected_owner_system_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/unexpected_owner_system_id.fail.sh new file mode 100644 index 000000000000..b5e490a9043b --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_groupownership/tests/unexpected_owner_system_id.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config +chgrp -R 2 /home/$USER/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/bash/shared.sh new file mode 100644 index 000000000000..3cc01d15f8c5 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/bash/shared.sh @@ -0,0 +1,16 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = medium +# disruption = medium + +while IFS=: read -r user _ _ _ _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + chown "$user" "$dot_dir" + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/rule.yml b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/rule.yml new file mode 100644 index 000000000000..6065b352c25f --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/rule.yml @@ -0,0 +1,22 @@ +documentation_complete: true + +title: 'Configure Local Interactive User Dot Directory Ownership' + +description: |- + Dot directories below each local interactive user's home directory must be + owned by that user. + +rationale: |- + Unexpected ownership can allow one account to control another account's + configuration or authentication data. + +severity: medium + +platform: system_with_kernel + +ocil_clause: 'a dot directory has an unexpected owner' + +ocil: |- + For each local interactive user, recursively inspect dot directories on the + local filesystem below that user's home directory. Every directory must be + owned by that user. diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/sce/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/sce/shared.sh new file mode 100644 index 000000000000..9c0ec988f4de --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/sce/shared.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +result=$XCCDF_RESULT_PASS +while IFS=: read -r user _ uid _ _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + owner_uid=$(stat -Lc '%u' "$dot_dir") + if [[ "$owner_uid" != "$uid" ]]; then + echo "$dot_dir is owned by UID $owner_uid; expected $uid ($user)." + result=$XCCDF_RESULT_FAIL + fi + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd +exit "$result" diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/expected_owner.pass.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/expected_owner.pass.sh new file mode 100644 index 000000000000..7c6374400b6d --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/expected_owner.pass.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config +chown -R $USER /home/$USER/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/swapped_owners.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/swapped_owners.fail.sh new file mode 100644 index 000000000000..faa6bc1a2496 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/swapped_owners.fail.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER1="cac_user1" +USER2="cac_user2" +useradd -m $USER1 +useradd -m $USER2 +mkdir -p /home/$USER1/.config +chown -R $USER2 /home/$USER1/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/unexpected_owner_system_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/unexpected_owner_system_id.fail.sh new file mode 100644 index 000000000000..0726e0c52221 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_ownership/tests/unexpected_owner_system_id.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config +chown -R 2 /home/$USER/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/bash/shared.sh new file mode 100644 index 000000000000..b2c625b45be5 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/bash/shared.sh @@ -0,0 +1,20 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = medium +# disruption = medium + +while IFS=: read -r _ _ _ _ _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + if [[ ${dot_dir##*/} == .ssh ]]; then + chmod go-rwx "$dot_dir" + else + chmod g-w,o-rwx "$dot_dir" + fi + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/rule.yml b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/rule.yml new file mode 100644 index 000000000000..47c406d0a657 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/rule.yml @@ -0,0 +1,23 @@ +documentation_complete: true + +title: 'Configure Local Interactive User Dot Directory Permissions' + +description: |- + Dot directories below local interactive user home directories must be mode + 0750 or more restrictive. The .ssh directory must be mode 0700 or + more restrictive. + +rationale: |- + Restrictive dot-directory permissions protect private user configuration + and authentication material. + +severity: medium + +platform: system_with_kernel + +ocil_clause: 'a dot directory is too permissive' + +ocil: |- + Find dot directories below every local interactive user home. No directory + may grant group write or any access to other users, and .ssh may not + grant any group access. diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/sce/shared.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/sce/shared.sh new file mode 100644 index 000000000000..f5594999f9bd --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/sce/shared.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +result=$XCCDF_RESULT_PASS +while IFS=: read -r _ _ _ _ _ home shell; do + grep -qxF "$shell" /etc/shells 2>/dev/null || continue + [[ "$shell" == */nologin || "$shell" == */false || ! -d "$home" ]] && continue + case $(findmnt -no FSTYPE --target "$home" 2>/dev/null) in + nfs|nfs4|cifs|smbfs|smb3|fuse.sshfs|afs|ncpfs|glusterfs|ceph) continue ;; + esac + while IFS= read -r -d '' dot_dir; do + mode=$(stat -Lc '%a' "$dot_dir") + mask=0027 + [[ ${dot_dir##*/} == .ssh ]] && mask=0077 + if (( 8#$mode & mask )); then + echo "$dot_dir has mode $mode, which is too permissive." + result=$XCCDF_RESULT_FAIL + fi + done < <(find "$home" -xdev -mindepth 1 -maxdepth 1 -type d -name '.*' -print0 2>/dev/null) +done < /etc/passwd +exit "$result" diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/group_writable_dot_dir.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/group_writable_dot_dir.fail.sh new file mode 100644 index 000000000000..70792354f31c --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/group_writable_dot_dir.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config +chmod 0770 /home/$USER/.config diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/restrictive_modes.pass.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/restrictive_modes.pass.sh new file mode 100644 index 000000000000..b35792900df2 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/restrictive_modes.pass.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.config /home/$USER/.ssh +chmod 0750 /home/$USER/.config +chmod 0700 /home/$USER/.ssh diff --git a/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/world_readable_ssh_dir.fail.sh b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/world_readable_ssh_dir.fail.sh new file mode 100644 index 000000000000..9a6b95aa5583 --- /dev/null +++ b/linux_os/guide/system/accounts/accounts-session/accounts_user_dot_dirs_permissions/tests/world_readable_ssh_dir.fail.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# platform = Ubuntu 26.04 + +USER="cac_user" +useradd -m $USER +mkdir -p /home/$USER/.ssh +chmod 0755 /home/$USER/.ssh diff --git a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_groupowner_backup_etc_gshadow/rule.yml b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_groupowner_backup_etc_gshadow/rule.yml index 1133e78ccc3a..bf754ff3453a 100644 --- a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_groupowner_backup_etc_gshadow/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_groupowner_backup_etc_gshadow/rule.yml @@ -49,3 +49,4 @@ template: gid_or_name@debian12: '42' gid_or_name@ubuntu2204: '42' gid_or_name@ubuntu2404: shadow + gid_or_name@ubuntu2604: shadow diff --git a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_gshadow/rule.yml b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_gshadow/rule.yml index f988368aae7c..e7b0bf33c258 100644 --- a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_gshadow/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_gshadow/rule.yml @@ -52,3 +52,4 @@ template: filemode@debian12: '0640' filemode@ubuntu2204: '0640' filemode@ubuntu2404: '0640' + filemode@ubuntu2604: '0640' diff --git a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_shadow/rule.yml b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_shadow/rule.yml index b3e300599f20..92e8e0c764df 100644 --- a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_shadow/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_backup_etc_shadow/rule.yml @@ -55,6 +55,7 @@ template: filemode@debian12: '0640' filemode@ubuntu2204: '0640' filemode@ubuntu2404: '0640' + filemode@ubuntu2604: '0640' filemode@sle12: '0640' filemode@sle15: '0640' filemode@sle16: '0640' diff --git a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_gshadow/rule.yml b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_gshadow/rule.yml index ba2d383c9416..5fb572ec3eb4 100644 --- a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_gshadow/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_gshadow/rule.yml @@ -61,3 +61,4 @@ template: filemode@debian13: '0640' filemode@ubuntu2204: '0640' filemode@ubuntu2404: '0640' + filemode@ubuntu2604: '0640' diff --git a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_shadow/rule.yml b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_shadow/rule.yml index 4dc4175e98e1..2e0e37f28f65 100644 --- a/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_shadow/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_important_account_files/file_permissions_etc_shadow/rule.yml @@ -73,3 +73,4 @@ template: filemode@sle16: '0640' filemode@ubuntu2204: '0640' filemode@ubuntu2404: '0640' + filemode@ubuntu2604: '0640' diff --git a/linux_os/guide/system/permissions/files/permissions_local_var_log/rule.yml b/linux_os/guide/system/permissions/files/permissions_local_var_log/rule.yml index daae0440207a..896e077fdbc3 100644 --- a/linux_os/guide/system/permissions/files/permissions_local_var_log/rule.yml +++ b/linux_os/guide/system/permissions/files/permissions_local_var_log/rule.yml @@ -58,6 +58,7 @@ template: excluded_files@ubuntu2204: ['history.log*', 'eipp.log.xz*', '[bw]tmp', '[bw]tmp.*', '[bw]tmp-*', 'lastlog', 'lastlog.*'] excluded_files@debian13: ['history.log*', 'eipp.log.xz*', '[bw]tmp', '[bw]tmp.*', '[bw]tmp-*', 'lastlog', 'lastlog.*'] excluded_files@ubuntu2404: ['history.log*', 'eipp.log.xz*', '[bw]tmp', '[bw]tmp.*', '[bw]tmp-*', 'lastlog', 'lastlog.*', 'cloud-init.log*', 'localmessages*', 'waagent.log*'] + excluded_files@ubuntu2604: ['history.log*', 'eipp.log.xz*', '[bw]tmp', '[bw]tmp.*', '[bw]tmp-*', 'lastlog', 'lastlog.*', 'cloud-init.log*', 'localmessages*', 'waagent.log*'] file_regex: '.*' filemode: '0640' filepath: /var/log/ @@ -69,3 +70,4 @@ template: recursive@slmicro6: 'true' recursive@ubuntu2204: 'true' recursive@ubuntu2404: 'true' + recursive@ubuntu2604: 'true' diff --git a/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files.pass.sh b/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files.pass.sh index f31fb2d11fba..3e0ee5c1f532 100644 --- a/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files.pass.sh +++ b/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files.pass.sh @@ -4,7 +4,7 @@ find /var/log -exec chmod g-rwx,o-rwx {} \; {{% if product in ['ubuntu2204'] %}} excluded_files=('history.log' 'eipp.log.xz' 'btmp' 'btmp.1' 'btmp-1' 'wtmp' 'wtmp.1' 'wtmp-1' 'lastlog' 'lastlog.1') -{{% elif product in ['ubuntu2404'] %}} +{{% elif product in ['ubuntu2404', 'ubuntu2604'] %}} excluded_files=('btmp' 'btmp.1' 'btmp-1' 'wtmp' 'wtmp.1' 'wtmp-1' 'lastlog' 'lastlog.1'\ 'cloud-init.log' 'cloud-init.log2' 'localmessages' 'localmessages2' 'waagent.log' 'waagent.log2') {{% elif product in ['sle12', 'sle15'] %}} diff --git a/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files_similar.fail.sh b/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files_similar.fail.sh index 894c22dc3750..c541716a1118 100644 --- a/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files_similar.fail.sh +++ b/linux_os/guide/system/permissions/files/permissions_local_var_log/tests/excluded_files_similar.fail.sh @@ -4,7 +4,7 @@ find /var/log -exec chmod g-rwx,o-rwx {} \; {{% if product in ['ubuntu2204'] %}} excluded_files=('2history.log' '2eipp.log.xz' 'btmp1' 'wtmp1' 'lastlog1') -{{% elif product in ['ubuntu2404'] %}} +{{% elif product in ['ubuntu2404', 'ubuntu2604'] %}} excluded_files=('btmp1' 'wtmp1' 'lastlog1' '2cloud-init.log''2localmessages' '2waagent.log') {{% elif product in ['sle12', 'sle15'] %}} excluded_files=('btmp.1', 'wtmp.1', 'lastlog.1') diff --git a/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/ansible/ubuntu2604.yml b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/ansible/ubuntu2604.yml new file mode 100644 index 000000000000..f8b25de09171 --- /dev/null +++ b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/ansible/ubuntu2604.yml @@ -0,0 +1,51 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = configure +# complexity = low +# disruption = medium + +- name: Read the active tmp.mount options + ansible.builtin.command: + cmd: systemctl show tmp.mount --property=Options --value + register: tmp_mount_options + changed_when: false + check_mode: false + +- name: Require an active systemd mount definition for /tmp + ansible.builtin.assert: + that: + - tmp_mount_options.rc == 0 + - tmp_mount_options.stdout | trim | length > 0 + fail_msg: tmp.mount does not report its mount options. + +- name: Create the tmp.mount drop-in directory + ansible.builtin.file: + path: /etc/systemd/system/tmp.mount.d + state: directory + owner: root + group: root + mode: '0755' + when: "'noexec' not in (tmp_mount_options.stdout | trim).split(',')" + +- name: Add noexec to the tmp.mount options + ansible.builtin.copy: + dest: /etc/systemd/system/tmp.mount.d/99-cis-noexec.conf + content: |- + [Mount] + Options={{ tmp_mount_options.stdout | trim }},noexec + owner: root + group: root + mode: '0644' + register: tmp_mount_drop_in + when: "'noexec' not in (tmp_mount_options.stdout | trim).split(',')" + +- name: Reload systemd after changing tmp.mount + ansible.builtin.systemd_service: + daemon_reload: true + when: tmp_mount_drop_in is changed + +- name: Remount /tmp with noexec + ansible.builtin.command: + cmd: mount --options remount,noexec --target /tmp + changed_when: true + when: tmp_mount_drop_in is changed diff --git a/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/bash/ubuntu2604.sh b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/bash/ubuntu2604.sh new file mode 100644 index 000000000000..97f12d28ac85 --- /dev/null +++ b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/bash/ubuntu2604.sh @@ -0,0 +1,35 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = configure +# complexity = low +# disruption = medium + +tmp_unit="tmp.mount" +mount_option="noexec" + +if ! systemctl is-active --quiet "$tmp_unit"; then + echo "$tmp_unit is not active; the remediation cannot preserve its mount options." >&2 + exit 1 +fi + +current_options=$(systemctl show "$tmp_unit" --property=Options --value) +if [[ -z "$current_options" ]]; then + echo "$tmp_unit does not report its mount options." >&2 + exit 1 +fi + +case ",$current_options," in + *",$mount_option,"*) + exit 0 + ;; +esac + +drop_in_dir="/etc/systemd/system/${tmp_unit}.d" +drop_in_file="${drop_in_dir}/99-cis-noexec.conf" + +install -d -m 0755 "$drop_in_dir" +printf '[Mount]\nOptions=%s,%s\n' "$current_options" "$mount_option" > "$drop_in_file" +chmod 0644 "$drop_in_file" + +systemctl daemon-reload +mount --options "remount,$mount_option" --target /tmp diff --git a/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/rule.yml b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/rule.yml index d1be8719333e..2497c8b55a16 100644 --- a/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/rule.yml +++ b/linux_os/guide/system/permissions/partitions/mount_option_tmp_noexec/rule.yml @@ -6,7 +6,12 @@ title: 'Add noexec Option to /tmp' description: |- The noexec mount option can be used to prevent binaries from being executed out of /tmp. + {{% if product == "ubuntu2604" %}} + Add noexec to the Options setting in a + tmp.mount systemd drop-in. + {{% else %}} {{{ describe_mount(option="noexec", part="/tmp") }}} + {{% endif %}} rationale: |- Allowing users to execute binaries from world-writable directories @@ -55,7 +60,14 @@ template: mountoption: noexec # Note that /tmp on RHEL systems is not tmpfs +{{% if product == "ubuntu2604" %}} +fixtext: |- + Create a systemd drop-in for tmp.mount that preserves the active + mount options and adds noexec. Reload systemd and remount + /tmp with noexec. +{{% else %}} fixtext: |- {{{ fixtext_mount_option("/tmp", "noexec") }}} +{{% endif %}} srg_requirement: '{{{ srg_requirement_mount_option("/tmp", "noexec") }}}' diff --git a/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/ansible/shared.yml b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/ansible/shared.yml new file mode 100644 index 000000000000..aeed126ce4ae --- /dev/null +++ b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/ansible/shared.yml @@ -0,0 +1,16 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +- name: Install rsyslog for sudo event logging + ansible.builtin.package: + name: rsyslog + state: present + +- name: Enable rsyslog for sudo event logging + ansible.builtin.systemd_service: + name: rsyslog.service + enabled: true + state: started diff --git a/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/bash/shared.sh b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/bash/shared.sh new file mode 100644 index 000000000000..9facdf328434 --- /dev/null +++ b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/bash/shared.sh @@ -0,0 +1,9 @@ +# platform = Ubuntu 26.04 +# reboot = false +# strategy = restrict +# complexity = low +# disruption = low + +{{{ bash_package_install("rsyslog") }}} + +systemctl enable --now rsyslog.service diff --git a/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/rule.yml b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/rule.yml new file mode 100644 index 000000000000..8a54fe48502b --- /dev/null +++ b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/rule.yml @@ -0,0 +1,23 @@ +documentation_complete: true + +title: 'Ensure Sudo Events Are Logged' + +description: |- + Sudo events must be captured in a durable log. sudo-rs sends + events to the authpriv syslog facility and does not support the + classic sudo Defaults logfile= option. + +rationale: |- + A durable record of privilege escalation supports auditing and incident + investigation. + +severity: low + +platform: package[sudo] + +ocil_clause: 'sudo events are not present in the journal or a persistent log' + +ocil: |- + Run journalctl -t sudo -t sudo-rs and inspect + /var/log/auth.log. At least one location must contain sudo events. + Classic sudo may instead use a valid Defaults logfile= setting. diff --git a/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/sce/shared.sh b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/sce/shared.sh new file mode 100644 index 000000000000..feb0716b763c --- /dev/null +++ b/linux_os/guide/system/software/sudo/sudo_logging_ubuntu2604/sce/shared.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# platform = Ubuntu 26.04 +# check-import = stdout + +if journalctl -q -t sudo -t sudo-rs --since=-5min --no-pager 2>/dev/null | + grep -q '[^[:space:]]'; then + exit "$XCCDF_RESULT_PASS" +fi + +if grep -Eq '\b(sudo|sudo-rs)\[[0-9]+\]:' /var/log/auth.log 2>/dev/null; then + exit "$XCCDF_RESULT_PASS" +fi + +if ! dpkg-query --show --showformat='${db:Status-Status}' sudo-rs 2>/dev/null | + grep -qx installed; then + if grep -rPsiq \ + '^\h*Defaults\h+([^#]+,\h*)?logfile\h*=\h*("|'"'"')?\H+("|'"'"')?(,\h*\H+\h*)*\h*(#.*)?$' \ + /etc/sudoers /etc/sudoers.d 2>/dev/null; then + exit "$XCCDF_RESULT_PASS" + fi +fi + +echo 'No sudo event was found in the journal or /var/log/auth.log.' +exit "$XCCDF_RESULT_FAIL" diff --git a/products/ubuntu2604/controls/cis_ubuntu2604.yml b/products/ubuntu2604/controls/cis_ubuntu2604.yml new file mode 100644 index 000000000000..0611900a4898 --- /dev/null +++ b/products/ubuntu2604/controls/cis_ubuntu2604.yml @@ -0,0 +1,2377 @@ +--- +policy: CIS Benchmark for Ubuntu 26.04 LTS +title: CIS Benchmark for Ubuntu 26.04 LTS +id: cis_ubuntu2604 +version: "1.0.0" +source: https://www.cisecurity.org/cis-benchmarks + +levels: + - id: l1_server + - id: l2_server + inherits_from: + - l1_server + - id: l1_workstation + - id: l2_workstation + inherits_from: + - l1_workstation +reference_type: cis +product: ubuntu2604 + +controls: + - id: 1.1.1.1 + title: Ensure cramfs kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_cramfs_disabled + status: automated + - id: 1.1.1.2 + title: Ensure freevxfs kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_freevxfs_disabled + status: automated + - id: 1.1.1.3 + title: Ensure hfs kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_hfs_disabled + status: automated + - id: 1.1.1.4 + title: Ensure hfsplus kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_hfsplus_disabled + status: automated + - id: 1.1.1.5 + title: Ensure jffs2 kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_jffs2_disabled + status: automated + - id: 1.1.1.9 + title: Ensure firewire-core kernel module is not available (Automated) + levels: + - l1_server + - l2_workstation + rules: + - kernel_module_firewire-core_disabled + status: automated + - id: 1.1.1.10 + title: Ensure usb-storage kernel module is not available (Automated) + levels: + - l1_server + - l2_workstation + rules: + - kernel_module_usb-storage_disabled + status: automated + - id: 1.1.1.11 + title: Ensure unused filesystems kernel modules are not available (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 1.1.2.1.1 + title: Ensure /tmp is tmpfs or a separate partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - partition_for_tmp + status: automated + - id: 1.1.2.1.2 + title: Ensure nodev option set on /tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_tmp_nodev + status: automated + - id: 1.1.2.1.3 + title: Ensure nosuid option set on /tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_tmp_nosuid + status: automated + - id: 1.1.2.1.4 + title: Ensure noexec option set on /tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_tmp_noexec + status: automated + - id: 1.1.2.2.1 + title: Ensure /dev/shm is tmpfs or a separate partition (Automated) + levels: + - l1_server + - l1_workstation + status: automated + rules: + - partition_for_dev_shm + - id: 1.1.2.2.2 + title: Ensure nodev option set on /dev/shm partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_dev_shm_nodev + status: automated + - id: 1.1.2.2.3 + title: Ensure nosuid option set on /dev/shm partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_dev_shm_nosuid + status: automated + - id: 1.1.2.2.4 + title: Ensure noexec option set on /dev/shm partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_dev_shm_noexec + status: automated + - id: 1.1.2.3.2 + title: Ensure nodev option set on /home partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_home_nodev + status: automated + - id: 1.1.2.3.3 + title: Ensure nosuid option set on /home partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_home_nosuid + status: automated + - id: 1.1.2.4.2 + title: Ensure nodev option set on /var partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_nodev + status: automated + - id: 1.1.2.4.3 + title: Ensure nosuid option set on /var partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_nosuid + status: automated + - id: 1.1.2.5.2 + title: Ensure nodev option set on /var/tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_tmp_nodev + status: automated + - id: 1.1.2.5.3 + title: Ensure nosuid option set on /var/tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_tmp_nosuid + status: automated + - id: 1.1.2.5.4 + title: Ensure noexec option set on /var/tmp partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_tmp_noexec + status: automated + - id: 1.1.2.6.2 + title: Ensure nodev option set on /var/log partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_nodev + status: automated + - id: 1.1.2.6.3 + title: Ensure nosuid option set on /var/log partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_nosuid + status: automated + - id: 1.1.2.6.4 + title: Ensure noexec option set on /var/log partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_noexec + status: automated + - id: 1.1.2.7.2 + title: Ensure nodev option set on /var/log/audit partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_audit_nodev + status: automated + - id: 1.1.2.7.3 + title: Ensure nosuid option set on /var/log/audit partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_audit_nosuid + status: automated + - id: 1.1.2.7.4 + title: Ensure noexec option set on /var/log/audit partition (Automated) + levels: + - l1_server + - l1_workstation + rules: + - mount_option_var_log_audit_noexec + status: automated + - id: 1.2.1.1 + title: Ensure source.list and .source files use the Signed-By option (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 1.2.1.3 + title: Ensure access to gpg key files are configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_apt_gpg_keys + - file_groupowner_apt_sources_list_d + - file_owner_apt_gpg_keys + - file_owner_apt_sources_list_d + - file_permissions_apt_gpg_keys + - file_permissions_apt_sources_list_d + status: automated + - id: 1.2.1.4 + title: Ensure access to /etc/apt/trusted.gpg.d directory is configured (Automated) + levels: &l1_both + - l1_server + - l1_workstation + rules: + - directory_groupowner_apt_trusted_gpg_d + - directory_owner_apt_trusted_gpg_d + - directory_permissions_apt_trusted_gpg_d + status: automated + - id: 1.2.1.5 + title: Ensure access to /etc/apt/auth.conf.d directory is configured (Automated) + levels: *l1_both + rules: + - directory_groupowner_apt_auth_conf_d + - directory_owner_apt_auth_conf_d + - directory_permissions_apt_auth_conf_d + status: automated + - id: 1.2.1.6 + title: Ensure access to files in /etc/apt/auth.conf.d is configured (Automated) + levels: *l1_both + rules: + - file_groupowner_apt_auth_conf_d + - file_owner_apt_auth_conf_d + - file_permissions_apt_auth_conf_d + status: automated + - id: 1.2.1.7 + title: Ensure access to /usr/share/keyrings directory is configured (Automated) + levels: *l1_both + rules: + - directory_groupowner_usr_share_keyrings + - directory_owner_usr_share_keyrings + - directory_permissions_usr_share_keyrings + status: automated + - id: 1.2.1.8 + title: Ensure access to /etc/apt/sources.list.d directory is configured (Automated) + levels: *l1_both + rules: + - directory_groupowner_apt_sources_list_d + - directory_owner_apt_sources_list_d + - directory_permissions_apt_sources_list_d + status: automated + - id: 1.2.1.9 + title: Ensure access to files in /etc/apt/sources.list.d is configured (Automated) + levels: *l1_both + rules: + - file_groupowner_apt_sources_list_d + - file_owner_apt_sources_list_d + - file_permissions_apt_sources_list_d + status: automated + - id: 1.2.1.10 + title: Ensure repositories are configured to use HTTPS in /etc/apt/sources.list (Automated) + levels: *l1_both + rules: + - apt_sources_list_https + status: automated + - id: 1.2.1.11 + title: Ensure repositories are configured to use HTTPS in /etc/apt/sources.list.d (Automated) + levels: *l1_both + rules: + - apt_sources_list_d_https + status: automated + - id: 1.2.1.12 + title: Ensure AllowInsecureRepositories is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - apt_configure_allow_insecure_repositories + status: automated + - id: 1.2.1.13 + title: Ensure AllowWeakRepositories is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - apt_configure_allow_weak_repositories + status: automated + - id: 1.2.1.14 + title: Ensure AllowDowngradeToInsecureRepositories is configured (Automated) + levels: *l1_both + rules: + - apt_configure_allow_downgrade_to_insecure_repositories + status: automated + - id: 1.2.1.15 + title: Ensure Acquire::Check-Date is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - apt_configure_check_date + status: automated + - id: 1.2.2.1 + title: Ensure updates, patches, and additional security software are installed (Manual) + levels: *l1_both + status: manual + - id: 1.3.1.1 + title: Ensure apparmor packages are installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_apparmor_installed + - package_apparmor-utils_installed + status: automated + - id: 1.3.1.2 + title: Ensure AppArmor is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - grub2_enable_apparmor + status: automated + - id: 1.3.1.4 + title: Ensure kernel.apparmor_restrict_unprivileged_unconfined is configured (Automated) + levels: *l1_both + rules: + - sysctl_kernel_apparmor_restrict_unprivileged_unconfined + status: automated + - id: 1.4.1 + title: Ensure bootloader password is set (Automated) + levels: + - l1_server + - l1_workstation + rules: + - grub2_password + status: automated + - id: 1.4.2 + title: Ensure access to bootloader config is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_owner_grub2_cfg + - file_permissions_grub2_cfg + status: automated + - id: 1.5.1 + title: Ensure fs.protected_hardlinks is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_fs_protected_hardlinks + status: automated + - id: 1.5.3 + title: Ensure kernel.yama.ptrace_scope is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_kernel_yama_ptrace_scope_value=1 + - sysctl_kernel_yama_ptrace_scope + status: automated + - id: 1.5.4 + title: Ensure fs.suid_dumpable is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_fs_suid_dumpable + status: automated + - id: 1.5.5 + title: Ensure kernel.dmesg_restrict is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_kernel_dmesg_restrict + status: automated + - id: 1.5.6 + title: Ensure prelink is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_prelink_removed + status: automated + - id: 1.5.7 + title: Ensure Automatic Error Reporting is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - service_apport_disabled + status: automated + - id: 1.5.8 + title: Ensure kernel.kptr_restrict is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_kernel_kptr_restrict + status: automated + - id: 1.5.9 + title: Ensure kernel.randomize_va_space is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_kernel_randomize_va_space + status: automated + - id: 1.5.10 + title: Ensure core file size is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - disable_users_coredumps + status: automated + - id: 1.5.11 + title: Ensure systemd-coredump ProcessSizeMax is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - coredump_disable_backtraces + status: automated + - id: 1.5.12 + title: Ensure systemd-coredump Storage is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - coredump_disable_storage + status: automated + - id: 1.6.1 + title: Ensure /etc/motd is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - cis_banner_text=cis + - banner_etc_motd_cis + status: automated + - id: 1.6.2 + title: Ensure /etc/issue is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - cis_banner_text=cis + - banner_etc_issue_cis + status: automated + - id: 1.6.3 + title: Ensure /etc/issue.net is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - cis_banner_text=cis + - banner_etc_issue_net_cis + status: automated + - id: 1.6.4 + title: Ensure pam_motd is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - pam_motd_configured + status: automated + - id: 1.6.5 + title: Ensure sshd warning Banner is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - cis_banner_text=cis + - banner_etc_issue_net_cis + status: automated + notes: The Level 1 profiles also select sshd_enable_warning_banner_net, which configures /etc/issue.net as the SSH warning + banner. + - id: 1.6.6 + title: Ensure access to /etc/motd is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_motd + - file_owner_etc_motd + - file_permissions_etc_motd + status: automated + - id: 1.6.7 + title: Ensure access to /etc/issue is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_issue + - file_owner_etc_issue + - file_permissions_etc_issue + status: automated + - id: 1.6.8 + title: Ensure access to /etc/issue.net is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_issue_net + - file_owner_etc_issue_net + - file_permissions_etc_issue_net + status: automated + - id: 1.6.9 + title: Ensure access to pam_motd file is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_permissions_pam_motd + status: automated + - id: 1.6.10 + title: Ensure access to sshd warning banner is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_issue_net + - file_owner_etc_issue_net + - file_permissions_etc_issue_net + status: automated + notes: The Level 1 profiles configure /etc/issue.net as the SSH warning banner, so the existing ownership and mode rules + cover the selected banner file. + - id: 1.6.11 + title: Ensure update-notifier-motd.service is not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - service_update-notifier-motd_disabled + status: automated + - id: 1.6.12 + title: Ensure update-notifier-motd.timer is not in use (Automated) + levels: + - l1_server + rules: + - timer_update-notifier-motd_disabled + status: automated + - id: 1.7.1 + title: Ensure GDM login banner is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - dconf_login_banner_text=cis_default + - dconf_login_banner_contents=cis_default + - dconf_gnome_banner_enabled + - dconf_gnome_login_banner_text + status: automated + - id: 1.7.2 + title: Ensure GDM disable-user-list is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - dconf_gnome_disable_user_list + status: automated + - id: 1.7.3 + title: Ensure GDM screen lock is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - inactivity_timeout_value=15_minutes + - var_screensaver_lock_delay=5_seconds + - dconf_gnome_screensaver_idle_delay + - dconf_gnome_screensaver_lock_enabled + - dconf_gnome_screensaver_lock_delay + status: automated + - id: 1.7.4 + title: Ensure GDM automount is configured (Automated) + levels: + - l1_server + - l2_workstation + rules: + - dconf_gnome_disable_automount + - dconf_gnome_disable_automount_open + status: automated + - id: 1.7.5 + title: Ensure GDM autorun-never is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - dconf_gnome_disable_autorun + status: automated + - id: 1.7.6 + title: Ensure XDMCP is not enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - gnome_gdm_disable_xdmcp + status: automated + - id: 2.1.1 + title: Ensure autofs services are not in use (Automated) + levels: + - l1_server + - l2_workstation + rules: + - service_autofs_disabled + - package_autofs_removed + status: automated + - id: 2.1.2 + title: Ensure avahi daemon services are not in use (Automated) + levels: + - l1_server + - l2_workstation + rules: + - package_avahi_removed + - service_avahi-daemon_disabled + status: automated + - id: 2.1.3 + title: Ensure dhcp server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_dhcp_removed + - service_dhcpd_disabled + - service_dhcpd6_disabled + status: automated + - id: 2.1.4 + title: Ensure dns server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_bind_removed + - service_named_disabled + status: automated + - id: 2.1.5 + title: Ensure dnsmasq services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_dnsmasq_removed + - service_dnsmasq_disabled + status: automated + - id: 2.1.6 + title: Ensure ftp server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_vsftpd_removed + - service_vsftpd_disabled + status: automated + - id: 2.1.7 + title: Ensure ldap server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_openldap-servers_removed + - service_slapd_disabled + status: automated + - id: 2.1.8 + title: Ensure message access server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_dovecot_removed + - service_dovecot_disabled + status: automated + - id: 2.1.9 + title: Ensure network file system services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_nfs-kernel-server_removed + - service_nfs_disabled + status: automated + - id: 2.1.10 + title: Ensure nis server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_ypserv_removed + - service_ypserv_disabled + status: automated + - id: 2.1.11 + title: Ensure print server services are not in use (Automated) + levels: + - l1_server + - l2_workstation + rules: + - package_cups_removed + - service_cups_disabled + status: automated + - id: 2.1.12 + title: Ensure rpcbind services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_rpcbind_removed + - service_rpcbind_disabled + status: automated + - id: 2.1.13 + title: Ensure rsync services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_rsync_removed + - service_rsyncd_disabled + status: automated + - id: 2.1.14 + title: Ensure samba file server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_samba_removed + - service_smb_disabled + status: automated + - id: 2.1.15 + title: Ensure snmp services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_net-snmp_removed + - service_snmpd_disabled + status: automated + - id: 2.1.16 + title: Ensure telnet server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_inetutils-telnetd_removed + - package_telnetd_removed + status: automated + - id: 2.1.17 + title: Ensure tftp server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_tftp-server_removed + - service_tftp_disabled + status: automated + - id: 2.1.18 + title: Ensure web proxy server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_squid_removed + - service_squid_disabled + status: automated + - id: 2.1.19 + title: Ensure web server services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_httpd_removed + - package_nginx_removed + - service_httpd_disabled + - service_nginx_disabled + status: automated + - id: 2.1.20 + title: Ensure xinetd services are not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_xinetd_removed + - service_xinetd_disabled + status: automated + - id: 2.1.22 + title: Ensure mail transfer agents are configured for local-only mode (Automated) + levels: *l1_both + rules: + - has_nonlocal_mta + - var_postfix_inet_interfaces=loopback-only + - postfix_network_listening_disabled + status: automated + - id: 2.1.23 + title: Ensure only approved services are listening on a network interface (Manual) + levels: *l1_both + status: manual + - id: 2.2.1 + title: Ensure nis client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_nis_removed + status: automated + - id: 2.2.2 + title: Ensure rsh client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_rsh_removed + status: automated + - id: 2.2.3 + title: Ensure talk client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_talk_removed + status: automated + - id: 2.2.4 + title: Ensure telnet client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_inetutils-telnet_removed + - package_telnet_removed + status: automated + - id: 2.2.5 + title: Ensure ldap client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_openldap-clients_removed + status: automated + - id: 2.2.6 + title: Ensure ftp client is not installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_ftp_removed + - package_tnftp_removed + status: automated + - id: 2.3.1.1 + title: Ensure a single time synchronization daemon is in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_timesync_service=chronyd + - package_chrony_installed + - service_chronyd_enabled + - service_chronyd_disabled + - package_timesyncd_installed + - service_timesyncd_enabled + - service_timesyncd_disabled + - ntp_single_service_active + status: automated + notes: | + To select which timesync daemon to install and configure, use the + profile variable var_timesync_service. + - id: 2.3.2.1 + title: Ensure systemd-timesyncd is configured with authorized timeserver (Automated) + levels: *l1_both + rules: + - var_multiple_time_servers=ubuntu + - service_timesyncd_configured + status: automated + - id: 2.3.2.2 + title: Ensure systemd-timesyncd is enabled and running (Automated) + levels: + - l1_server + - l1_workstation + related_rules: + - service_timesyncd_enabled + - service_timesyncd_disabled + status: automated + notes: Implemented in 2.3.1.1 + - id: 2.3.3.1 + title: Ensure chrony is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_multiple_time_servers=ubuntu + - chronyd_specify_remote_server + status: automated + notes: |- + The Ubuntu 26.04 OVAL check follows active sourcedir and confdir + directives and accepts server or pool entries according to site policy. + - id: 2.3.3.2 + title: Ensure chrony is running as user _chrony (Automated) + levels: + - l1_server + - l1_workstation + rules: + - chronyd_run_as_chrony_user + status: automated + - id: 2.3.3.3 + title: Ensure chrony is enabled and running (Automated) + levels: + - l1_server + - l1_workstation + related_rules: + - service_chronyd_enabled + - service_chronyd_disabled + status: automated + notes: Implemented in 2.3.1.1 + - id: 2.4.1.1 + title: Ensure cron daemon is enabled and active (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_cron_installed + - service_cron_enabled + status: automated + - id: 2.4.1.2 + title: Ensure access to /etc/crontab is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_crontab + - file_owner_crontab + - file_permissions_crontab + status: automated + - id: 2.4.1.3 + title: Ensure access to /etc/cron.hourly is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_hourly + - file_owner_cron_hourly + - file_permissions_cron_hourly + status: automated + - id: 2.4.1.4 + title: Ensure access to /etc/cron.daily is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_daily + - file_owner_cron_daily + - file_permissions_cron_daily + status: automated + - id: 2.4.1.5 + title: Ensure access to /etc/cron.weekly is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_weekly + - file_owner_cron_weekly + - file_permissions_cron_weekly + status: automated + - id: 2.4.1.6 + title: Ensure access to /etc/cron.monthly is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_monthly + - file_owner_cron_monthly + - file_permissions_cron_monthly + status: automated + - id: 2.4.1.7 + title: Ensure access to /etc/cron.yearly is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_yearly + - file_owner_cron_yearly + - file_permissions_cron_yearly + status: automated + - id: 2.4.1.8 + title: Ensure access to /etc/cron.d is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_cron_d + - file_owner_cron_d + - file_permissions_cron_d + status: automated + - id: 2.4.1.9 + title: Ensure access to crontab is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_cron_allow_exists + - file_cron_deny_not_exist + - file_groupowner_cron_allow + - file_owner_cron_allow + - file_permissions_cron_allow + status: automated + - id: 2.4.2.1 + title: Ensure access to at is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_at_allow_exists + - file_groupowner_at_allow + - file_owner_at_allow + - file_permissions_at_allow + - file_groupowner_at_deny + - file_owner_at_deny + - file_permissions_at_deny + status: automated + notes: file_owner_at_deny and file_owner_at_allow currently require root as owner and don't accept daemon + - id: 3.1.1 + title: Ensure IPv6 status is identified (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 3.1.2 + title: Ensure wireless interfaces are not available (Automated) + levels: + - l1_server + rules: + - wireless_disable_interfaces + status: automated + - id: 3.1.3 + title: Ensure bluetooth services are not in use (Automated) + levels: + - l1_server + - l2_workstation + rules: + - service_bluetooth_disabled + status: automated + - id: 3.2.1 + title: Ensure atm kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_atm_disabled + status: automated + - id: 3.2.2 + title: Ensure can kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_can_disabled + status: automated + - id: 3.2.3 + title: Ensure dccp kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_dccp_disabled + status: automated + - id: 3.2.4 + title: Ensure rds kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_rds_disabled + status: automated + - id: 3.2.5 + title: Ensure sctp kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_sctp_disabled + status: automated + - id: 3.2.6 + title: Ensure tipc kernel module is not available (Automated) + levels: + - l1_server + - l1_workstation + rules: + - kernel_module_tipc_disabled + status: automated + - id: 3.2.7 + title: Ensure unneeded network protocols are not available (Manual) + levels: *l1_both + status: manual + - id: 3.3.1.1 + title: Ensure net.ipv4.ip_forward is configured (Automated) + levels: + - l1_workstation + - l1_server + rules: + - sysctl_net_ipv4_ip_forward + status: automated + - id: 3.3.1.2 + title: Ensure net.ipv4.conf.all.forwarding is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_forwarding + status: automated + - id: 3.3.1.3 + title: Ensure net.ipv4.conf.default.forwarding is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_default_forwarding + status: automated + - id: 3.3.1.4 + title: Ensure net.ipv4.conf.all.send_redirects is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_send_redirects + status: automated + - id: 3.3.1.5 + title: Ensure net.ipv4.conf.default.send_redirects is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_default_send_redirects + status: automated + - id: 3.3.1.6 + title: Ensure net.ipv4.icmp_ignore_bogus_error_responses is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_icmp_ignore_bogus_error_responses + status: automated + - id: 3.3.1.7 + title: Ensure net.ipv4.icmp_echo_ignore_broadcasts is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_icmp_echo_ignore_broadcasts + status: automated + - id: 3.3.1.8 + title: Ensure net.ipv4.conf.all.accept_redirects is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_accept_redirects + status: automated + - id: 3.3.1.9 + title: Ensure net.ipv4.conf.default.accept_redirects is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_conf_default_accept_redirects + status: automated + - id: 3.3.1.10 + title: Ensure net.ipv4.conf.all.secure_redirects is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_secure_redirects + status: automated + - id: 3.3.1.11 + title: Ensure net.ipv4.conf.default.secure_redirects is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_conf_default_secure_redirects + status: automated + - id: 3.3.1.12 + title: Ensure net.ipv4.conf.all.rp_filter is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_rp_filter + status: automated + - id: 3.3.1.13 + title: Ensure net.ipv4.conf.default.rp_filter is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_default_rp_filter + status: automated + - id: 3.3.1.14 + title: Ensure net.ipv4.conf.all.accept_source_route is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_conf_all_accept_source_route + status: automated + - id: 3.3.1.15 + title: Ensure net.ipv4.conf.default.accept_source_route is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv4_conf_default_accept_source_route + status: automated + - id: 3.3.1.16 + title: Ensure net.ipv4.conf.all.log_martians is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_log_martians + status: automated + - id: 3.3.1.17 + title: Ensure net.ipv4.conf.default.log_martians is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_default_log_martians + status: automated + notes: See notes for 3.3.1.16. + - id: 3.3.1.18 + title: Ensure net.ipv4.tcp_syncookies is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_tcp_syncookies + status: automated + - id: 3.3.1.19 + title: Ensure net.ipv4.conf.all.route_localnet is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv4_conf_all_route_localnet + status: automated + - id: 3.3.2.1 + title: Ensure net.ipv6.conf.all.forwarding is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv6_conf_all_forwarding + status: automated + - id: 3.3.2.2 + title: Ensure net.ipv6.conf.default.forwarding is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv6_conf_default_forwarding + status: automated + - id: 3.3.2.3 + title: Ensure net.ipv6.conf.all.accept_redirects is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv6_conf_all_accept_redirects + status: automated + - id: 3.3.2.4 + title: Ensure net.ipv6.conf.default.accept_redirects is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv6_conf_default_accept_redirects + status: automated + - id: 3.3.2.5 + title: Ensure net.ipv6.conf.all.accept_source_route is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv6_conf_all_accept_source_route + status: automated + - id: 3.3.2.6 + title: Ensure net.ipv6.conf.default.accept_source_route is configured (Automated) + levels: *l1_both + rules: + - sysctl_net_ipv6_conf_default_accept_source_route + status: automated + - id: 3.3.2.7 + title: Ensure net.ipv6.conf.all.accept_ra is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv6_conf_all_accept_ra + status: automated + - id: 3.3.2.8 + title: Ensure net.ipv6.conf.default.accept_ra is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sysctl_net_ipv6_conf_default_accept_ra + status: automated + - id: 4.1.1 + title: Ensure ufw is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_ufw_installed + status: automated + - id: 4.1.2 + title: Ensure ufw service is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - check_ufw_active + - service_ufw_enabled + status: automated + - id: 4.1.3 + title: Ensure ufw incoming default is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - ufw_default_incoming_rule + status: automated + - id: 4.1.5 + title: Ensure ufw routed default is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - ufw_disabled_routed + status: automated + - id: 5.1.1 + title: Ensure access to /etc/ssh/sshd_config is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_sshd_config + - file_owner_sshd_config + - file_permissions_sshd_config + status: automated + - id: 5.1.2 + title: Ensure access to /etc/ssh/sshd_config.d is configured (Automated) + levels: *l1_both + rules: + - directory_groupowner_sshd_config_d + - directory_owner_sshd_config_d + - directory_permissions_sshd_config_d + status: automated + - id: 5.1.3 + title: Ensure access to SSH private host key files is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_permissions_sshd_private_key + status: automated + - id: 5.1.4 + title: Ensure access to SSH public host key files is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_permissions_sshd_pub_key + status: automated + - id: 5.1.5 + title: Ensure sshd access is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_limit_user_access + status: automated + - id: 5.1.6 + title: Ensure sshd Banner is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_enable_warning_banner_net + status: automated + - id: 5.1.7 + title: Ensure sshd Ciphers are configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_use_strong_ciphers + status: automated + - id: 5.1.8 + title: Ensure sshd ClientAliveInterval and ClientAliveCountMax are configured (Automated) + levels: *l1_both + rules: + - sshd_idle_timeout_value=5_minutes + - var_sshd_set_keepalive=3 + - sshd_set_idle_timeout + - sshd_set_keepalive_positive + status: partial + - id: 5.1.9 + title: Ensure sshd DisableForwarding is enabled (Automated) + levels: + - l1_workstation + - l1_server + rules: + - sshd_disable_forwarding + status: automated + - id: 5.1.10 + title: Ensure sshd GSSAPIAuthentication is disabled (Automated) + levels: + - l1_workstation + - l1_server + rules: + - sshd_disable_gssapi_auth + status: automated + - id: 5.1.11 + title: Ensure sshd HostbasedAuthentication is disabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - disable_host_auth + status: automated + - id: 5.1.12 + title: Ensure sshd IgnoreRhosts is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_disable_rhosts + status: automated + - id: 5.1.13 + title: Ensure sshd KexAlgorithms is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_use_strong_kex_ubuntu2604 + status: automated + - id: 5.1.14 + title: Ensure sshd post-quantum key exchange algorithms are configured (Manual) + levels: *l1_both + status: manual + - id: 5.1.15 + title: Ensure sshd LoginGraceTime is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_sshd_set_login_grace_time=60 + - sshd_set_login_grace_time + status: automated + - id: 5.1.16 + title: Ensure sshd LogLevel is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_set_loglevel_info + status: automated + - id: 5.1.17 + title: Ensure sshd MACs are configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_strong_macs=cis_ubuntu2404 + - sshd_use_strong_macs + status: automated + - id: 5.1.18 + title: Ensure sshd MaxAuthTries is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_max_auth_tries_value=4 + - sshd_set_max_auth_tries + status: automated + - id: 5.1.19 + title: Ensure sshd MaxSessions is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_sshd_max_sessions=10 + - sshd_set_max_sessions + status: automated + - id: 5.1.20 + title: Ensure sshd MaxStartups is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_sshd_set_maxstartups=10:30:60 + - sshd_set_maxstartups + status: automated + - id: 5.1.21 + title: Ensure sshd PermitEmptyPasswords is disabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_disable_empty_passwords_default + status: automated + - id: 5.1.22 + title: Ensure sshd PermitRootLogin is disabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_disable_root_login + status: automated + - id: 5.1.23 + title: Ensure sshd PermitUserEnvironment is disabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_do_not_permit_user_env + status: automated + - id: 5.1.24 + title: Ensure sshd UsePAM is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sshd_enable_pam + status: automated + - id: 5.2.1 + title: Ensure sudo is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_sudo_installed + status: automated + - id: 5.2.2 + title: Ensure sudo commands use pty (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sudo_add_use_pty + status: automated + - id: 5.2.3 + title: Ensure sudo log file exists (Automated) + levels: + - l1_server + - l1_workstation + rules: + - sudo_logging_ubuntu2604 + status: automated + - id: 5.2.5 + title: Ensure users must provide a password for privilege escalation (Automated) + levels: *l1_both + rules: + - sudo_remove_no_authenticate + status: automated + - id: 5.2.6 + title: Ensure sudo timestamp_timeout is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_sudo_timestamp_timeout=15_minutes + - sudo_require_reauthentication + status: automated + - id: 5.2.7 + title: Ensure access to the su command is restricted (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_pam_wheel_group_for_su=cis + - ensure_pam_wheel_group_empty + - use_pam_wheel_group_for_su + status: automated + - id: 5.3.1.1 + title: Ensure latest version of pam is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_pam_runtime_installed + status: automated + notes: | + The CIS control checks that version >= 1.5.3-5 and not that + it is the latest version as the title suggests. + - id: 5.3.1.2 + title: Ensure latest version of libpam-modules is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_pam_modules_installed + status: automated + - id: 5.3.1.3 + title: Ensure latest version of libpam-pwquality is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_pam_pwquality_installed + status: automated + - id: 5.3.1.4 + title: Ensure latest version of cracklib-runtime is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_cracklib-runtime_installed + status: automated + - id: 5.3.2.1 + title: Ensure pam_unix module is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_unix_enabled + status: automated + - id: 5.3.2.2 + title: Ensure pam_faillock module is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_passwords_pam_faillock_enabled + status: automated + - id: 5.3.2.3 + title: Ensure pam_pwquality module is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_pwquality_enabled + status: automated + - id: 5.3.2.4 + title: Ensure pam_pwhistory module is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_pwhistory_enabled + status: automated + - id: 5.3.3.1.1 + title: Ensure password failed attempts lockout is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_passwords_pam_faillock_deny=4 + - accounts_passwords_pam_faillock_deny + status: automated + - id: 5.3.3.1.2 + title: Ensure password unlock time is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_passwords_pam_faillock_unlock_time=900 + - accounts_passwords_pam_faillock_unlock_time + status: automated + - id: 5.3.3.2.1 + title: Ensure password number of changed characters is configured (Automated) + levels: *l1_both + rules: + - var_password_pam_difok=2 + - accounts_password_pam_difok + status: automated + - id: 5.3.3.2.2 + title: Ensure password length is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_password_pam_minlen=14 + - accounts_password_pam_minlen + status: automated + - id: 5.3.3.2.3 + title: Ensure password complexity is configured (Manual) + levels: + - l1_server + - l1_workstation + rules: + - var_password_pam_minclass=4 + - var_password_pam_dcredit=1 + - var_password_pam_lcredit=1 + - var_password_pam_ocredit=1 + - var_password_pam_ucredit=1 + - accounts_password_pam_minclass + - accounts_password_pam_dcredit + - accounts_password_pam_lcredit + - accounts_password_pam_ocredit + - accounts_password_pam_ucredit + status: automated + - id: 5.3.3.2.4 + title: Ensure password same consecutive characters is configured (Automated) + levels: *l1_both + rules: + - var_password_pam_maxrepeat=3 + - accounts_password_pam_maxrepeat + status: automated + - id: 5.3.3.2.5 + title: Ensure password maximum sequential characters is configured (Automated) + levels: *l1_both + rules: + - accounts_password_pam_maxsequence + status: automated + - id: 5.3.3.2.6 + title: Ensure password dictionary check is enabled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_password_pam_dictcheck=1 + - accounts_password_pam_dictcheck + status: automated + - id: 5.3.3.2.7 + title: Ensure password quality checking is enforced (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_password_pam_enforcing=1 + - accounts_password_pam_enforcing + status: automated + - id: 5.3.3.2.8 + title: Ensure password quality is enforced for the root user (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_enforce_root + status: automated + - id: 5.3.3.3.1 + title: Ensure password history remember is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_password_pam_remember=24 + - accounts_password_pam_pwhistory_remember + status: automated + - id: 5.3.3.3.2 + title: Ensure password history is enforced for the root user (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_pwhistory_enforce_root + status: automated + - id: 5.3.3.3.3 + title: Ensure pam_pwhistory includes use_authtok (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_pwhistory_use_authtok + status: automated + - id: 5.3.3.4.1 + title: Ensure pam_unix does not include nullok (Automated) + levels: + - l1_server + - l1_workstation + rules: + - no_empty_passwords_unix + status: automated + - id: 5.3.3.4.2 + title: Ensure pam_unix does not include remember (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_unix_no_remember + status: automated + - id: 5.3.3.4.3 + title: Ensure pam_unix includes a strong password hashing algorithm (Automated) + levels: *l1_both + rules: + - var_password_hashing_algorithm_pam=yescrypt + - set_password_hashing_algorithm_systemauth + status: automated + - id: 5.3.3.4.4 + title: Ensure pam_unix includes use_authtok (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_pam_unix_authtok + status: automated + - id: 5.4.1.1 + title: Ensure password expiration is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_maximum_age_login_defs=365 + - accounts_maximum_age_login_defs + - accounts_password_set_max_life_existing + status: automated + - id: 5.4.1.3 + title: Ensure password expiration warning days is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_password_warn_age_login_defs=7 + - accounts_password_warn_age_login_defs + status: automated + - id: 5.4.1.4 + title: Ensure strong password hashing algorithm is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - set_password_hashing_algorithm_logindefs + - var_password_hashing_algorithm=cis_ubuntu2404 + status: automated + notes: Rule allows either SHA512 or YESCRYPT + - id: 5.4.1.5 + title: Ensure inactive password lock is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_account_disable_post_pw_expiration=45 + - account_disable_post_pw_expiration + - accounts_set_post_pw_existing + status: automated + notes: CIS setting now 45 days. + - id: 5.4.1.6 + title: Ensure all users last password change date is in the past (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_last_change_is_in_past + status: automated + - id: 5.4.2.1 + title: Ensure root is the only UID 0 account (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_no_uid_except_zero + status: automated + - id: 5.4.2.2 + title: Ensure root is the only GID 0 account (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_root_gid_zero + status: automated + notes: | + The remediation is not automated as the removal or modification + of group IDs from a system is too disruptive. + - id: 5.4.2.3 + title: Ensure group root is the only GID 0 group (Automated) + levels: + - l1_server + - l1_workstation + rules: + - groups_no_zero_gid_except_root + status: automated + notes: | + The remediation is not automated as the removal or modification + of group IDs from a system is too disruptive. + - id: 5.4.2.4 + title: Ensure root account access is controlled (Automated) + levels: + - l1_server + - l1_workstation + rules: + - ensure_root_access_controlled + status: automated + notes: |- + This rule doesn't come with a remediation, as the exact requirement allows root to either + have a password or be locked. + - id: 5.4.2.5 + title: Ensure root path integrity (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_root_path_dirs_no_write + - root_path_all_dirs + - root_path_no_dot + - no_dirs_unowned_by_root + status: automated + - id: 5.4.2.6 + title: Ensure root user umask is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_umask_root + status: automated + - id: 5.4.2.7 + title: Ensure system accounts do not have a valid login shell (Automated) + levels: + - l1_server + - l1_workstation + rules: + - no_shelllogin_for_systemaccounts + status: automated + - id: 5.4.2.8 + title: Ensure accounts without a valid login shell are locked (Automated) + levels: + - l1_server + - l1_workstation + rules: + - no_invalid_shell_accounts_unlocked + status: automated + notes: | + Remediation is not automated. + - id: 5.4.3.2 + title: Ensure default user shell timeout is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_tmout=15_min + - accounts_tmout + status: automated + - id: 5.4.3.3 + title: Ensure default user umask is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - var_accounts_user_umask=027 + - accounts_umask_etc_bashrc + - accounts_umask_etc_login_defs + - accounts_umask_etc_profile + status: automated + - id: 6.1.1.1.1 + title: Ensure journald service is active (Automated) + levels: + - l1_server + - l1_workstation + rules: + - service_systemd-journald_enabled + status: automated + - id: 6.1.1.1.2 + title: Ensure systemd-journal-remote service is not in use (Automated) + levels: + - l1_server + - l1_workstation + rules: + - socket_systemd-journal-remote_disabled + status: automated + - id: 6.1.1.1.3 + title: Ensure journald is configured to send logs to rsyslog (Automated) + levels: + - l1_server + - l1_workstation + rules: + - journald_forward_to_syslog + status: automated + - id: 6.1.1.1.4 + title: Ensure journald log file access is configured (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 6.1.1.1.5 + title: Ensure journald log file rotation is configured (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 6.1.1.1.6 + title: Ensure journald Storage is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - journald_storage + status: automated + - id: 6.1.1.1.7 + title: Ensure journald Compress is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - journald_compress + status: automated + - id: 6.1.2.1 + title: Ensure rsyslog is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - package_rsyslog_installed + status: automated + - id: 6.1.2.2 + title: Ensure rsyslog service is enabled and active (Automated) + levels: + - l1_server + - l1_workstation + rules: + - service_rsyslog_enabled + status: automated + - id: 6.1.2.3 + title: Ensure rsyslog log file creation mode is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - rsyslog_filecreatemode + status: automated + - id: 6.1.2.4 + title: Ensure rsyslog logging is configured (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 6.1.2.5 + title: Ensure rsyslog is configured to send logs to a remote log host (Manual) + levels: *l1_both + related_rules: + - rsyslog_remote_loghost + status: manual + - id: 6.1.2.6 + title: Ensure rsyslog is not configured to receive logs from a remote client (Automated) + levels: *l1_both + rules: + - rsyslog_nolisten + status: automated + - id: 6.1.2.7 + title: Ensure logrotate is configured (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 6.1.3.1 + title: Ensure access to all logfiles has been configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupownerships_var_log_apt + - file_groupownerships_var_log_gdm + - file_groupownerships_var_log_gdm3 + - file_groupownerships_var_log_landscape + - file_groupownerships_var_log_sssd + - file_groupowner_var_log_auth + - file_groupowner_var_log_cloud_init + - file_groupowner_var_log_journal + - file_groupowner_var_log_lastlog + - file_groupowner_var_log_localmessages + - file_groupowner_var_log_messages + - file_groupowner_var_log_secure + - file_groupowner_var_log_syslog + - file_groupowner_var_log_waagent + - file_groupowner_var_log_wbtmp + - file_ownerships_var_log_apt + - file_ownerships_var_log_gdm + - file_ownerships_var_log_gdm3 + - file_ownerships_var_log_landscape + - file_ownerships_var_log_sssd + - file_owner_var_log_auth + - file_owner_var_log_cloud_init + - file_owner_var_log_journal + - file_owner_var_log_lastlog + - file_owner_var_log_localmessages + - file_owner_var_log_messages + - file_owner_var_log_secure + - file_owner_var_log_syslog + - file_owner_var_log_waagent + - file_owner_var_log_wbtmp + - file_permissions_var_log_apt + - file_permissions_var_log_auth + - file_permissions_var_log_cloud-init + - file_permissions_var_log_gdm + - file_permissions_var_log_gdm3 + - file_permissions_var_log_lastlog + - file_permissions_var_log_cloud-init + - file_permissions_var_log_localmessages + - file_permissions_var_log_messages + - file_permissions_var_log_secure + - file_permissions_var_log_sssd + - file_permissions_var_log_syslog + - file_permissions_var_log_waagent + - file_permissions_var_log_wbtmp + - file_groupownerships_var_log + - file_ownerships_var_log + - permissions_local_var_log + status: automated + - id: 6.3.1 + title: Ensure AIDE is installed (Automated) + levels: + - l1_server + - l1_workstation + rules: + - aide_build_database + - package_aide_installed + status: automated + - id: 6.3.2 + title: Ensure filesystem integrity is regularly checked (Automated) + levels: + - l1_server + - l1_workstation + rules: + - aide_periodic_checking_systemd_timer + status: automated + - id: 7.1.1 + title: Ensure access to /etc/passwd is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_passwd + - file_owner_etc_passwd + - file_permissions_etc_passwd + status: automated + - id: 7.1.2 + title: Ensure access to /etc/passwd- is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_backup_etc_passwd + - file_owner_backup_etc_passwd + - file_permissions_backup_etc_passwd + status: automated + - id: 7.1.3 + title: Ensure access to /etc/group is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_group + - file_owner_etc_group + - file_permissions_etc_group + status: automated + - id: 7.1.4 + title: Ensure access to /etc/group- is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_backup_etc_group + - file_owner_backup_etc_group + - file_permissions_backup_etc_group + status: automated + - id: 7.1.5 + title: Ensure access to /etc/shadow is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_shadow + - file_owner_etc_shadow + - file_permissions_etc_shadow + status: automated + - id: 7.1.6 + title: Ensure access to /etc/shadow- is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_backup_etc_shadow + - file_owner_backup_etc_shadow + - file_permissions_backup_etc_shadow + status: automated + - id: 7.1.7 + title: Ensure access to /etc/gshadow is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_etc_gshadow + - file_owner_etc_gshadow + - file_permissions_etc_gshadow + status: automated + - id: 7.1.8 + title: Ensure access to /etc/gshadow- is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_groupowner_backup_etc_gshadow + - file_owner_backup_etc_gshadow + - file_permissions_backup_etc_gshadow + status: automated + - id: 7.1.9 + title: Ensure access to /etc/shells is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_owner_etc_shells + - file_groupowner_etc_shells + - file_permissions_etc_shells + status: automated + - id: 7.1.10 + title: Ensure access to /etc/security/opasswd is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_owner_etc_security_opasswd + - file_groupowner_etc_security_opasswd + - file_permissions_etc_security_opasswd + - file_owner_etc_security_opasswd_old + - file_groupowner_etc_security_opasswd_old + - file_permissions_etc_security_opasswd_old + status: automated + - id: 7.1.11 + title: Ensure world writable files and directories are secured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - file_permissions_unauthorized_world_writable + status: automated + - id: 7.1.12 + title: Ensure no files or directories without an owner and a group exist (Automated) + levels: *l1_both + rules: + - no_files_unowned_by_user + - file_permissions_ungroupowned + status: automated + - id: 7.1.13 + title: Ensure SUID and SGID files are reviewed (Manual) + levels: + - l1_server + - l1_workstation + status: manual + - id: 7.2.1 + title: Ensure accounts in /etc/passwd use shadowed passwords (Automated) + levels: + - l1_server + - l1_workstation + rules: + - accounts_password_all_shadowed + status: automated + - id: 7.2.2 + title: Ensure /etc/shadow password fields are not empty (Automated) + levels: + - l1_server + - l1_workstation + rules: + - no_empty_passwords_etc_shadow + status: automated + - id: 7.2.3 + title: Ensure all groups in /etc/passwd exist in /etc/group (Automated) + levels: + - l1_server + - l1_workstation + rules: + - gid_passwd_group_same + status: automated + - id: 7.2.4 + title: Ensure shadow group is empty (Automated) + levels: + - l1_server + - l1_workstation + rules: + - ensure_shadow_group_empty + status: automated + - id: 7.2.5 + title: Ensure no duplicate UIDs exist (Automated) + levels: + - l1_server + - l1_workstation + rules: + - account_unique_id + status: automated + - id: 7.2.6 + title: Ensure no duplicate GIDs exist (Automated) + levels: + - l1_server + - l1_workstation + rules: + - group_unique_id + status: automated + - id: 7.2.7 + title: Ensure no duplicate user names exist (Automated) + levels: + - l1_server + - l1_workstation + rules: + - account_unique_name + status: automated + - id: 7.2.8 + title: Ensure no duplicate group names exist (Automated) + levels: + - l1_server + - l1_workstation + rules: + - group_unique_name + status: automated + - id: 7.2.9 + title: Ensure local interactive user home directories are configured (Automated) + levels: *l1_both + rules: + - accounts_user_interactive_home_directory_exists + - file_groupownership_home_directories + - file_ownership_home_directories + - file_permissions_home_directories + status: automated + - id: 7.2.10 + title: Ensure local interactive user dot files access is configured (Automated) + levels: + - l1_server + - l1_workstation + rules: + - no_rsh_trust_files + - no_forward_files + - no_netrc_files + - accounts_user_dot_user_ownership + - accounts_user_dot_group_ownership + - var_user_initialization_files_regex=all_dotfiles + - file_permission_user_init_files + - file_permission_user_bash_history + status: automated + - id: 7.2.11 + title: Ensure local interactive user dot directories access is configured (Automated) + levels: *l1_both + rules: + - accounts_user_dot_dirs_permissions + - accounts_user_dot_dirs_ownership + - accounts_user_dot_dirs_groupownership + status: automated diff --git a/products/ubuntu2604/profiles/cis_level1_server.profile b/products/ubuntu2604/profiles/cis_level1_server.profile new file mode 100644 index 000000000000..02b99c7b3af4 --- /dev/null +++ b/products/ubuntu2604/profiles/cis_level1_server.profile @@ -0,0 +1,24 @@ +--- +documentation_complete: true + +metadata: + version: 1.0.0 + SMEs: + - mpurg + - dodys + - alanmcanonical + +reference: https://www.cisecurity.org/benchmark/ubuntu_linux + +title: 'DRAFT - CIS Ubuntu Linux 26.04 LTS Benchmark for Level 1 - Server' + +description: |- + This draft profile defines a baseline that aligns to the "Level 1 - Server" + configuration from the Center for Internet Security® + Ubuntu Linux 26.04 LTS Benchmark™, v1.0.0, released 2026-08-26. + + This profile includes Center for Internet Security® + Ubuntu Linux 26.04 LTS Benchmark™ content. + +selections: + - cis_ubuntu2604:all:l1_server diff --git a/products/ubuntu2604/profiles/cis_level1_workstation.profile b/products/ubuntu2604/profiles/cis_level1_workstation.profile new file mode 100644 index 000000000000..ed0652c04599 --- /dev/null +++ b/products/ubuntu2604/profiles/cis_level1_workstation.profile @@ -0,0 +1,24 @@ +--- +documentation_complete: true + +metadata: + version: 1.0.0 + SMEs: + - mpurg + - dodys + - alanmcanonical + +reference: https://www.cisecurity.org/benchmark/ubuntu_linux + +title: 'DRAFT - CIS Ubuntu Linux 26.04 LTS Benchmark for Level 1 - Workstation' + +description: |- + This draft profile defines a baseline that aligns to the "Level 1 - Workstation" + configuration from the Center for Internet Security® + Ubuntu Linux 26.04 LTS Benchmark™, v1.0.0, released 2026-08-26. + + This profile includes Center for Internet Security® + Ubuntu Linux 26.04 LTS Benchmark™ content. + +selections: + - cis_ubuntu2604:all:l1_workstation diff --git a/shared/templates/sysctl/ansible.template b/shared/templates/sysctl/ansible.template index 76b97a10aaf0..213a81c8573d 100644 --- a/shared/templates/sysctl/ansible.template +++ b/shared/templates/sysctl/ansible.template @@ -22,7 +22,7 @@ - "/run/sysctl.d/" - "/usr/local/lib/sysctl.d/" {{% endif %}} -{{% if product not in ["fedora", "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "sle12", "sle15", "sle16", "slmicro5", "slmicro6", "ubuntu2204", "ubuntu2404"] %}} +{{% if product not in ["fedora", "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "sle12", "sle15", "sle16", "slmicro5", "slmicro6", "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} - "/usr/lib/sysctl.d/" {{% endif %}} @@ -54,7 +54,7 @@ loop: '{{ find_all_values.stdout_lines }}' when: find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines | length > find_correct_value.stdout_lines | length -{{% if product in [ "ubuntu2204", "ubuntu2404"] %}} +{{% if product in [ "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} - name: {{{ rule_title }}} - Comment out any occurrences of {{{ SYSCTLVAR }}} from /etc/ufw/sysctl.conf ansible.builtin.replace: path: "/etc/ufw/sysctl.conf" diff --git a/shared/templates/sysctl/bash.template b/shared/templates/sysctl/bash.template index fe6068fb8bd1..c1f40efb57b0 100644 --- a/shared/templates/sysctl/bash.template +++ b/shared/templates/sysctl/bash.template @@ -7,7 +7,7 @@ # Comment out any occurrences of {{{ SYSCTLVAR }}} from /etc/sysctl.d/*.conf files {{% if product in [ "sle12", "sle15", "slmicro5", "slmicro6"] %}} for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf /lib/sysctl.d/*.conf; do -{{% elif product in [ "ubuntu2204", "ubuntu2404"] %}} +{{% elif product in [ "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf /etc/ufw/sysctl.conf; do {{% elif product not in [ "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10"] %}} for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf /usr/lib/sysctl.d/*.conf; do diff --git a/shared/templates/sysctl/oval.template b/shared/templates/sysctl/oval.template index 916a62b97d9a..98ca932c003d 100644 --- a/shared/templates/sysctl/oval.template +++ b/shared/templates/sysctl/oval.template @@ -180,7 +180,7 @@ object_static_etc_sysctls_{{{ rule_id }}} -{{% if product not in [ "almalinux8", "almalinux9", "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "ubuntu2204", "ubuntu2404"] %}} +{{% if product not in [ "almalinux8", "almalinux9", "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} object_static_lib_sysctld_{{{ rule_id }}} {{% endif %}} @@ -231,7 +231,7 @@ ^.*\.conf$ {{{ sysctl_match() }}} -{{% if product not in [ "ol7", "ol8", "ol9", "rhcos4", "ubuntu2204", "ubuntu2404"] or 'rhel' in product or 'almalinux' in product %}} +{{% if product not in [ "ol7", "ol8", "ol9", "rhcos4", "ubuntu2204", "ubuntu2404", "ubuntu2604"] or 'rhel' in product or 'almalinux' in product %}} /lib/sysctl.d ^.*\.conf$ diff --git a/shared/templates/sysctl/sce-bash.template b/shared/templates/sysctl/sce-bash.template index 72fa4ff8289c..ca625c59afb2 100644 --- a/shared/templates/sysctl/sce-bash.template +++ b/shared/templates/sysctl/sce-bash.template @@ -4,13 +4,13 @@ # check-export = sysctl_{{{ SYSCTLID }}}_value=sysctl_{{{ SYSCTLID }}}_value {{% endif %}} -{{% if product in [ "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "ubuntu2204", "ubuntu2404"] %}} +{{% if product in [ "ol7", "ol8", "ol9", "rhcos4", "rhel8", "rhel9", "rhel10", "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} FILES_NOT_MANAGED_BY_PACKAGES=("/etc/sysctl.conf" "/etc/sysctl.d/*.conf" "/usr/local/lib/sysctl.d/*.conf" "/run/sysctl.d/*.conf") {{% else %}} FILES_NOT_MANAGED_BY_PACKAGES=("/etc/sysctl.conf" "/etc/sysctl.d/*.conf" "/lib/sysctl.d/*.conf" "/usr/local/lib/sysctl.d/*.conf" "/run/sysctl.d/*.conf") {{% endif %}} FILES_MANAGED_BY_PACKAGES=("/usr/lib/sysctl.d/*.conf") -{{% if product in [ "ubuntu2204", "ubuntu2404"] %}} +{{% if product in [ "ubuntu2204", "ubuntu2404", "ubuntu2604"] %}} FILES_NOT_MANAGED_BY_PACKAGES+=("/etc/ufw/sysctl.conf") {{% endif %}}