diff --git a/bin/hardening/chrony_authorized_server.sh b/bin/hardening/chrony_authorized_server.sh new file mode 100755 index 00000000..3202d63a --- /dev/null +++ b/bin/hardening/chrony_authorized_server.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +# run-shellcheck +# +# CIS Debian Hardening +# + +# +# Ensure chrony is configured with authorized timeserver (Automated) +# + +set -e # One error, it's over +set -u # One variable unset, it's over + +# shellcheck disable=2034 +HARDENING_LEVEL=2 +# shellcheck disable=2034 +DESCRIPTION="Ensure chrony is configured with authorized timeserver." + +PACKAGE='chrony' +SOURCES_DIR='/etc/chrony/sources.d' +SOURCES_FILE="$SOURCES_DIR/authorized.sources" +MAIN_CONF='/etc/chrony/chrony.conf' + +# Configurable via create_config +CHRONY_TIME_SOURCES='' + +# Global state (0=success, 1=failure) +CHRONY_AUTH_PKG_INSTALLED=1 +CHRONY_AUTH_CONFIG_OK=1 + +# Check function to populate state +chrony_auth_check() { + CHRONY_AUTH_PKG_INSTALLED=1 + CHRONY_AUTH_CONFIG_OK=1 + + is_pkg_installed "$PACKAGE" + if [ "$FNRET" != 0 ]; then + # Package not installed (1=not installed/failure) + CHRONY_AUTH_PKG_INSTALLED=1 + return + fi + # Package is installed (0=installed/success) + CHRONY_AUTH_PKG_INSTALLED=0 + + # Check if sources.d directory is included in main config + if [ -f "$MAIN_CONF" ]; then + does_pattern_exist_in_file "$MAIN_CONF" "^sourcedir.*$SOURCES_DIR" + if [ "$FNRET" != 0 ]; then + # sourcedir not configured (1=not OK/failure) + CHRONY_AUTH_CONFIG_OK=1 + return + fi + else + # Main config not found (1=not OK/failure) + CHRONY_AUTH_CONFIG_OK=1 + return + fi + + # Check sources file + if [ ! -f "$SOURCES_FILE" ]; then + # Sources file doesn't exist (1=not OK/failure) + CHRONY_AUTH_CONFIG_OK=1 + return + fi + + if [ -z "$CHRONY_TIME_SOURCES" ]; then + # Cannot verify without configured sources (1=not OK/failure) + CHRONY_AUTH_CONFIG_OK=1 + return + fi + + # Check if configured sources are present + does_pattern_exist_in_file "$SOURCES_FILE" "$CHRONY_TIME_SOURCES" + if [ "$FNRET" != 0 ]; then + # Sources not found (1=not OK/failure) + CHRONY_AUTH_CONFIG_OK=1 + return + fi + + # All checks passed (0=OK/success) + CHRONY_AUTH_CONFIG_OK=0 +} + +# This function will be called if the script status is on enabled / audit mode +audit() { + chrony_auth_check + + if [ "$CHRONY_AUTH_PKG_INSTALLED" -ne 0 ]; then + crit "$PACKAGE is not installed" + return + fi + ok "$PACKAGE is installed" + + if [ "$CHRONY_AUTH_CONFIG_OK" -ne 0 ]; then + crit "Chrony configuration is not correct" + else + ok "Time sources correctly configured" + fi +} + +# This function will be called if the script status is on enabled mode +apply() { + if [ "$CHRONY_AUTH_PKG_INSTALLED" -ne 0 ]; then + crit "$PACKAGE is not installed, cannot apply" + return + fi + + if [ "$CHRONY_AUTH_CONFIG_OK" -ne 0 ]; then + # Ensure sourcedir directive exists in main config + info "Ensuring sourcedir is configured in $MAIN_CONF" + if [ -f "$MAIN_CONF" ]; then + does_pattern_exist_in_file "$MAIN_CONF" "^sourcedir.*$SOURCES_DIR" + if [ "$FNRET" != 0 ]; then + backup_file "$MAIN_CONF" + add_end_of_file "$MAIN_CONF" "sourcedir $SOURCES_DIR" + fi + fi + + # Create sources directory and file + info "Creating chrony sources configuration" + mkdir -p "$SOURCES_DIR" + + if [ -n "$CHRONY_TIME_SOURCES" ]; then + echo "$CHRONY_TIME_SOURCES" >"$SOURCES_FILE" + fi + + # Restart chronyd service + info "Restarting chronyd service" + is_systemctl_running + if [ "$FNRET" = 0 ]; then + systemctl restart chronyd + else + info "Systemd is not running, skipping service restart" + fi + else + ok "Chrony configuration already correct" + fi +} + +# This function will check config parameters required +check_config() { + if [ -z "$CHRONY_TIME_SOURCES" ]; then + crit "CHRONY_TIME_SOURCES is not configured" + exit 128 + fi +} + +# This function will create the config file for this check with default values +create_config() { + cat </dev/null; then + ok "SSH configuration is valid" + info "Reloading SSH service" + is_systemctl_running + if [ "$FNRET" = 0 ]; then + systemctl reload sshd || systemctl reload ssh || /etc/init.d/ssh reload + else + /etc/init.d/ssh reload + fi + else + crit "SSH configuration test failed, not reloading" + fi + else + ok "$OPTION already correctly configured" + fi +} + +# This function will check config parameters required +check_config() { + : +} + +# Source Root Dir Parameter +if [ -r /etc/default/cis-hardening ]; then + # shellcheck source=../../debian/default + . /etc/default/cis-hardening +fi +if [ -z "${CIS_LIB_DIR}" ]; then + echo "There is no /etc/default/cis-hardening file nor cis-hardening directory in current environment." + echo "Cannot source CIS_LIB_DIR variable, aborting." + exit 128 +fi + +# Main function, will call the proper functions given the configuration (audit, enabled, disabled) +if [ -r "${CIS_LIB_DIR}"/main.sh ]; then + # shellcheck source=../../lib/main.sh + . "${CIS_LIB_DIR}"/main.sh +else + echo "Cannot find main.sh, have you correctly defined your root directory? Current value is ${CIS_LIB_DIR} in /etc/default/cis-hardening" + exit 128 +fi diff --git a/bin/hardening/timesyncd_authorized_server.sh b/bin/hardening/timesyncd_authorized_server.sh new file mode 100755 index 00000000..c3fee865 --- /dev/null +++ b/bin/hardening/timesyncd_authorized_server.sh @@ -0,0 +1,185 @@ +#!/bin/bash + +# run-shellcheck +# +# CIS Debian Hardening +# + +# +# Ensure systemd-timesyncd configured with authorized timeserver (Automated) +# + +set -e # One error, it's over +set -u # One variable unset, it's over + +# shellcheck disable=2034 +HARDENING_LEVEL=2 +# shellcheck disable=2034 +DESCRIPTION="Ensure systemd-timesyncd configured with authorized timeserver." + +PACKAGE='systemd-timesyncd' +CONFIG_DIR='/etc/systemd/timesyncd.conf.d' +CONFIG_FILE="$CONFIG_DIR/50-timesyncd.conf" + +# Configurable via create_config +NTP_SERVERS='' +FALLBACK_NTP_SERVERS='' + +# Global state (0=success, 1=failure) +TIMESYNCD_AUTH_PKG_INSTALLED=1 +TIMESYNCD_AUTH_CONFIG_OK=1 + +# Check function to populate state +timesyncd_auth_check() { + TIMESYNCD_AUTH_PKG_INSTALLED=1 + TIMESYNCD_AUTH_CONFIG_OK=1 + + is_pkg_installed "$PACKAGE" + if [ "$FNRET" != 0 ]; then + # Package not installed (1=not installed/failure) + TIMESYNCD_AUTH_PKG_INSTALLED=1 + return + fi + # Package is installed (0=installed/success) + TIMESYNCD_AUTH_PKG_INSTALLED=0 + + if [ ! -f "$CONFIG_FILE" ]; then + # Config not OK (1=not OK/failure) + return + fi + + # Check NTP servers if configured + if [ -n "$NTP_SERVERS" ]; then + does_pattern_exist_in_file "$CONFIG_FILE" "^NTP=" + if [ "$FNRET" != 0 ]; then + # NTP line not found (1=not OK/failure) + return + fi + # Verify the configured servers + does_pattern_exist_in_file "$CONFIG_FILE" "^NTP=.*$NTP_SERVERS" + if [ "$FNRET" != 0 ]; then + # Configured servers not found (1=not OK/failure) + return + fi + fi + + # Check Fallback NTP servers if configured + if [ -n "$FALLBACK_NTP_SERVERS" ]; then + does_pattern_exist_in_file "$CONFIG_FILE" "^FallbackNTP=" + if [ "$FNRET" != 0 ]; then + # FallbackNTP line not found (1=not OK/failure) + return + fi + # Verify the configured servers + does_pattern_exist_in_file "$CONFIG_FILE" "^FallbackNTP=.*$FALLBACK_NTP_SERVERS" + if [ "$FNRET" != 0 ]; then + # Configured fallback servers not found (1=not OK/failure) + return + fi + fi + + # All checks passed (0=OK/success) + TIMESYNCD_AUTH_CONFIG_OK=0 +} + +# This function will be called if the script status is on enabled / audit mode +audit() { + timesyncd_auth_check + + if [ "$TIMESYNCD_AUTH_PKG_INSTALLED" -ne 0 ]; then + crit "$PACKAGE is not installed" + return + fi + ok "$PACKAGE is installed" + + if [ "$TIMESYNCD_AUTH_CONFIG_OK" -ne 0 ]; then + crit "Timesyncd configuration is not correct" + else + ok "Timesyncd is properly configured" + fi +} + +# This function will be called if the script status is on enabled mode +apply() { + if [ "$TIMESYNCD_AUTH_PKG_INSTALLED" -ne 0 ]; then + crit "$PACKAGE is not installed, cannot apply" + return + fi + + if [ "$TIMESYNCD_AUTH_CONFIG_OK" -ne 0 ]; then + info "Creating timesyncd configuration" + mkdir -p "$CONFIG_DIR" + + # Remove existing keys to avoid duplicates + if [ -f "$CONFIG_FILE" ]; then + backup_file "$CONFIG_FILE" + delete_line_in_file "$CONFIG_FILE" "^NTP=" + delete_line_in_file "$CONFIG_FILE" "^FallbackNTP=" + else + # Create new file with [Time] section + cat >"$CONFIG_FILE" <>"${CIS_CONF_DIR}/conf.d/${script}.cfg" + + describe Tests purposely failing + rm -rf /etc/chrony/sources.d/authorized.sources + register_test retvalshouldbe 1 + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "Time sources correctly configured" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Cleanup + apt-get purge -y chrony || true + rm -rf /etc/chrony/sources.d + # Restore original configuration + if [ -d /tmp/chrony_sources.d.backup ]; then + mkdir -p /etc/chrony + mv /tmp/chrony_sources.d.backup /etc/chrony/sources.d + fi +} diff --git a/tests/hardening/dev_shm_nodev.sh b/tests/hardening/dev_shm_nodev.sh new file mode 100644 index 00000000..02b405e0 --- /dev/null +++ b/tests/hardening/dev_shm_nodev.sh @@ -0,0 +1,43 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + # Skip test if running in a container (mount operations not allowed) + if [ -f "/.dockerenv" ] || grep -q 'docker\|lxc\|containerd' /proc/1/cgroup 2>/dev/null; then + skip "Test requires mount operations, skipping in container environment" + return + fi + + describe Running on blank host + register_test retvalshouldbe 0 + # shellcheck disable=2154 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Backup original fstab + cp /etc/fstab /tmp/fstab.backup + + # Try to break it - remove nodev from /dev/shm if present + describe Tests purposely failing + if grep -qE '\s/dev/shm\s' /etc/fstab; then + # Remove nodev option from fstab + sed -i 's/,nodev//g; s/nodev,//g; s/nodev//g' /etc/fstab + mount -o remount /dev/shm + register_test retvalshouldbe 1 + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + else + skip "/dev/shm not in fstab, skipping noncompliant test" + fi + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "has nodev in fstab" + register_test contain "mounted with nodev" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Restore original fstab + mv /tmp/fstab.backup /etc/fstab + mount -o remount /dev/shm +} diff --git a/tests/hardening/dev_shm_noexec.sh b/tests/hardening/dev_shm_noexec.sh new file mode 100644 index 00000000..953ba6bd --- /dev/null +++ b/tests/hardening/dev_shm_noexec.sh @@ -0,0 +1,43 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + # Skip test if running in a container (mount operations not allowed) + if [ -f "/.dockerenv" ] || grep -q 'docker\|lxc\|containerd' /proc/1/cgroup 2>/dev/null; then + skip "Test requires mount operations, skipping in container environment" + return + fi + + describe Running on blank host + register_test retvalshouldbe 0 + # shellcheck disable=2154 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Backup original fstab + cp /etc/fstab /tmp/fstab.backup + + # Try to break it - remove noexec from /dev/shm if present + describe Tests purposely failing + if grep -qE '\s/dev/shm\s' /etc/fstab; then + # Remove noexec option from fstab + sed -i 's/,noexec//g; s/noexec,//g; s/noexec//g' /etc/fstab + mount -o remount /dev/shm + register_test retvalshouldbe 1 + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + else + skip "/dev/shm not in fstab, skipping noncompliant test" + fi + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "has noexec in fstab" + register_test contain "mounted with noexec" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Restore original fstab + mv /tmp/fstab.backup /etc/fstab + mount -o remount /dev/shm +} diff --git a/tests/hardening/dev_shm_nosuid.sh b/tests/hardening/dev_shm_nosuid.sh new file mode 100644 index 00000000..bfeabc10 --- /dev/null +++ b/tests/hardening/dev_shm_nosuid.sh @@ -0,0 +1,43 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + # Skip test if running in a container (mount operations not allowed) + if [ -f "/.dockerenv" ] || grep -q 'docker\|lxc\|containerd' /proc/1/cgroup 2>/dev/null; then + skip "Test requires mount operations, skipping in container environment" + return + fi + + describe Running on blank host + register_test retvalshouldbe 0 + # shellcheck disable=2154 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Backup original fstab + cp /etc/fstab /tmp/fstab.backup + + # Try to break it - remove nosuid from /dev/shm if present + describe Tests purposely failing + if grep -qE '\s/dev/shm\s' /etc/fstab; then + # Remove nosuid option from fstab + sed -i 's/,nosuid//g; s/nosuid,//g; s/nosuid//g' /etc/fstab + mount -o remount /dev/shm + register_test retvalshouldbe 1 + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + else + skip "/dev/shm not in fstab, skipping noncompliant test" + fi + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "has nosuid in fstab" + register_test contain "mounted with nosuid" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Restore original fstab + mv /tmp/fstab.backup /etc/fstab + mount -o remount /dev/shm +} diff --git a/tests/hardening/sshd_disable_gssapi.sh b/tests/hardening/sshd_disable_gssapi.sh new file mode 100644 index 00000000..dff78ba4 --- /dev/null +++ b/tests/hardening/sshd_disable_gssapi.sh @@ -0,0 +1,43 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + describe Test without openssh-server installed + apt-get purge -y openssh-server >/dev/null 2>&1 || true + register_test retvalshouldbe 0 + register_test contain "not installed" + # shellcheck disable=2154 + run no_ssh "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + + describe Running on blank host + register_test retvalshouldbe 0 + dismiss_count_for_test + # shellcheck disable=2154 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Tests purposely failing + # Enable GSSAPIAuthentication + echo "GSSAPIAuthentication yes" >>/etc/ssh/sshd_config + register_test retvalshouldbe 1 + register_test contain "not properly set" + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "is set to no" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Cleanup + sed -i '/^GSSAPIAuthentication/d' /etc/ssh/sshd_config + apt-get purge -y openssh-server >/dev/null 2>&1 || true +} diff --git a/tests/hardening/sshd_idle_timeout.sh b/tests/hardening/sshd_idle_timeout.sh index e5dd39ec..1af38bf5 100644 --- a/tests/hardening/sshd_idle_timeout.sh +++ b/tests/hardening/sshd_idle_timeout.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # run-shellcheck test_audit() { + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + describe Running on blank host register_test retvalshouldbe 1 register_test contain "openssh-server is installed" diff --git a/tests/hardening/sshd_limit_access.sh b/tests/hardening/sshd_limit_access.sh index 475d9920..5e046c87 100644 --- a/tests/hardening/sshd_limit_access.sh +++ b/tests/hardening/sshd_limit_access.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # run-shellcheck test_audit() { + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + describe Running on blank host register_test retvalshouldbe 1 register_test contain "openssh-server is installed" diff --git a/tests/hardening/sshd_login_grace_time.sh b/tests/hardening/sshd_login_grace_time.sh index da832bf2..ea47a207 100644 --- a/tests/hardening/sshd_login_grace_time.sh +++ b/tests/hardening/sshd_login_grace_time.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # run-shellcheck test_audit() { + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + describe Running on blank host register_test retvalshouldbe 1 register_test contain "openssh-server is installed" diff --git a/tests/hardening/sshd_loglevel.sh b/tests/hardening/sshd_loglevel.sh index ac1f6d83..be90f95b 100644 --- a/tests/hardening/sshd_loglevel.sh +++ b/tests/hardening/sshd_loglevel.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # run-shellcheck test_audit() { + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + describe Running on blank host register_test retvalshouldbe 0 dismiss_count_for_test diff --git a/tests/hardening/sshd_maxauthtries.sh b/tests/hardening/sshd_maxauthtries.sh index 812f771e..bd97d8b3 100644 --- a/tests/hardening/sshd_maxauthtries.sh +++ b/tests/hardening/sshd_maxauthtries.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # run-shellcheck test_audit() { + describe Installing openssh-server for tests + apt-get update >/dev/null 2>&1 || true + apt-get install -y openssh-server >/dev/null 2>&1 || { + skip "Cannot install openssh-server, skipping tests" + return + } + describe Running on blank host register_test retvalshouldbe 1 register_test contain "openssh-server is installed" diff --git a/tests/hardening/timesyncd_authorized_server.sh b/tests/hardening/timesyncd_authorized_server.sh new file mode 100644 index 00000000..112118b8 --- /dev/null +++ b/tests/hardening/timesyncd_authorized_server.sh @@ -0,0 +1,44 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + describe Test without systemd-timesyncd installed + apt-get purge -y systemd-timesyncd || true + register_test retvalshouldbe 1 + register_test contain "not installed" + # shellcheck disable=2154 + run no_timesyncd "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Installing systemd-timesyncd + apt-get install -y systemd-timesyncd || true + + # Backup original configuration + if [ -d /etc/systemd/timesyncd.conf.d ]; then + cp -r /etc/systemd/timesyncd.conf.d /tmp/timesyncd.conf.d.backup + fi + + describe Configure the check + echo "NTP_SERVERS='time.example.com'" >>"${CIS_CONF_DIR}/conf.d/${script}.cfg" + echo "FALLBACK_NTP_SERVERS='pool.ntp.org'" >>"${CIS_CONF_DIR}/conf.d/${script}.cfg" + + describe Tests purposely failing + rm -rf /etc/systemd/timesyncd.conf.d/50-timesyncd.conf + register_test retvalshouldbe 1 + run noncompliant "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + register_test contain "Timesyncd is properly configured" + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # Cleanup + rm -rf /etc/systemd/timesyncd.conf.d + # Restore original configuration + if [ -d /tmp/timesyncd.conf.d.backup ]; then + mkdir -p /etc/systemd + mv /tmp/timesyncd.conf.d.backup /etc/systemd/timesyncd.conf.d + fi +}