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
+
+
+
+
+
+
+
+ /*.sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ^[\s]*(?:server|pool)[\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 %}}