diff --git a/Earthfile b/Earthfile index 060eb183..aa5e3c73 100644 --- a/Earthfile +++ b/Earthfile @@ -4,7 +4,8 @@ ARG TARGETARCH # Default image repositories used in the builds. ARG SPECTRO_PUB_REPO=us-docker.pkg.dev/palette-images -ARG SPECTRO_THIRD_PARTY_IMAGE=us-east1-docker.pkg.dev/spectro-images/third-party/spectro-third-party:4.6 +ARG BUILDER_3RDPARTY_VERSION=4.8 +ARG SPECTRO_THIRD_PARTY_IMAGE=us-docker.pkg.dev/palette-images/third-party/spectro-third-party:$BUILDER_3RDPARTY_VERSION ARG ALPINE_TAG=3.20 ARG ALPINE_IMG=$SPECTRO_PUB_REPO/edge/canvos/alpine:$ALPINE_TAG FROM $ALPINE_IMG diff --git a/README.md b/README.md index 5bb8571c..08e4a7b0 100644 --- a/README.md +++ b/README.md @@ -236,8 +236,9 @@ cp .arg.template .arg | HTTPS_PROXY | URL of the HTTPS Proxy server to be used if needed (Optional) | string | | | NO_PROXY | URLS that should be excluded from proxying (Optional) | string | | | UPDATE_KERNEL | Determines whether to upgrade the Kernel version to the latest from the upstream OS provider | boolean | `false` | -| DISABLE_SELINUX | Disable selinux in the operating system. Some applications (like Kubevirt) do not like selinux | boolean | `true` | -| CLUSTERCONFIG | Path of the cluster config | string | | +| DISABLE_SELINUX | Disable selinux in the operating system. Some applications (like Kubevirt) do not like selinux | boolean | `true` | +| CIS_HARDENING | Enable CIS Benchmark hardening for the image. When set to `true`, applies CIS Benchmark security controls during the build. | boolean | `false` | +| CLUSTERCONFIG | Path of the cluster config | string | | | IS_UKI | Build UKI(Trusted boot) images | boolean | `false` | | UKI_BRING_YOUR_OWN_KEYS | Bring your own public/private key pairs if this is set to true. Otherwise, CanvOS will generate the key pair. | boolean | `false` | | INCLUDE_MS_SECUREBOOT_KEYS | Include Microsoft 3rd Party UEFI CA certificate in generated keys | boolean | `true` | diff --git a/cis-harden/harden.sh b/cis-harden/harden.sh index f71aa290..db21f7c9 100755 --- a/cis-harden/harden.sh +++ b/cis-harden/harden.sh @@ -105,18 +105,35 @@ get_os() { ########################################################################## upgrade_packages() { if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then + export DEBIAN_FRONTEND=noninteractive apt-get update apt-get -y upgrade check_error $? "Failed upgrading packages" 1 - apt-get install -y auditd apparmor-utils libpam-pwquality - if $? -ne 0 ; then + # CIS 1.3.1 - Install AIDE for file integrity monitoring + # CIS 5.5.1 - Install vlock for screen locking + DEBIAN_FRONTEND=noninteractive apt-get install -y auditd apparmor apparmor-utils libpam-pwquality aide vlock + if [[ $? -ne 0 ]]; then echo 'deb http://archive.ubuntu.com/ubuntu focal main restricted' > /etc/apt/sources.list.d/repotmp.list apt-get update - apt-get install -y auditd apparmor-utils libpam-pwquality + DEBIAN_FRONTEND=noninteractive apt-get install -y auditd apparmor apparmor-utils libpam-pwquality aide vlock check_error $? "Failed installing audit packages" 1 rm -f /etc/apt/sources.list.d/repotmp.list apt-get update fi + + # CIS 1.3.2 - Initialize AIDE database + echo "Initializing AIDE database..." + if [[ -x /usr/sbin/aideinit ]]; then + /usr/sbin/aideinit -y -f 2>/dev/null || true + fi + + # CIS 1.7.1.2 - Enable AppArmor + echo "Enabling AppArmor..." + systemctl enable apparmor 2>/dev/null || true + + # CIS 4.2.1.2 - Enable rsyslog + echo "Enabling rsyslog..." + systemctl enable rsyslog 2>/dev/null || true fi if [[ ${OS_FLAVOUR} == "centos" ]]; then @@ -191,6 +208,14 @@ harden_sysctl() { update_config_files 'net.ipv6.conf.all.accept_ra' 'net.ipv6.conf.all.accept_ra=0' ${config_file} update_config_files 'net.ipv6.conf.default.accept_ra' 'net.ipv6.conf.default.accept_ra=0' ${config_file} + # CIS Level 2 - Additional kernel hardening (Ubuntu only) + if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then + update_config_files 'kernel.yama.ptrace_scope' 'kernel.yama.ptrace_scope=1' ${config_file} + update_config_files 'kernel.dmesg_restrict' 'kernel.dmesg_restrict=1' ${config_file} + update_config_files 'kernel.kptr_restrict' 'kernel.kptr_restrict=2' ${config_file} + update_config_files 'kernel.perf_event_paranoid' 'kernel.perf_event_paranoid=3' ${config_file} + fi + # To restrict core dumps config_file='/etc/security/limits.conf' echo "" >> ${config_file} @@ -212,7 +237,7 @@ harden_ssh() { chmod og-rwx ${config_file} echo "" >> ${config_file} - update_config_files 'Protocol ' 'Protocol 2' ${config_file} + # Note: Protocol 2 is deprecated in OpenSSH 7.4+ (SSH1 removed), skip on modern systems update_config_files 'LogLevel ' 'LogLevel INFO' ${config_file} update_config_files 'PermitEmptyPasswords ' 'PermitEmptyPasswords no' ${config_file} update_config_files 'X11Forwarding ' 'X11Forwarding no' ${config_file} @@ -231,6 +256,16 @@ harden_ssh() { update_config_files 'MACs' 'MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256' ${config_file} update_config_files 'KexAlgorithms' 'KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256' ${config_file} + # CIS Level 2 - Additional SSH hardening + update_config_files 'GSSAPIAuthentication' 'GSSAPIAuthentication no' ${config_file} + update_config_files 'UsePAM' 'UsePAM yes' ${config_file} + update_config_files 'AllowTcpForwarding' 'AllowTcpForwarding no' ${config_file} + update_config_files 'TCPKeepAlive' 'TCPKeepAlive no' ${config_file} + update_config_files 'AllowAgentForwarding' 'AllowAgentForwarding no' ${config_file} + update_config_files 'DisableForwarding' 'DisableForwarding yes' ${config_file} + # CIS 5.2.6 - Ensure SSH access is configured with pubkey authentication + update_config_files 'PubkeyAuthentication' 'PubkeyAuthentication yes' ${config_file} + #############Shell timeout policy################## # Configuration lines to add to /etc/profile.d/timeout.sh @@ -346,12 +381,22 @@ harden_audit() { "-w /etc/apparmor/ -p wa -k MAC-policy" ) + # Note: /var/log/tallylog only exists on systems using pam_tally2 (Ubuntu <22.04) + # The rule is harmless on newer systems but won't match anything local content_logineventsrules=( "-w /var/log/faillog -p wa -k logins" "-w /var/log/lastlog -p wa -k logins" "-w /var/log/tallylog -p wa -k logins" ) + # CIS 4.1.3.* - Session initiation information audit rules + local file_path_sessionrules="/etc/audit/rules.d/50-session.rules" + local content_sessionrules=( + "-w /var/run/utmp -p wa -k session" + "-w /var/log/wtmp -p wa -k session" + "-w /var/log/btmp -p wa -k session" + ) + local content_DACrules=( "-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod" "-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod" @@ -433,6 +478,11 @@ harden_audit() { echo "$line" | sudo tee -a "$file_path_DACrules" >/dev/null done + # CIS 4.1.3.* - Create or append to the session rules + for line in "${content_sessionrules[@]}"; do + echo "$line" | sudo tee -a "$file_path_sessionrules" >/dev/null + done + # Verify if the files were created or appended successfully if [ -f "$file_path_timechange" ] && [ -f "$file_path_identityrules" ] && [ -f "$file_path_accessrules" ] && [ -f "$file_path_deleterules" ] && [ -f "$file_path_mountrules" ] && [ -f "$file_path_scoperules" ] && [ -f "$file_path_actionsrules" ] && [ -f "$file_path_modulesrules" ] && [ -f "$file_path_immutablerules" ] && [ -f "$file_path_networkrules" ] && [ -f "$file_path_MACrules" ] && [ -f "$file_path_logineventsrules" ] && [ -f "$file_path_DACrules" ]; then @@ -444,10 +494,35 @@ harden_audit() { # Define the desired value for max_log_file max_log_file_value=100 - # Set the max_log_file parameter in auditd.conf - sed -i "s/^max_log_file = 8/max_log_file = ${max_log_file_value}/" /etc/audit/auditd.conf - - echo "The max_log_file parameter has been set to ${max_log_file_value}." + # Configure audit log rotation in auditd.conf + echo "Configuring audit log rotation..." + + # Set max log file size to 100 MB + sed -i "s/^max_log_file = .*/max_log_file = ${max_log_file_value}/" /etc/audit/auditd.conf + + # Set max_log_file_action to ROTATE (rotate logs when max size reached) + sed -i "s/^max_log_file_action = .*/max_log_file_action = ROTATE/" /etc/audit/auditd.conf + + # Keep 10 rotated log files (10 x 100MB = 1GB total) + sed -i "s/^num_logs = .*/num_logs = 10/" /etc/audit/auditd.conf + + # Set space_left to 25% of partition (warning threshold) + sed -i "s/^space_left = .*/space_left = 250/" /etc/audit/auditd.conf + sed -i "s/^space_left_action = .*/space_left_action = SYSLOG/" /etc/audit/auditd.conf + + # Set admin_space_left to 10% (critical threshold - rotate aggressively) + sed -i "s/^admin_space_left = .*/admin_space_left = 100/" /etc/audit/auditd.conf + sed -i "s/^admin_space_left_action = .*/admin_space_left_action = ROTATE/" /etc/audit/auditd.conf + + # When disk is full, keep old logs (don't stop auditing) + sed -i "s/^disk_full_action = .*/disk_full_action = ROTATE/" /etc/audit/auditd.conf + sed -i "s/^disk_error_action = .*/disk_error_action = SYSLOG/" /etc/audit/auditd.conf + + echo "Audit log rotation configured:" + echo " - Max log file size: ${max_log_file_value} MB" + echo " - Number of rotated logs: 10 (total ~1GB)" + echo " - Rotation trigger: ROTATE on max size" + echo " - Space warnings enabled at 250 MB and 100 MB remaining" # Enable auditd service systemctl enable auditd @@ -516,7 +591,7 @@ harden_system() { echo "Error out if there are users with empty password" cat /etc/shadow |awk -F : '($2 == "" ){ exit 1}' - if $? -ne 0 ; then + if [[ $? -ne 0 ]]; then echo "Users present with empty password. Remove the user or set password for the users" exit 1 fi @@ -533,7 +608,7 @@ harden_system() { for each in ${cron_files}; do if [[ -e ${each} ]]; then stat -L -c "%a %u %g" "${each}" | grep -E ".00 0 0" - if $? -ne 0 ; then + if [[ $? -ne 0 ]]; then chown root:root "${each}" chmod og-rwx "${each}" fi @@ -586,15 +661,34 @@ remove_services() { if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then echo "Disable setrouble shoot service if enabled" - systemctl disable setroubleshoot + systemctl disable setroubleshoot 2>/dev/null || true echo "Removing legacy networking services" - systemctl disable xinetd - apt-get remove -y openbsd-inetd rsh-client rsh-redone-client nis talk telnet ldap-utils gdm3 - apt-get purge -y telnet vim vim-common vim-runtime vim-tiny + systemctl disable xinetd 2>/dev/null || true + apt-get remove -y openbsd-inetd rsh-client rsh-redone-client nis talk telnet ldap-utils gdm3 2>/dev/null || true + apt-get purge -y telnet vim vim-common vim-runtime vim-tiny 2>/dev/null || true echo "Removing X packages" - apt-get remove -y xserver-xorg* + apt-get remove -y xserver-xorg* 2>/dev/null || true + + # CIS Level 2 - Additional packages to remove + echo "Removing additional CIS Level 2 packages" + apt-get remove -y avahi-daemon cups rpcbind nfs-kernel-server vsftpd apache2 nginx samba squid snmpd 2>/dev/null || true + + # CIS Level 2 - Disable additional services + echo "Disabling additional CIS Level 2 services" + systemctl disable avahi-daemon 2>/dev/null || true + systemctl disable cups 2>/dev/null || true + systemctl disable rpcbind 2>/dev/null || true + systemctl disable nfs-server 2>/dev/null || true + systemctl disable bluetooth 2>/dev/null || true + systemctl disable apport 2>/dev/null || true + systemctl disable autofs 2>/dev/null || true + + # CIS 1.6.1 - Disable kdump service + echo "Disabling kdump service" + systemctl disable kdump-tools 2>/dev/null || true + systemctl mask kdump-tools 2>/dev/null || true fi if [[ ${OS_FLAVOUR} == "centos" ]] || [[ ${OS_FLAVOUR} == "rhel" ]]; then @@ -632,15 +726,56 @@ disable_modules() { echo "install tipc /bin/true" >> /etc/modprobe.d/tipc.conf echo "install cramfs /bin/false" > /etc/modprobe.d/cramfs.conf - echo "install freevxfs /bin/true" >> /etc/modprobe.d/freevxfs.conf - echo "install jffs2 /bin/true" >> /etc/modprobe.d/jffs2.conf - echo "install hfs /bin/true" >> /etc/modprobe.d/hfs.conf - echo "install hfsplus /bin/true" >> /etc/modprobe.d/hfsplus.conf + echo "install freevxfs /bin/true" > /etc/modprobe.d/freevxfs.conf + echo "install jffs2 /bin/true" > /etc/modprobe.d/jffs2.conf + echo "install hfs /bin/true" > /etc/modprobe.d/hfs.conf + echo "install hfsplus /bin/true" > /etc/modprobe.d/hfsplus.conf + + # CIS Level 2 - Additional filesystem modules to disable + echo "blacklist cramfs" >> /etc/modprobe.d/cramfs.conf + echo "blacklist freevxfs" >> /etc/modprobe.d/freevxfs.conf + echo "blacklist jffs2" >> /etc/modprobe.d/jffs2.conf + echo "blacklist hfs" >> /etc/modprobe.d/hfs.conf + echo "blacklist hfsplus" >> /etc/modprobe.d/hfsplus.conf + + # Needed for Kairos - do not disable squashfs and udf + #echo "install squashfs /bin/true" > /etc/modprobe.d/squashfs.conf + #echo "install udf /bin/true" > /etc/modprobe.d/udf.conf + #echo "install usb-storage /bin/false" > /etc/modprobe.d/usb_storage.conf + fi - # Needed for Kairos - #echo "install squashfs /bin/true" >> /etc/modprobe.d/squashfs.conf - #echo "install udf /bin/true" >> /etc/modprobe.d/udf.conf - #echo "install usb-storage /bin/false" >> /etc/modprobe.d/usb_storage.conf + return 0 +} + +########################################################################## +# CIS Level 2 - Journald Hardening (Ubuntu only) +########################################################################## +harden_journald() { + # Only apply to Ubuntu + if [[ ${OS_FLAVOUR} != "ubuntu" ]]; then + return 0 + fi + + echo "Configuring journald for CIS Level 2 compliance" + + local journald_conf="/etc/systemd/journald.conf" + + if [[ -f ${journald_conf} ]]; then + # Ensure journald is configured to compress large log files + if grep -q "^Compress=" ${journald_conf}; then + sed -i "s/^Compress=.*/Compress=yes/" ${journald_conf} + else + echo "Compress=yes" >> ${journald_conf} + fi + + # Ensure journald is not configured to send logs to rsyslog + if grep -q "^ForwardToSyslog=" ${journald_conf}; then + sed -i "s/^ForwardToSyslog=.*/ForwardToSyslog=no/" ${journald_conf} + else + echo "ForwardToSyslog=no" >> ${journald_conf} + fi + + echo "Journald configuration updated for CIS Level 2 compliance" fi return 0 @@ -808,47 +943,85 @@ harden_auth() { echo "File /etc/security/pwquality.conf not found." fi - # Configuration lines to add to /etc/pam.d/su - config_lines="auth required pam_wheel.so use_uid group=admin" + ##############Restrict su command access################## + if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then + # Ubuntu uses 'admin' or 'sudo' group + config_lines="auth required pam_wheel.so use_uid group=sudo" + elif [[ ${OS_FLAVOUR} == "centos" ]] || [[ ${OS_FLAVOUR} == "rhel" ]]; then + # RHEL/CentOS uses 'wheel' group + config_lines="auth required pam_wheel.so use_uid group=wheel" + else + config_lines="auth required pam_wheel.so use_uid" + fi # Add configuration lines to the top of the file - echo -e "$config_lines\n$(cat /etc/pam.d/su)" > /etc/pam.d/su - - echo "Configuration to ensure access to the su command is restricted have been made" + if [[ -f /etc/pam.d/su ]]; then + echo -e "$config_lines\n$(cat /etc/pam.d/su)" > /etc/pam.d/su + echo "Configuration to ensure access to the su command is restricted have been made" + fi ##############Password lockout policies################## + if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then + # Get Ubuntu version for compatibility checks + ubuntu_version="22" + if [[ -f /etc/os-release ]]; then + . /etc/os-release + ubuntu_version=$(echo "$VERSION_ID" | cut -d. -f1) + fi - # Backup the original file - cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak - - { - echo "auth required pam_faillock.so preauth audit silent deny=4 fail_interval=900 unlock_time=600" - echo "auth [success=1 default=ignore] pam_unix.so nullok" - echo "auth [default=die] pam_faillock.so authfail audit deny=4 fail_interval=900 unlock_time=600" - echo "auth sufficient pam_faillock.so authsucc audit deny=4 fail_interval=900 unlock_time=600" - echo "auth requisite pam_deny.so" - echo "auth required pam_permit.so" - } >> /etc/pam.d/common-auth - - # Backup the original file - cp /etc/pam.d/common-account /etc/pam.d/common-account.bak - - echo "account required pam_faillock.so" >> /etc/pam.d/common-account + # Ubuntu/Debian uses common-auth, common-account, common-password + # pam_faillock.so is only available in Ubuntu 22.04+ + if [[ "$ubuntu_version" -ge 22 ]]; then + if [[ -f /etc/pam.d/common-auth ]]; then + cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak + { + echo "auth required pam_faillock.so preauth audit silent deny=4 fail_interval=900 unlock_time=600" + echo "auth [success=1 default=ignore] pam_unix.so nullok" + echo "auth [default=die] pam_faillock.so authfail audit deny=4 fail_interval=900 unlock_time=600" + echo "auth sufficient pam_faillock.so authsucc audit deny=4 fail_interval=900 unlock_time=600" + echo "auth requisite pam_deny.so" + echo "auth required pam_permit.so" + } >> /etc/pam.d/common-auth + fi - ##############Password reuse policy################## + if [[ -f /etc/pam.d/common-account ]]; then + cp /etc/pam.d/common-account /etc/pam.d/common-account.bak + echo "account required pam_faillock.so" >> /etc/pam.d/common-account + fi + echo "Ubuntu 22.04+ PAM faillock configuration applied" + else + # Ubuntu 20.04 uses pam_tally2 instead of pam_faillock + echo "Ubuntu $ubuntu_version detected - using pam_tally2 for account lockout" + if [[ -f /etc/pam.d/common-auth ]]; then + cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak + # Only add if not already present + if ! grep -q "pam_tally2" /etc/pam.d/common-auth; then + sed -i '/^auth.*pam_unix.so/i auth required pam_tally2.so deny=4 onerr=fail unlock_time=600' /etc/pam.d/common-auth + fi + fi + if [[ -f /etc/pam.d/common-account ]]; then + cp /etc/pam.d/common-account /etc/pam.d/common-account.bak + if ! grep -q "pam_tally2" /etc/pam.d/common-account; then + echo "account required pam_tally2.so" >> /etc/pam.d/common-account + fi + fi + fi - # Backup the original file - cp /etc/pam.d/common-password /etc/pam.d/common-password.bak + ##############Password reuse policy################## + if [[ -f /etc/pam.d/common-password ]]; then + cp /etc/pam.d/common-password /etc/pam.d/common-password.bak + { + echo "password requisite pam_pwquality.so retry=3" + echo "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass remember=5" + echo "password requisite pam_deny.so" + echo "password required pam_permit.so" + } >> /etc/pam.d/common-password + fi + echo "Ubuntu PAM configuration updated for password lockout and reuse policies" - { - echo "password requisite pam_pwquality.so retry=3" - echo "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass remember=5" - echo "password requisite pam_deny.so" - echo "password required pam_permit.so" - } >> /etc/pam.d/common-password + fi #####################Password expiry policy################# - #Define the destination file config_file='/etc/login.defs' @@ -861,31 +1034,50 @@ harden_auth() { echo "Password expiry policy updated to PASS_MIN_DAYS 1 & PASS_MAX_DAYS 365 & PASS_WARN_AGE 7" #####################Password encryption standards########## - config_file='/etc/login.defs' - update_config_files 'ENCRYPT_METHOD' 'ENCRYPT_METHOD yescrypt' ${config_file} - - echo "Password encryption method set to yescrypt" + if [[ ${OS_FLAVOUR} == "ubuntu" ]]; then + # Check Ubuntu version for encryption method support + ubuntu_version="22" + if [[ -f /etc/os-release ]]; then + . /etc/os-release + ubuntu_version=$(echo "$VERSION_ID" | cut -d. -f1) + fi + if [[ "$ubuntu_version" -ge 22 ]]; then + # Ubuntu 22.04+ supports yescrypt + update_config_files 'ENCRYPT_METHOD' 'ENCRYPT_METHOD yescrypt' ${config_file} + echo "Password encryption method set to yescrypt" + else + # Ubuntu 20.04 and earlier use SHA512 + update_config_files 'ENCRYPT_METHOD' 'ENCRYPT_METHOD SHA512' ${config_file} + echo "Password encryption method set to SHA512 (Ubuntu $ubuntu_version)" + fi + else + # Default for other OS (original behavior) + update_config_files 'ENCRYPT_METHOD' 'ENCRYPT_METHOD yescrypt' ${config_file} + echo "Password encryption method set to yescrypt" + fi ####################Inactive password lock################ - #Define the destination file config_file='/etc/default/useradd' - echo "" >> ${config_file} - - update_config_files 'INACTIVE' 'INACTIVE=30' ${config_file} - echo "Inactive password lock policy updated to 30 days" + if [[ -f ${config_file} ]]; then + echo "" >> ${config_file} + update_config_files 'INACTIVE' 'INACTIVE=30' ${config_file} + echo "Inactive password lock policy updated to 30 days" + fi #################Session expiry policy##################### # Configuration lines to add to /etc/profile config_lines="readonly TMOUT=900 ; export TMOUT" - # Add configuration lines to the top of the file - echo "$config_lines" >> /etc/profile + # Add configuration lines to the file + if [[ -f /etc/profile ]]; then + echo "$config_lines" >> /etc/profile + echo "Configuration added to /etc/profile for shell timeout policy" + fi - echo "Configuration added to /etc/profile for shell timeout policy" return 0 } @@ -929,6 +1121,7 @@ harden_password_files harden_system remove_services disable_modules +harden_journald harden_audit harden_banner harden_log @@ -937,4 +1130,4 @@ cleanup_cache mv /etc/os-release.bak /etc/os-release -exit 0 +exit 0 \ No newline at end of file diff --git a/k8s_version.json b/k8s_version.json index 5127ecf4..720993bc 100644 --- a/k8s_version.json +++ b/k8s_version.json @@ -48,7 +48,8 @@ "1.33.1", "1.33.3", "1.33.5", - "1.33.6" + "1.33.6", + "1.34.2" ], "kubeadm": [ "1.27.2", @@ -100,7 +101,8 @@ "1.33.3", "1.33.4", "1.33.5", - "1.33.6" + "1.33.6", + "1.34.2" ], "rke2": [ "1.27.2", @@ -161,7 +163,8 @@ "1.33.1", "1.33.3", "1.33.5", - "1.33.6" + "1.33.6", + "1.34.2" ], "kubeadm-fips": [ "1.27.2", @@ -211,7 +214,8 @@ "1.33.3", "1.33.4", "1.33.5", - "1.33.6" + "1.33.6", + "1.34.2" ], "nodeadm": [ "1.29.0", diff --git a/ubuntu-fips/22.04/fix.sh b/ubuntu-fips/22.04/fix.sh index 93deea46..0a9c41ed 100755 --- a/ubuntu-fips/22.04/fix.sh +++ b/ubuntu-fips/22.04/fix.sh @@ -121,6 +121,43 @@ else echo "/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf fi +# Needed for default system path +if grep -i '^.*/sbin/auditctl.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/auditctl.*#/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + +if grep -i '^.*/sbin/auditd.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/auditd.*#/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + +if grep -i '^.*/sbin/ausearch.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/ausearch.*#/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + +if grep -i '^.*/sbin/aureport.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/aureport.*#/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + +if grep -i '^.*/sbin/autrace.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/autrace.*#/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + +if grep -i '^.*/sbin/augenrules.*$' /etc/aide/aide.conf; then +sed -i "s#.*/sbin/augenrules.*#/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf +else +echo "/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf +fi + else >&2 echo 'Remediation is not applicable, nothing was done' fi @@ -2584,7 +2621,7 @@ fi # Check to see if auth exists if ! grep -Erq "^auth\.\*,authpriv\.\*" /etc/rsyslog.*; then - echo "auth.*,authpriv.* /var/log/secure" >> /etc/rsyslog.d/50-default.conf + echo "auth.*,authpriv.* /var/log/auth.log" >> /etc/rsyslog.d/50-default.conf fi if ! grep -Erq "^daemon\.\*" /etc/rsyslog.*; then @@ -4034,6 +4071,7 @@ if [ -z "$line_number" ]; then else head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" printf '%s\n' "Ciphers $sshd_approved_ciphers" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" + printf '%s\n' "Ciphers $sshd_approved_ciphers" >> "/etc/ssh/sshd_config" tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" fi # Clean up after ourselves. @@ -4122,6 +4160,7 @@ if [ -z "$line_number" ]; then else head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" printf '%s\n' "MACs $sshd_approved_macs" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" + printf '%s\n' "MACs $sshd_approved_macs" >> "/etc/ssh/sshd_config" tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" fi # Clean up after ourselves. @@ -4279,6 +4318,15 @@ else # no audit=arg is present, append it sed -i "s/\(^GRUB_CMDLINE_LINUX=\".*\)\"/\1 audit=1\"/" '/etc/default/grub' fi + +# Correct the form of default kernel command line in GRUB for GRUB_CMDLINE_LINUX_DEFAULT +if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=.*audit=.*"' '/etc/default/grub' ; then + # modify the GRUB command-line if an audit= arg already exists + sed -i "s/\(^GRUB_CMDLINE_LINUX_DEFAULT=\".*\)audit=[^[:space:]]\+\(.*\"\)/\1audit=1\2/" '/etc/default/grub' +else + # no audit=arg is present, append it + sed -i "s/\(^GRUB_CMDLINE_LINUX_DEFAULT=\".*\)\"/\1 audit=1\"/" '/etc/default/grub' +fi update-grub else @@ -4493,6 +4541,7 @@ do # with proper key echo "-w /run/utmp -p wa -k session" >> "$audit_rules_file" + echo "-w /var/run/utmp -p wa -k session" >> "$audit_rules_file" fi done # Create a list of audit *.rules files that should be inspected for presence and correctness @@ -6466,7 +6515,7 @@ do # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key - echo "-w /var/log/journal/ -p wa -k audit_rules_var_log_journal" >> "$audit_rules_file" + echo "-w /var/log/journal/ -p wa -k systemd_journal" >> "$audit_rules_file" fi done # Create a list of audit *.rules files that should be inspected for presence and correctness @@ -22672,6 +22721,7 @@ do # with proper key echo "-w /sbin/fdisk -p x -k modules" >> "$audit_rules_file" + echo "-w /usr/sbin/fdisk -p x -k modules" >> "$audit_rules_file" fi done # Create a list of audit *.rules files that should be inspected for presence and correctness @@ -22749,6 +22799,7 @@ do # with proper key echo "-w /sbin/fdisk -p x -k modules" >> "$audit_rules_file" + echo "-w /usr/sbin/fdisk -p x -k modules" >> "$audit_rules_file" fi done @@ -23084,6 +23135,7 @@ else fi # END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_gpasswd' + ############################################################################### # BEGIN fix (194 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_kmod' ############################################################################### @@ -23092,7 +23144,7 @@ fi if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/bin/kmod -F perm=x" +OTHER_FILTERS="-F path=/bin/kmod -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -25527,7 +25579,7 @@ fi if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/bin/su -F perm=x" +OTHER_FILTERS="-F path=/bin/su -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -25847,14 +25899,14 @@ fi # END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_su' ############################################################################### -# BEGIN fix (203 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo' +# BEGIN fix (202 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_su' ############################################################################### -(>&2 echo "Remediating rule 203/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo'") +(>&2 echo "Remediating rule 202/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_su'") # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/bin/sudo -F perm=x" +OTHER_FILTERS="-F path=/usr/bin/su -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -26171,17 +26223,17 @@ fi else >&2 echo 'Remediation is not applicable, nothing was done' fi -# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo' +# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_su' ############################################################################### -# BEGIN fix (204 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit' +# BEGIN fix (203 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo' ############################################################################### -(>&2 echo "Remediating rule 204/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit'") +(>&2 echo "Remediating rule 203/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo'") # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/bin/sudoedit -F perm=x" +OTHER_FILTERS="-F path=/usr/bin/sudo -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -26498,17 +26550,17 @@ fi else >&2 echo 'Remediation is not applicable, nothing was done' fi -# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit' +# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudo' ############################################################################### -# BEGIN fix (205 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount' +# BEGIN fix (204 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit' ############################################################################### -(>&2 echo "Remediating rule 205/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount'") +(>&2 echo "Remediating rule 204/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit'") # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/bin/umount -F perm=x" +OTHER_FILTERS="-F path=/usr/bin/sudoedit -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -26825,17 +26877,17 @@ fi else >&2 echo 'Remediation is not applicable, nothing was done' fi -# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount' +# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_sudoedit' ############################################################################### -# BEGIN fix (206 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update' +# BEGIN fix (205 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount' ############################################################################### -(>&2 echo "Remediating rule 206/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update'") +(>&2 echo "Remediating rule 205/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount'") # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then ACTION_ARCH_FILTERS="-a always,exit" -OTHER_FILTERS="-F path=/usr/sbin/unix_update -F perm=x" +OTHER_FILTERS="-F path=/usr/bin/umount -F perm=x" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" @@ -27149,6 +27201,660 @@ if [ "$skip" -ne 0 ]; then fi fi +else + >&2 echo 'Remediation is not applicable, nothing was done' +fi +# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_umount' + +############################################################################### +# BEGIN fix (206 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update' +############################################################################### +(>&2 echo "Remediating rule 206/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update'") +# Remediation is applicable only in certain platforms +if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then + +ACTION_ARCH_FILTERS="-a always,exit" +OTHER_FILTERS="-F path=/usr/sbin/unix_update -F perm=x" +AUID_FILTERS="-F auid>=1000 -F auid!=unset" +SYSCALL="" +KEY="privileged-unix-update" +SYSCALL_GROUPING="" +# Perform the remediation for both possible tools: 'auditctl' and 'augenrules' +unset syscall_a +unset syscall_grouping +unset syscall_string +unset syscall +unset file_to_edit +unset rule_to_edit +unset rule_syscalls_to_edit +unset other_string +unset auid_string +unset full_rule + +# Load macro arguments into arrays +read -a syscall_a <<< $SYSCALL +read -a syscall_grouping <<< $SYSCALL_GROUPING + +# Create a list of audit *.rules files that should be inspected for presence and correctness +# of a particular audit rule. The scheme is as follows: +# +# ----------------------------------------------------------------------------------------- +# Tool used to load audit rules | Rule already defined | Audit rules file to inspect | +# ----------------------------------------------------------------------------------------- +# auditctl | Doesn't matter | /etc/audit/audit.rules | +# ----------------------------------------------------------------------------------------- +# augenrules | Yes | /etc/audit/rules.d/*.rules | +# augenrules | No | /etc/audit/rules.d/$key.rules | +# ----------------------------------------------------------------------------------------- +# +files_to_inspect=() + + +# If audit tool is 'augenrules', then check if the audit rule is defined +# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection +# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection +default_file="/etc/audit/rules.d/$KEY.rules" +# As other_filters may include paths, lets use a different delimiter for it +# The "F" script expression tells sed to print the filenames where the expressions matched +readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules) +# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet +if [ ${#files_to_inspect[@]} -eq "0" ] +then + file_to_inspect="/etc/audit/rules.d/$KEY.rules" + files_to_inspect=("$file_to_inspect") + if [ ! -e "$file_to_inspect" ] + then + touch "$file_to_inspect" + chmod 0640 "$file_to_inspect" + fi +fi + +# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead +skip=1 + +for audit_file in "${files_to_inspect[@]}" +do + # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern, + # i.e, collect rules that match: + # * the action, list and arch, (2-nd argument) + # * the other filters, (3-rd argument) + # * the auid filters, (4-rd argument) + readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file") + + candidate_rules=() + # Filter out rules that have more fields then required. This will remove rules more specific than the required scope + for s_rule in "${similar_rules[@]}" + do + # Strip all the options and fields we know of, + # than check if there was any field left over + extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule") + grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule") + done + + if [[ ${#syscall_a[@]} -ge 1 ]] + then + # Check if the syscall we want is present in any of the similar existing rules + for rule in "${candidate_rules[@]}" + do + rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs) + all_syscalls_found=0 + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || { + # A syscall was not found in the candidate rule + all_syscalls_found=1 + } + done + if [[ $all_syscalls_found -eq 0 ]] + then + # We found a rule with all the syscall(s) we want; skip rest of macro + skip=0 + break + fi + + # Check if this rule can be grouped with our target syscall and keep track of it + for syscall_g in "${syscall_grouping[@]}" + do + if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls" + then + file_to_edit=${audit_file} + rule_to_edit=${rule} + rule_syscalls_to_edit=${rule_syscalls} + fi + done + done + else + # If there is any candidate rule, it is compliant; skip rest of macro + if [ "${#candidate_rules[@]}" -gt 0 ] + then + skip=0 + fi + fi + + if [ "$skip" -eq 0 ]; then + break + fi +done + +if [ "$skip" -ne 0 ]; then + # We checked all rules that matched the expected resemblance pattern (action, arch & auid) + # At this point we know if we need to either append the $full_rule or group + # the syscall together with an exsiting rule + + # Append the full_rule if it cannot be grouped to any other rule + if [ -z ${rule_to_edit+x} ] + then + # Build full_rule while avoid adding double spaces when other_filters is empty + if [ "${#syscall_a[@]}" -gt 0 ] + then + syscall_string="" + for syscall in "${syscall_a[@]}" + do + syscall_string+=" -S $syscall" + done + fi + other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true + auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true + full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true + echo "$full_rule" >> "$default_file" + chmod o-rwx ${default_file} + else + # Check if the syscalls are declared as a comma separated list or + # as multiple -S parameters + if grep -q -- "," <<< "${rule_syscalls_to_edit}" + then + delimiter="," + else + delimiter=" -S " + fi + new_grouped_syscalls="${rule_syscalls_to_edit}" + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || { + # A syscall was not found in the candidate rule + new_grouped_syscalls+="${delimiter}${syscall}" + } + done + + # Group the syscall in the rule + sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit" + fi +fi +unset syscall_a +unset syscall_grouping +unset syscall_string +unset syscall +unset file_to_edit +unset rule_to_edit +unset rule_syscalls_to_edit +unset other_string +unset auid_string +unset full_rule + +# Load macro arguments into arrays +read -a syscall_a <<< $SYSCALL +read -a syscall_grouping <<< $SYSCALL_GROUPING + +# Create a list of audit *.rules files that should be inspected for presence and correctness +# of a particular audit rule. The scheme is as follows: +# +# ----------------------------------------------------------------------------------------- +# Tool used to load audit rules | Rule already defined | Audit rules file to inspect | +# ----------------------------------------------------------------------------------------- +# auditctl | Doesn't matter | /etc/audit/audit.rules | +# ----------------------------------------------------------------------------------------- +# augenrules | Yes | /etc/audit/rules.d/*.rules | +# augenrules | No | /etc/audit/rules.d/$key.rules | +# ----------------------------------------------------------------------------------------- +# +files_to_inspect=() + + + +# If audit tool is 'auditctl', then add '/etc/audit/audit.rules' +# file to the list of files to be inspected +default_file="/etc/audit/audit.rules" +files_to_inspect+=('/etc/audit/audit.rules' ) + +# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead +skip=1 + +for audit_file in "${files_to_inspect[@]}" +do + # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern, + # i.e, collect rules that match: + # * the action, list and arch, (2-nd argument) + # * the other filters, (3-rd argument) + # * the auid filters, (4-rd argument) + readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file") + + candidate_rules=() + # Filter out rules that have more fields then required. This will remove rules more specific than the required scope + for s_rule in "${similar_rules[@]}" + do + # Strip all the options and fields we know of, + # than check if there was any field left over + extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule") + grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule") + done + + if [[ ${#syscall_a[@]} -ge 1 ]] + then + # Check if the syscall we want is present in any of the similar existing rules + for rule in "${candidate_rules[@]}" + do + rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs) + all_syscalls_found=0 + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || { + # A syscall was not found in the candidate rule + all_syscalls_found=1 + } + done + if [[ $all_syscalls_found -eq 0 ]] + then + # We found a rule with all the syscall(s) we want; skip rest of macro + skip=0 + break + fi + + # Check if this rule can be grouped with our target syscall and keep track of it + for syscall_g in "${syscall_grouping[@]}" + do + if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls" + then + file_to_edit=${audit_file} + rule_to_edit=${rule} + rule_syscalls_to_edit=${rule_syscalls} + fi + done + done + else + # If there is any candidate rule, it is compliant; skip rest of macro + if [ "${#candidate_rules[@]}" -gt 0 ] + then + skip=0 + fi + fi + + if [ "$skip" -eq 0 ]; then + break + fi +done + +if [ "$skip" -ne 0 ]; then + # We checked all rules that matched the expected resemblance pattern (action, arch & auid) + # At this point we know if we need to either append the $full_rule or group + # the syscall together with an exsiting rule + + # Append the full_rule if it cannot be grouped to any other rule + if [ -z ${rule_to_edit+x} ] + then + # Build full_rule while avoid adding double spaces when other_filters is empty + if [ "${#syscall_a[@]}" -gt 0 ] + then + syscall_string="" + for syscall in "${syscall_a[@]}" + do + syscall_string+=" -S $syscall" + done + fi + other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true + auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true + full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true + echo "$full_rule" >> "$default_file" + chmod o-rwx ${default_file} + else + # Check if the syscalls are declared as a comma separated list or + # as multiple -S parameters + if grep -q -- "," <<< "${rule_syscalls_to_edit}" + then + delimiter="," + else + delimiter=" -S " + fi + new_grouped_syscalls="${rule_syscalls_to_edit}" + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || { + # A syscall was not found in the candidate rule + new_grouped_syscalls+="${delimiter}${syscall}" + } + done + + # Group the syscall in the rule + sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit" + fi +fi + +else + >&2 echo 'Remediation is not applicable, nothing was done' +fi +# END fix for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update' + +############################################################################### +# BEGIN fix (206 / 214) for 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update' +############################################################################### +(>&2 echo "Remediating rule 206/214: 'xccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_unix_update'") +# Remediation is applicable only in certain platforms +if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then + +ACTION_ARCH_FILTERS="-a always,exit" +OTHER_FILTERS="-F path=/sbin/unix_update -F perm=x" +AUID_FILTERS="-F auid>=1000 -F auid!=unset" +SYSCALL="" +KEY="privileged-unix-update" +SYSCALL_GROUPING="" +# Perform the remediation for both possible tools: 'auditctl' and 'augenrules' +unset syscall_a +unset syscall_grouping +unset syscall_string +unset syscall +unset file_to_edit +unset rule_to_edit +unset rule_syscalls_to_edit +unset other_string +unset auid_string +unset full_rule + +# Load macro arguments into arrays +read -a syscall_a <<< $SYSCALL +read -a syscall_grouping <<< $SYSCALL_GROUPING + +# Create a list of audit *.rules files that should be inspected for presence and correctness +# of a particular audit rule. The scheme is as follows: +# +# ----------------------------------------------------------------------------------------- +# Tool used to load audit rules | Rule already defined | Audit rules file to inspect | +# ----------------------------------------------------------------------------------------- +# auditctl | Doesn't matter | /etc/audit/audit.rules | +# ----------------------------------------------------------------------------------------- +# augenrules | Yes | /etc/audit/rules.d/*.rules | +# augenrules | No | /etc/audit/rules.d/$key.rules | +# ----------------------------------------------------------------------------------------- +# +files_to_inspect=() + + +# If audit tool is 'augenrules', then check if the audit rule is defined +# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection +# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection +default_file="/etc/audit/rules.d/$KEY.rules" +# As other_filters may include paths, lets use a different delimiter for it +# The "F" script expression tells sed to print the filenames where the expressions matched +readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules) +# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet +if [ ${#files_to_inspect[@]} -eq "0" ] +then + file_to_inspect="/etc/audit/rules.d/$KEY.rules" + files_to_inspect=("$file_to_inspect") + if [ ! -e "$file_to_inspect" ] + then + touch "$file_to_inspect" + chmod 0640 "$file_to_inspect" + fi +fi + +# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead +skip=1 + +for audit_file in "${files_to_inspect[@]}" +do + # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern, + # i.e, collect rules that match: + # * the action, list and arch, (2-nd argument) + # * the other filters, (3-rd argument) + # * the auid filters, (4-rd argument) + readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file") + + candidate_rules=() + # Filter out rules that have more fields then required. This will remove rules more specific than the required scope + for s_rule in "${similar_rules[@]}" + do + # Strip all the options and fields we know of, + # than check if there was any field left over + extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule") + grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule") + done + + if [[ ${#syscall_a[@]} -ge 1 ]] + then + # Check if the syscall we want is present in any of the similar existing rules + for rule in "${candidate_rules[@]}" + do + rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs) + all_syscalls_found=0 + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || { + # A syscall was not found in the candidate rule + all_syscalls_found=1 + } + done + if [[ $all_syscalls_found -eq 0 ]] + then + # We found a rule with all the syscall(s) we want; skip rest of macro + skip=0 + break + fi + + # Check if this rule can be grouped with our target syscall and keep track of it + for syscall_g in "${syscall_grouping[@]}" + do + if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls" + then + file_to_edit=${audit_file} + rule_to_edit=${rule} + rule_syscalls_to_edit=${rule_syscalls} + fi + done + done + else + # If there is any candidate rule, it is compliant; skip rest of macro + if [ "${#candidate_rules[@]}" -gt 0 ] + then + skip=0 + fi + fi + + if [ "$skip" -eq 0 ]; then + break + fi +done + +if [ "$skip" -ne 0 ]; then + # We checked all rules that matched the expected resemblance pattern (action, arch & auid) + # At this point we know if we need to either append the $full_rule or group + # the syscall together with an exsiting rule + + # Append the full_rule if it cannot be grouped to any other rule + if [ -z ${rule_to_edit+x} ] + then + # Build full_rule while avoid adding double spaces when other_filters is empty + if [ "${#syscall_a[@]}" -gt 0 ] + then + syscall_string="" + for syscall in "${syscall_a[@]}" + do + syscall_string+=" -S $syscall" + done + fi + other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true + auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true + full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true + echo "$full_rule" >> "$default_file" + chmod o-rwx ${default_file} + else + # Check if the syscalls are declared as a comma separated list or + # as multiple -S parameters + if grep -q -- "," <<< "${rule_syscalls_to_edit}" + then + delimiter="," + else + delimiter=" -S " + fi + new_grouped_syscalls="${rule_syscalls_to_edit}" + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || { + # A syscall was not found in the candidate rule + new_grouped_syscalls+="${delimiter}${syscall}" + } + done + + # Group the syscall in the rule + sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit" + fi +fi +unset syscall_a +unset syscall_grouping +unset syscall_string +unset syscall +unset file_to_edit +unset rule_to_edit +unset rule_syscalls_to_edit +unset other_string +unset auid_string +unset full_rule + +# Load macro arguments into arrays +read -a syscall_a <<< $SYSCALL +read -a syscall_grouping <<< $SYSCALL_GROUPING + +# Create a list of audit *.rules files that should be inspected for presence and correctness +# of a particular audit rule. The scheme is as follows: +# +# ----------------------------------------------------------------------------------------- +# Tool used to load audit rules | Rule already defined | Audit rules file to inspect | +# ----------------------------------------------------------------------------------------- +# auditctl | Doesn't matter | /etc/audit/audit.rules | +# ----------------------------------------------------------------------------------------- +# augenrules | Yes | /etc/audit/rules.d/*.rules | +# augenrules | No | /etc/audit/rules.d/$key.rules | +# ----------------------------------------------------------------------------------------- +# +files_to_inspect=() + + + +# If audit tool is 'auditctl', then add '/etc/audit/audit.rules' +# file to the list of files to be inspected +default_file="/etc/audit/audit.rules" +files_to_inspect+=('/etc/audit/audit.rules' ) + +# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead +skip=1 + +for audit_file in "${files_to_inspect[@]}" +do + # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern, + # i.e, collect rules that match: + # * the action, list and arch, (2-nd argument) + # * the other filters, (3-rd argument) + # * the auid filters, (4-rd argument) + readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file") + + candidate_rules=() + # Filter out rules that have more fields then required. This will remove rules more specific than the required scope + for s_rule in "${similar_rules[@]}" + do + # Strip all the options and fields we know of, + # than check if there was any field left over + extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule") + grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule") + done + + if [[ ${#syscall_a[@]} -ge 1 ]] + then + # Check if the syscall we want is present in any of the similar existing rules + for rule in "${candidate_rules[@]}" + do + rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs) + all_syscalls_found=0 + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || { + # A syscall was not found in the candidate rule + all_syscalls_found=1 + } + done + if [[ $all_syscalls_found -eq 0 ]] + then + # We found a rule with all the syscall(s) we want; skip rest of macro + skip=0 + break + fi + + # Check if this rule can be grouped with our target syscall and keep track of it + for syscall_g in "${syscall_grouping[@]}" + do + if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls" + then + file_to_edit=${audit_file} + rule_to_edit=${rule} + rule_syscalls_to_edit=${rule_syscalls} + fi + done + done + else + # If there is any candidate rule, it is compliant; skip rest of macro + if [ "${#candidate_rules[@]}" -gt 0 ] + then + skip=0 + fi + fi + + if [ "$skip" -eq 0 ]; then + break + fi +done + +if [ "$skip" -ne 0 ]; then + # We checked all rules that matched the expected resemblance pattern (action, arch & auid) + # At this point we know if we need to either append the $full_rule or group + # the syscall together with an exsiting rule + + # Append the full_rule if it cannot be grouped to any other rule + if [ -z ${rule_to_edit+x} ] + then + # Build full_rule while avoid adding double spaces when other_filters is empty + if [ "${#syscall_a[@]}" -gt 0 ] + then + syscall_string="" + for syscall in "${syscall_a[@]}" + do + syscall_string+=" -S $syscall" + done + fi + other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true + auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true + full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true + echo "$full_rule" >> "$default_file" + chmod o-rwx ${default_file} + else + # Check if the syscalls are declared as a comma separated list or + # as multiple -S parameters + if grep -q -- "," <<< "${rule_syscalls_to_edit}" + then + delimiter="," + else + delimiter=" -S " + fi + new_grouped_syscalls="${rule_syscalls_to_edit}" + for syscall in "${syscall_a[@]}" + do + grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || { + # A syscall was not found in the candidate rule + new_grouped_syscalls+="${delimiter}${syscall}" + } + done + + # Group the syscall in the rule + sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit" + fi +fi + else >&2 echo 'Remediation is not applicable, nothing was done' fi