#!/bin/bash #DESC Makes security related system changes based on openscap rules for PCI-DSS4.0. #PRDno #HELP updateos dss4.0_harden # #Sourced from redhat insights. #Fix rpm file permissions. for file in `rpm -Va | awk '{ if (substr($0,6,1)=="U" || substr($0,7,1)=="G") print $NF }'` do PACKAGENAME=`rpm -qf $file` rpm --setugids $PACKAGENAME done #system-wide crypto update-crypto-policies --set #sudoers config [ "`grep use_pty /etc/sudoers`" == "" ] && echo "Defaults use_pty" >> /etc/sudoers [ "`grep timestamp_timeout=5 /etc/sudoers`" == "" ] && echo "Defaults timestamp_timeout=5" >> /etc/sudoers # #Below sourced from scada. #Ensure pam enforces minimum digit chars. # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then var_password_pam_dcredit='-1' # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dcredit") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dcredit" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^dcredit\\>" "/etc/security/pwquality.conf"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^dcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf" else if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf" fi cce="CCE-80653-9" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf" printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #Ensure pam enforces min lower case chars. # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then var_password_pam_lcredit='-1' # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^lcredit") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_lcredit" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^lcredit\\>" "/etc/security/pwquality.conf"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^lcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf" else if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf" fi cce="CCE-80655-4" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf" printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #minimum length password. # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then var_password_pam_minlen='7' # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minlen") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minlen" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^minlen\\>" "/etc/security/pwquality.conf"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^minlen\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf" else if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf" fi cce="CCE-80656-2" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf" printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #minimum upper case chars. # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then var_password_pam_ucredit='-1' # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ucredit") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_ucredit" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^ucredit\\>" "/etc/security/pwquality.conf"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^ucredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf" else if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf" fi cce="CCE-80665-3" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf" printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #set password hashing alg. # Remediation is applicable only in certain platforms if rpm --quiet -q libuser; then LIBUSER_CONF="/etc/libuser.conf" CRYPT_STYLE_REGEX='[[:space:]]*\[defaults](.*(\n)+)+?[[:space:]]*crypt_style[[:space:]]*' # Try find crypt_style in [defaults] section. If it is here, then change algorithm to sha512. # If it isn't here, then add it to [defaults] section. if grep -qzosP $CRYPT_STYLE_REGEX $LIBUSER_CONF ; then sed -i "s/\(crypt_style[[:space:]]*=[[:space:]]*\).*/\1sha512/g" $LIBUSER_CONF elif grep -qs "\[defaults]" $LIBUSER_CONF ; then sed -i "/[[:space:]]*\[defaults]/a crypt_style = sha512" $LIBUSER_CONF else echo -e "[defaults]\ncrypt_style = sha512" >> $LIBUSER_CONF fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if rpm --quiet -q shadow-utils; then var_password_hashing_algorithm='SHA512' if grep --silent ^ENCRYPT_METHOD /etc/login.defs ; then sed -i "s/^ENCRYPT_METHOD .*/ENCRYPT_METHOD $var_password_hashing_algorithm/g" /etc/login.defs else echo "" >> /etc/login.defs echo "ENCRYPT_METHOD $var_password_hashing_algorithm" >> /etc/login.defs fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then if [ -e "/etc/pam.d/password-auth" ] ; then PAM_FILE_PATH="/etc/pam.d/password-auth" if [ -f /usr/bin/authselect ]; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }') # If not already in use, a custom profile is created preserving the enabled features. if [[ ! $CURRENT_PROFILE == custom/* ]]; then ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }') authselect create-profile hardening -b $CURRENT_PROFILE CURRENT_PROFILE="custom/hardening" authselect apply-changes -b --backup=before-hardening-custom-profile authselect select $CURRENT_PROFILE for feature in $ENABLED_FEATURES; do authselect enable-feature $feature; done authselect apply-changes -b --backup=after-hardening-custom-profile fi PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth") PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME" authselect apply-changes -b fi if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*' "$PAM_FILE_PATH"; then # Line matching group + control + module was not found. Check group + module. if [ "$(grep -cP '^\s*password\s+.*\s+pam_unix.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then # The control is updated only if one single line matches. sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_unix.so.*)/\1'"sufficient"' \2/' "$PAM_FILE_PATH" else echo 'password '"sufficient"' pam_unix.so' >> "$PAM_FILE_PATH" fi fi # Check the option if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*\ssha512\b' "$PAM_FILE_PATH"; then sed -i -E --follow-symlinks '/\s*password\s+'"sufficient"'\s+pam_unix.so.*/ s/$/ sha512/' "$PAM_FILE_PATH" fi if [ -f /usr/bin/authselect ]; then authselect apply-changes -b fi else echo "/etc/pam.d/password-auth was not found" >&2 fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then if [ -e "/etc/pam.d/system-auth" ] ; then PAM_FILE_PATH="/etc/pam.d/system-auth" if [ -f /usr/bin/authselect ]; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }') # If not already in use, a custom profile is created preserving the enabled features. if [[ ! $CURRENT_PROFILE == custom/* ]]; then ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }') authselect create-profile hardening -b $CURRENT_PROFILE CURRENT_PROFILE="custom/hardening" authselect apply-changes -b --backup=before-hardening-custom-profile authselect select $CURRENT_PROFILE for feature in $ENABLED_FEATURES; do authselect enable-feature $feature; done authselect apply-changes -b --backup=after-hardening-custom-profile fi PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth") PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME" authselect apply-changes -b fi if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*' "$PAM_FILE_PATH"; then # Line matching group + control + module was not found. Check group + module. if [ "$(grep -cP '^\s*password\s+.*\s+pam_unix.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then # The control is updated only if one single line matches. sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_unix.so.*)/\1'"sufficient"' \2/' "$PAM_FILE_PATH" else echo 'password '"sufficient"' pam_unix.so' >> "$PAM_FILE_PATH" fi fi # Check the option if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*\ssha512\b' "$PAM_FILE_PATH"; then sed -i -E --follow-symlinks '/\s*password\s+'"sufficient"'\s+pam_unix.so.*/ s/$/ sha512/' "$PAM_FILE_PATH" fi if [ -f /usr/bin/authselect ]; then authselect apply-changes -b fi else echo "/etc/pam.d/system-auth was not found" >&2 fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #PAM last login info. # Remediation is applicable only in certain platforms if rpm --quiet -q pam; then if [ -e "/etc/pam.d/postlogin" ] ; then PAM_FILE_PATH="/etc/pam.d/postlogin" if [ -f /usr/bin/authselect ]; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }') # If not already in use, a custom profile is created preserving the enabled features. if [[ ! $CURRENT_PROFILE == custom/* ]]; then ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }') authselect create-profile hardening -b $CURRENT_PROFILE CURRENT_PROFILE="custom/hardening" authselect apply-changes -b --backup=before-hardening-custom-profile authselect select $CURRENT_PROFILE for feature in $ENABLED_FEATURES; do authselect enable-feature $feature; done authselect apply-changes -b --backup=after-hardening-custom-profile fi PAM_FILE_NAME=$(basename "/etc/pam.d/postlogin") PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME" authselect apply-changes -b fi if ! grep -qP '^\s*session\s+'"required"'\s+pam_lastlog.so\s*.*' "$PAM_FILE_PATH"; then # Line matching group + control + module was not found. Check group + module. if [ "$(grep -cP '^\s*session\s+.*\s+pam_lastlog.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then # The control is updated only if one single line matches. sed -i -E --follow-symlinks 's/^(\s*session\s+).*(\bpam_lastlog.so.*)/\1'"required"' \2/' "$PAM_FILE_PATH" else sed -i --follow-symlinks '1i session '"required"' pam_lastlog.so' "$PAM_FILE_PATH" fi fi # Check the option if ! grep -qP '^\s*session\s+'"required"'\s+pam_lastlog.so\s*.*\sshowfailed\b' "$PAM_FILE_PATH"; then sed -i -E --follow-symlinks '/\s*session\s+'"required"'\s+pam_lastlog.so.*/ s/$/ showfailed/' "$PAM_FILE_PATH" fi if [ -f /usr/bin/authselect ]; then authselect apply-changes -b fi else echo "/etc/pam.d/postlogin was not found" >&2 fi if [ -e "/etc/pam.d/postlogin" ] ; then PAM_FILE_PATH="/etc/pam.d/postlogin" if [ -f /usr/bin/authselect ]; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }') # If not already in use, a custom profile is created preserving the enabled features. if [[ ! $CURRENT_PROFILE == custom/* ]]; then ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }') authselect create-profile hardening -b $CURRENT_PROFILE CURRENT_PROFILE="custom/hardening" authselect apply-changes -b --backup=before-hardening-custom-profile authselect select $CURRENT_PROFILE for feature in $ENABLED_FEATURES; do authselect enable-feature $feature; done authselect apply-changes -b --backup=after-hardening-custom-profile fi PAM_FILE_NAME=$(basename "/etc/pam.d/postlogin") PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME" authselect apply-changes -b fi if grep -qP '^\s*session\s.*\bpam_lastlog.so\s.*\bsilent\b' "$PAM_FILE_PATH"; then sed -i -E --follow-symlinks 's/(.*session.*pam_lastlog.so.*)\bsilent\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH" fi if [ -f /usr/bin/authselect ]; then authselect apply-changes -b fi else echo "/etc/pam.d/postlogin was not found" >&2 fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #multifactor auth support # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if ! rpm -q --quiet "opensc" ; then yum install -y "opensc" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if ! rpm -q --quiet "pcsc-lite" ; then yum install -y "pcsc-lite" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then SYSTEMCTL_EXEC='/usr/bin/systemctl' "$SYSTEMCTL_EXEC" unmask 'pcscd.service' "$SYSTEMCTL_EXEC" start 'pcscd.service' "$SYSTEMCTL_EXEC" enable 'pcscd.service' else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then var_smartcard_drivers='cac' OPENSC_TOOL="/usr/bin/opensc-tool" if [ -f "${OPENSC_TOOL}" ]; then ${OPENSC_TOOL} -S app:default:card_drivers:$var_smartcard_drivers fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then var_smartcard_drivers='cac' OPENSC_TOOL="/usr/bin/opensc-tool" if [ -f "${OPENSC_TOOL}" ]; then ${OPENSC_TOOL} -S app:default:force_card_driver:$var_smartcard_drivers fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #account expire due to inactivity # Remediation is applicable only in certain platforms if rpm --quiet -q shadow-utils; then var_account_disable_post_pw_expiration='90' # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^INACTIVE") # shellcheck disable=SC2059 printf -v formatted_output "%s=%s" "$stripped_key" "$var_account_disable_post_pw_expiration" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^INACTIVE\\>" "/etc/default/useradd"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^INACTIVE\\>.*/$escaped_formatted_output/gi" "/etc/default/useradd" else if [[ -s "/etc/default/useradd" ]] && [[ -n "$(tail -c 1 -- "/etc/default/useradd" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/default/useradd" fi cce="CCE-80954-1" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/default/useradd" >> "/etc/default/useradd" printf '%s\n' "$formatted_output" >> "/etc/default/useradd" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #password expire chage -M 180 -m 7 -W 7 USER # Remediation is applicable only in certain platforms if rpm --quiet -q shadow-utils; then var_accounts_maximum_age_login_defs='90' grep -q ^PASS_MAX_DAYS /etc/login.defs && \ sed -i "s/PASS_MAX_DAYS.*/PASS_MAX_DAYS $var_accounts_maximum_age_login_defs/g" /etc/login.defs if ! [ $? -eq 0 ]; then echo "PASS_MAX_DAYS $var_accounts_maximum_age_login_defs" >> /etc/login.defs fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #prevent empty password accounts. # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if [ -f /usr/bin/authselect ]; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi authselect enable-feature without-nullok authselect apply-changes -b else if grep -qP '^\s*auth\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/system-auth"; then sed -i -E --follow-symlinks 's/(.*auth.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/system-auth" fi if grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/system-auth"; then sed -i -E --follow-symlinks 's/(.*password.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/system-auth" fi if grep -qP '^\s*auth\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/password-auth"; then sed -i -E --follow-symlinks 's/(.*auth.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/password-auth" fi if grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/password-auth"; then sed -i -E --follow-symlinks 's/(.*password.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/password-auth" fi fi else >&2 echo 'Remediation is not applicable, nothing was done' fi #enable authselect #var_authselect_profile='sssd' var_authselect_profile='legacy' authselect select "$var_authselect_profile" if test "$?" -ne 0; then if rpm --quiet --verify pam; then authselect select --force "$var_authselect_profile" else echo "Files in the 'pam' package have been altered, so the authselect configuration won't be forced" >&2 fi fi #auditing # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="chmod" KEY="perm_mod" SYSCALL_GROUPING="chmod fchmod fchmodat" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="chown" KEY="perm_mod" SYSCALL_GROUPING="chown fchown fchownat lchown" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fchmod" KEY="perm_mod" SYSCALL_GROUPING="chmod fchmod fchmodat" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fchmodat" KEY="perm_mod" SYSCALL_GROUPING="chmod fchmod fchmodat" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fchown" KEY="perm_mod" SYSCALL_GROUPING="chown fchown fchownat lchown" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fchownat" KEY="perm_mod" SYSCALL_GROUPING="chown fchown fchownat lchown" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fremovexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="fremovexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="fsetxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="fsetxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="lchown" KEY="perm_mod" SYSCALL_GROUPING="chown fchown fchownat lchown" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="lremovexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="lremovexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="lsetxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="lsetxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="removexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="removexattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="setxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid=0" SYSCALL="setxattr" KEY="perm_mod" SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="rename" KEY="delete" SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="renameat" KEY="delete" SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="rmdir" KEY="delete" SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="unlink" KEY="delete" SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="unlinkat" KEY="delete" SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="creat" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="ftruncate" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="open" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="open_by_handle_at" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="openat" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="truncate" KEY="access" SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at" for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EACCES" # 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 done for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F exit=-EPERM" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system # Note: 32-bit and 64-bit kernel syscall numbers not always line up => # it's required on a 64-bit system to check also for the presence # of 32-bit's equivalent of the corresponding rule. # (See `man 7 audit.rules` for details ) [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="delete_module" KEY="modules" SYSCALL_GROUPING="delete_module" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system # Note: 32-bit and 64-bit kernel syscall numbers not always line up => # it's required on a 64-bit system to check also for the presence # of 32-bit's equivalent of the corresponding rule. # (See `man 7 audit.rules` for details ) [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="finit_module" KEY="modules" SYSCALL_GROUPING="init_module finit_module" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system # Note: 32-bit and 64-bit kernel syscall numbers not always line up => # it's required on a 64-bit system to check also for the presence # of 32-bit's equivalent of the corresponding rule. # (See `man 7 audit.rules` for details ) [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="init_module" KEY="modules" SYSCALL_GROUPING="init_module finit_module" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/tallylog" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/logins.rules" # If the logins.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/faillock" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/faillock $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/faillock$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/faillock -p wa -k logins" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/faillock" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/logins.rules" # If the logins.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/faillock" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/faillock $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/faillock$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/faillock -p wa -k logins" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/lastlog" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/logins.rules" # If the logins.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then ACTION_ARCH_FILTERS="-a always,exit" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="" KEY="privileged" SYSCALL_GROUPING="" FILTER_NODEV=$(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) PARTITIONS=$(findmnt -n -l -k -it $FILTER_NODEV | grep -Pv "noexec|nosuid|/proc($|/.*$)" | awk '{ print $1 }') for PARTITION in $PARTITIONS; do PRIV_CMDS=$(find "${PARTITION}" -xdev -perm /6000 -type f 2>/dev/null) for PRIV_CMD in $PRIV_CMDS; do OTHER_FILTERS="-F path=$PRIV_CMD -F perm=x" # 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 done done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do # Create expected audit group and audit rule form for particular system call & architecture if [ ${ARCH} = "b32" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output) # so append it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday stime" SYSCALL_GROUPING="adjtimex settimeofday stime" elif [ ${ARCH} = "b64" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output) # therefore don't add it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday" SYSCALL_GROUPING="adjtimex settimeofday" fi OTHER_FILTERS="" AUID_FILTERS="" KEY="audit_time_rules" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="-F a0=0x0" AUID_FILTERS="" SYSCALL="clock_settime" KEY="time-change" 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do # Create expected audit group and audit rule form for particular system call & architecture if [ ${ARCH} = "b32" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output) # so append it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday stime" SYSCALL_GROUPING="adjtimex settimeofday stime" elif [ ${ARCH} = "b64" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output) # therefore don't add it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday" SYSCALL_GROUPING="adjtimex settimeofday" fi OTHER_FILTERS="" AUID_FILTERS="" KEY="audit_time_rules" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do # Create expected audit group and audit rule form for particular system call & architecture if [ ${ARCH} = "b32" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output) # so append it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday stime" SYSCALL_GROUPING="adjtimex settimeofday stime" elif [ ${ARCH} = "b64" ] then ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output) # therefore don't add it to the list of time group system calls to be audited SYSCALL="adjtimex settimeofday" SYSCALL_GROUPING="adjtimex settimeofday" fi OTHER_FILTERS="" AUID_FILTERS="" KEY="audit_time_rules" # 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/localtime" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/localtime $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/localtime$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/localtime -p wa -k audit_time_rules" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_time_rules.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/localtime" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_time_rules.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_time_rules.rules" # If the audit_time_rules.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/localtime" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/localtime $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/localtime$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/localtime -p wa -k audit_time_rules" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Traverse all of: # # /etc/audit/audit.rules, (for auditctl case) # /etc/audit/rules.d/*.rules (for augenrules case) # # files to check if '-e .*' setting is present in that '*.rules' file already. # If found, delete such occurrence since auditctl(8) manual page instructs the # '-e 2' rule should be placed as the last rule in the configuration find /etc/audit /etc/audit/rules.d -maxdepth 1 -type f -name '*.rules' -exec sed -i '/-e[[:space:]]\+.*/d' {} ';' # Append '-e 2' requirement at the end of both: # * /etc/audit/audit.rules file (for auditctl case) # * /etc/audit/rules.d/immutable.rules (for augenrules case) for AUDIT_FILE in "/etc/audit/audit.rules" "/etc/audit/rules.d/immutable.rules" do echo '' >> $AUDIT_FILE echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_FILE echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_FILE echo '-e 2' >> $AUDIT_FILE chmod o-rwx $AUDIT_FILE done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/selinux/" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/selinux/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/selinux/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/selinux/ -p wa -k MAC-policy" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/MAC-policy.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/selinux/" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/MAC-policy.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/MAC-policy.rules" # If the MAC-policy.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/selinux/" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/selinux/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/selinux/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/selinux/ -p wa -k MAC-policy" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="-F auid>=1000 -F auid!=unset" SYSCALL="mount" KEY="perm_mod" 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 done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # First perform the remediation of the syscall rule # Retrieve hardware architecture of the underlying system [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64") for ARCH in "${RULE_ARCHS[@]}" do ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH" OTHER_FILTERS="" AUID_FILTERS="" SYSCALL="sethostname setdomainname" KEY="audit_rules_networkconfig_modification" SYSCALL_GROUPING="sethostname setdomainname" # 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 done # Then perform the remediations for the watch rules # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/issue" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules" # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/issue" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue.net" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules" # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/hosts" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/hosts" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules" # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/hosts" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sysconfig/network" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules" # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/run/utmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/run/utmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/run/utmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key 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 # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/run/utmp" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/session.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/session.rules" # If the session.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/run/utmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/run/utmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/run/utmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key 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 # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/btmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/btmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/btmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/btmp -p wa -k session" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/btmp" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/session.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/session.rules" # If the session.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/btmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/btmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/btmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/btmp -p wa -k session" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/wtmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/wtmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/wtmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/wtmp -p wa -k session" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/wtmp" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/session.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/session.rules" # If the session.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/var/log/wtmp" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/wtmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/var/log/wtmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /var/log/wtmp -p wa -k session" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sudoers" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sudoers$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sudoers -p wa -k actions" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/actions.rules" # If the actions.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sudoers" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sudoers$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sudoers -p wa -k actions" >> "$audit_rules_file" fi done # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sudoers.d/" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers.d/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sudoers.d/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers.d/" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/actions.rules" # If the actions.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/sudoers.d/" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers.d/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/sudoers.d/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/group" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/group" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/group" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/gshadow" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi# Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/gshadow" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/security/opasswd" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/passwd" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/passwd" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/passwd" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then # Perform the remediation for both possible tools: 'auditctl' and 'augenrules' # 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules' # into the list of files to be inspected files_to_inspect+=('/etc/audit/audit.rules') # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/shadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done # 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 the audit is 'augenrules', then check if rule is already defined # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection. # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection. readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/shadow" /etc/audit/rules.d/*.rules) # For each of the matched entries for match in "${matches[@]}" do # Extract filepath from the match rulesd_audit_file=$(echo $match | cut -f1 -d ':') # Append that path into list of files for inspection files_to_inspect+=("$rulesd_audit_file") done # Case when particular audit rule isn't defined yet if [ "${#files_to_inspect[@]}" -eq "0" ] then # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules" # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions if [ ! -e "$key_rule_file" ] then touch "$key_rule_file" chmod 0640 "$key_rule_file" fi files_to_inspect+=("$key_rule_file") fi # Finally perform the inspection and possible subsequent audit rule # correction for each of the files previously identified for inspection for audit_rules_file in "${files_to_inspect[@]}" do # Check if audit watch file system object rule for given path already present if grep -q -P -- "^[\s]*-w[\s]+/etc/shadow" "$audit_rules_file" then # Rule is found => verify yet if existing rule definition contains # all of the required access type bits # Define BRE whitespace class shortcut sp="[[:space:]]" # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file") # Split required access bits string into characters array # (to check bit's presence for one bit at a time) for access_bit in $(echo "wa" | grep -o .) do # For each from the required access bits (e.g. 'w', 'a') check # if they are already present in current access bits for rule. # If not, append that bit at the end if ! grep -q "$access_bit" <<< "$current_access_bits" then # Concatenate the existing mask with the missing bit current_access_bits="$current_access_bits$access_bit" fi done # Propagate the updated rule's access bits (original + the required # ones) back into the /etc/audit/audit.rules file for that rule sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file" else # Rule isn't present yet. Append it at the end of $audit_rules_file file # with proper key echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file" fi done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then if LC_ALL=C grep -m 1 -q ^log_group /etc/audit/auditd.conf; then GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ') if ! [ "${GROUP}" == 'root' ] ; then chown root:${GROUP} /var/log/audit chown root:${GROUP} /var/log/audit/audit.log* else chown root:root /var/log/audit chown root:root /var/log/audit/audit.log* fi else chown root:root /var/log/audit chown root:root /var/log/audit/audit.log* fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then if LC_ALL=C grep -iw ^log_file /etc/audit/auditd.conf; then FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ') else FILE="/var/log/audit/audit.log" fi chmod 0600 $FILE else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_syslog_active="yes" AUDISP_SYSLOGCONFIG=/etc/audit/plugins.d/syslog.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^active") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_syslog_active" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^active\\>" "$AUDISP_SYSLOGCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^active\\>.*/$escaped_formatted_output/gi" "$AUDISP_SYSLOGCONFIG" else if [[ -s "$AUDISP_SYSLOGCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDISP_SYSLOGCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDISP_SYSLOGCONFIG" fi cce="CCE-80677-8" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDISP_SYSLOGCONFIG" >> "$AUDISP_SYSLOGCONFIG" printf '%s\n' "$formatted_output" >> "$AUDISP_SYSLOGCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_action_mail_acct='root' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^action_mail_acct") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_action_mail_acct" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^action_mail_acct\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^action_mail_acct\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80678-6" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_action_mail_acct='root' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^action_mail_acct") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_action_mail_acct" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^action_mail_acct\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^action_mail_acct\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80678-6" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_admin_space_left_action='single' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^admin_space_left_action") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_admin_space_left_action" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^admin_space_left_action\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^admin_space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80679-4" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_max_log_file='6' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^max_log_file") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_max_log_file" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^max_log_file\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^max_log_file\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80681-0" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_max_log_file_action='rotate' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^max_log_file_action") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_max_log_file_action" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^max_log_file_action\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^max_log_file_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80682-8" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_num_logs='5' AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^num_logs") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_num_logs" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^num_logs\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^num_logs\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80683-6" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then var_auditd_space_left_action='email' # # If space_left_action present in /etc/audit/auditd.conf, change value # to var_auditd_space_left_action, else # add "space_left_action = $var_auditd_space_left_action" to /etc/audit/auditd.conf # AUDITCONFIG=/etc/audit/auditd.conf # Strip any search characters in the key arg so that the key can be replaced without # adding any search characters to the config file. stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^space_left_action") # shellcheck disable=SC2059 printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_space_left_action" # If the key exists, change it. Otherwise, add it to the config_file. # We search for the key string followed by a word boundary (matched by \>), # so if we search for 'setting', 'setting2' won't match. if LC_ALL=C grep -q -m 1 -i -e "^space_left_action\\>" "$AUDITCONFIG"; then escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output") LC_ALL=C sed -i --follow-symlinks "s/^space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG" else if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG" fi cce="CCE-80684-4" printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG" printf '%s\n' "$formatted_output" >> "$AUDITCONFIG" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if ! rpm -q --quiet "audispd-plugins" ; then yum install -y "audispd-plugins" fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { rpm --quiet -q audit; }; then SYSTEMCTL_EXEC='/usr/bin/systemctl' "$SYSTEMCTL_EXEC" unmask 'auditd.service' "$SYSTEMCTL_EXEC" start 'auditd.service' "$SYSTEMCTL_EXEC" enable 'auditd.service' else >&2 echo 'Remediation is not applicable, nothing was done' fi #grub2 stuff. # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { rpm --quiet -q grub2-common; }; then grubby --update-kernel=ALL --args=audit=1 --env=/boot/grub2/grubenv else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then chgrp 0 /boot/grub2/grub.cfg else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then chown 0 /boot/grub2/grub.cfg else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then # List of log file paths to be inspected for correct permissions # * Primarily inspect log file paths listed in /etc/rsyslog.conf RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf" # * And also the log file paths listed after rsyslog's $IncludeConfig directive # (store the result into array for the case there's shell glob used as value of IncludeConfig) readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2) readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) readarray -t NEW_INC < <(awk '/)/{f=0} /include\(/{f=1} f{nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){print nf}}' /etc/rsyslog.conf) readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) # Declare an array to hold the final list of different log file paths declare -a LOG_FILE_PATHS # Array to hold all rsyslog config entries RSYSLOG_CONFIGS=() RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}") # Get full list of files to be checked # RSYSLOG_CONFIGS may contain globs such as # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files. RSYSLOG_CONFIG_FILES=() for ENTRY in "${RSYSLOG_CONFIGS[@]}" do # If directory, rsyslog will search for config files in recursively. # However, files in hidden sub-directories or hidden files will be ignored. if [ -d "${ENTRY}" ] then readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f) RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}") elif [ -f "${ENTRY}" ] then RSYSLOG_CONFIG_FILES+=("${ENTRY}") else echo "Invalid include object: ${ENTRY}" fi done # Browse each file selected above as containing paths of log files # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration) for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do # From each of these files extract just particular log file path(s), thus: # * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters, # * Ignore empty lines, # * Strip quotes and closing brackets from paths. # * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files # * From the remaining valid rows select only fields constituting a log file path # Text file column is understood to represent a log file path if and only if all of the # following are met: # * it contains at least one slash '/' character, # * it is preceded by space # * it doesn't contain space (' '), colon (':'), and semicolon (';') characters # Search log file for path(s) only in case it exists! if [[ -f "${LOG_FILE}" ]] then NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}") LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}") FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}") CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}") MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}") # Since above sed command might return more than one item (delimited by newline), split # the particular matches entries into new array specific for this log file readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS" # Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with # items from newly created array for this log file LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}") # Delete the temporary array unset ARRAY_FOR_LOG_FILE fi done # Check for RainerScript action log format which might be also multiline so grep regex is a bit # curly: # extract possibly multiline action omfile expressions # extract File="logfile" expression # match only "logfile" expression for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do ACTION_OMFILE_LINES=$(grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}") OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -aoP "File\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)") LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")") done # Ensure the correct attribute if file exists FILE_CMD="chgrp" for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}" do # Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing if [ -z "$LOG_FILE_PATH" ] then continue fi $FILE_CMD "0" "$LOG_FILE_PATH" done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then # List of log file paths to be inspected for correct permissions # * Primarily inspect log file paths listed in /etc/rsyslog.conf RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf" # * And also the log file paths listed after rsyslog's $IncludeConfig directive # (store the result into array for the case there's shell glob used as value of IncludeConfig) readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2) readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) readarray -t NEW_INC < <(awk '/)/{f=0} /include\(/{f=1} f{nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){print nf}}' /etc/rsyslog.conf) readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) # Declare an array to hold the final list of different log file paths declare -a LOG_FILE_PATHS # Array to hold all rsyslog config entries RSYSLOG_CONFIGS=() RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}") # Get full list of files to be checked # RSYSLOG_CONFIGS may contain globs such as # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files. RSYSLOG_CONFIG_FILES=() for ENTRY in "${RSYSLOG_CONFIGS[@]}" do # If directory, rsyslog will search for config files in recursively. # However, files in hidden sub-directories or hidden files will be ignored. if [ -d "${ENTRY}" ] then readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f) RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}") elif [ -f "${ENTRY}" ] then RSYSLOG_CONFIG_FILES+=("${ENTRY}") else echo "Invalid include object: ${ENTRY}" fi done # Browse each file selected above as containing paths of log files # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration) for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do # From each of these files extract just particular log file path(s), thus: # * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters, # * Ignore empty lines, # * Strip quotes and closing brackets from paths. # * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files # * From the remaining valid rows select only fields constituting a log file path # Text file column is understood to represent a log file path if and only if all of the # following are met: # * it contains at least one slash '/' character, # * it is preceded by space # * it doesn't contain space (' '), colon (':'), and semicolon (';') characters # Search log file for path(s) only in case it exists! if [[ -f "${LOG_FILE}" ]] then NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}") LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}") FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}") CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}") MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}") # Since above sed command might return more than one item (delimited by newline), split # the particular matches entries into new array specific for this log file readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS" # Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with # items from newly created array for this log file LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}") # Delete the temporary array unset ARRAY_FOR_LOG_FILE fi done # Check for RainerScript action log format which might be also multiline so grep regex is a bit # curly: # extract possibly multiline action omfile expressions # extract File="logfile" expression # match only "logfile" expression for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do ACTION_OMFILE_LINES=$(grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}") OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -aoP "File\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)") LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")") done # Ensure the correct attribute if file exists FILE_CMD="chown" for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}" do # Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing if [ -z "$LOG_FILE_PATH" ] then continue fi $FILE_CMD "0" "$LOG_FILE_PATH" done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then # List of log file paths to be inspected for correct permissions # * Primarily inspect log file paths listed in /etc/rsyslog.conf RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf" # * And also the log file paths listed after rsyslog's $IncludeConfig directive # (store the result into array for the case there's shell glob used as value of IncludeConfig) readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2) readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) readarray -t NEW_INC < <(awk '/)/{f=0} /include\(/{f=1} f{nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){print nf}}' /etc/rsyslog.conf) readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done) # Declare an array to hold the final list of different log file paths declare -a LOG_FILE_PATHS # Array to hold all rsyslog config entries RSYSLOG_CONFIGS=() RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}") # Get full list of files to be checked # RSYSLOG_CONFIGS may contain globs such as # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files. RSYSLOG_CONFIG_FILES=() for ENTRY in "${RSYSLOG_CONFIGS[@]}" do # If directory, rsyslog will search for config files in recursively. # However, files in hidden sub-directories or hidden files will be ignored. if [ -d "${ENTRY}" ] then readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f) RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}") elif [ -f "${ENTRY}" ] then RSYSLOG_CONFIG_FILES+=("${ENTRY}") else echo "Invalid include object: ${ENTRY}" fi done # Browse each file selected above as containing paths of log files # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration) for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do # From each of these files extract just particular log file path(s), thus: # * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters, # * Ignore empty lines, # * Strip quotes and closing brackets from paths. # * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files # * From the remaining valid rows select only fields constituting a log file path # Text file column is understood to represent a log file path if and only if all of the # following are met: # * it contains at least one slash '/' character, # * it is preceded by space # * it doesn't contain space (' '), colon (':'), and semicolon (';') characters # Search log file for path(s) only in case it exists! if [[ -f "${LOG_FILE}" ]] then NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}") LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}") FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}") CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}") MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}") # Since above sed command might return more than one item (delimited by newline), split # the particular matches entries into new array specific for this log file readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS" # Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with # items from newly created array for this log file LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}") # Delete the temporary array unset ARRAY_FOR_LOG_FILE fi done # Check for RainerScript action log format which might be also multiline so grep regex is a bit # curly: # extract possibly multiline action omfile expressions # extract File="logfile" expression # match only "logfile" expression for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}" do ACTION_OMFILE_LINES=$(grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}") OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -aoP "File\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)") LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")") done # Ensure the correct attribute if file exists FILE_CMD="chmod" for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}" do # Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing if [ -z "$LOG_FILE_PATH" ] then continue fi $FILE_CMD "0640" "$LOG_FILE_PATH" done else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then LOGROTATE_CONF_FILE="/etc/logrotate.conf" CRON_DAILY_LOGROTATE_FILE="/etc/cron.daily/logrotate" # daily rotation is configured grep -q "^daily$" $LOGROTATE_CONF_FILE|| echo "daily" >> $LOGROTATE_CONF_FILE # remove any line configuring weekly, monthly or yearly rotation sed -i '/^\s*\(weekly\|monthly\|yearly\).*$/d' $LOGROTATE_CONF_FILE # configure cron.daily if not already if ! grep -q "^[[:space:]]*/usr/sbin/logrotate[[:alnum:][:blank:][:punct:]]*$LOGROTATE_CONF_FILE$" $CRON_DAILY_LOGROTATE_FILE; then echo '#!/bin/sh' > $CRON_DAILY_LOGROTATE_FILE echo "/usr/sbin/logrotate $LOGROTATE_CONF_FILE" >> $CRON_DAILY_LOGROTATE_FILE fi else >&2 echo 'Remediation is not applicable, nothing was done' fi if ! rpm -q --quiet "libreswan" ; then yum install -y "libreswan" fi chgrp 0 /etc/group chgrp 0 /etc/passwd chgrp 0 /etc/shadow chown 0 /etc/group chown 0 /etc/passwd chown 0 /etc/shadow chmod u-xs,g-xws,o-xwt /etc/group chmod u-xs,g-xws,o-xwt /etc/passwd chmod u-xwrs,g-xwrs,o-xwrt /etc/shadow # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if rpm --quiet -q "chrony" ; then if ! /usr/sbin/pidof ntpd ; then /usr/bin/systemctl enable "chronyd" /usr/bin/systemctl start "chronyd" # The service may not be running because it has been started and failed, # so let's reset the state so OVAL checks pass. # Service should be 'inactive', not 'failed' after reboot though. if /usr/bin/systemctl --failed | grep -q "chronyd"; then /usr/bin/systemctl reset-failed "chronyd" fi fi elif rpm --quiet -q "ntp" ; then /usr/bin/systemctl enable "ntpd" /usr/bin/systemctl start "ntpd" # The service may not be running because it has been started and failed, # so let's reset the state so OVAL checks pass. # Service should be 'inactive', not 'failed' after reboot though. if /usr/bin/systemctl --failed | grep -q "ntpd"; then /usr/bin/systemctl reset-failed "ntpd" fi else if ! rpm -q --quiet "chrony" ; then yum install -y "chrony" fi /usr/bin/systemctl enable "chronyd" /usr/bin/systemctl start "chronyd" # The service may not be running because it has been started and failed, # so let's reset the state so OVAL checks pass. # Service should be 'inactive', not 'failed' after reboot though. if /usr/bin/systemctl --failed | grep -q "chronyd"; then /usr/bin/systemctl reset-failed "chronyd" fi fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then var_multiple_time_servers='0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org' config_file="/etc/ntp.conf" /usr/sbin/pidof ntpd || config_file="/etc/chrony.conf" if ! [ "$(grep -c '^server' "$config_file")" -gt 1 ] ; then if ! grep -q '#[[:space:]]*server' "$config_file" ; then for server in $(echo "$var_multiple_time_servers" | tr ',' '\n') ; do printf '\nserver %s' "$server" >> "$config_file" done else sed -i 's/#[ \t]*server/server/g' "$config_file" fi fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { ( [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && ( rpm --quiet -q chrony || rpm --quiet -q ntp ) ); }; then var_multiple_time_servers='0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org' config_file="/etc/ntp.conf" /usr/sbin/pidof ntpd || config_file="/etc/chrony.conf" if ! grep -q ^server "$config_file" ; then if ! grep -q '#[[:space:]]*server' "$config_file" ; then for server in $(echo "$var_multiple_time_servers" | tr ',' '\n') ; do printf '\nserver %s' "$server" >> "$config_file" done else sed -i 's/#[ \t]*server/server/g' "$config_file" fi fi else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then if [ -e "/etc/ssh/sshd_config" ] ; then LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config" else touch "/etc/ssh/sshd_config" fi # make sure file has newline at the end sed -i -e '$a\' "/etc/ssh/sshd_config" cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak" # Insert before the line matching the regex '^Match'. line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')" if [ -z "$line_number" ]; then # There was no match of '^Match', insert at # the end of the file. printf '%s\n' "ClientAliveCountMax 0" >> "/etc/ssh/sshd_config" else head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config" printf '%s\n' "ClientAliveCountMax 0" >> "/etc/ssh/sshd_config" tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config" fi # Clean up after ourselves. rm "/etc/ssh/sshd_config.bak" else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { grep -qP "^ID=[\"']?rhel[\"']?$" "/etc/os-release" && { real="$(grep -P "^VERSION_ID=[\"']?[\w.]+[\"']?$" /etc/os-release | sed "s/^VERSION_ID=[\"']\?\([^\"']\+\)[\"']\?$/\1/")"; expected="8.5"; printf "%s\n%s" "$real" "$expected" | sort -VC; }; }; then sshd_idle_timeout_value='900' if [ -e "/etc/ssh/sshd_config" ] ; then LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config" else touch "/etc/ssh/sshd_config" fi # make sure file has newline at the end sed -i -e '$a\' "/etc/ssh/sshd_config" cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak" # Insert before the line matching the regex '^Match'. line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')" if [ -z "$line_number" ]; then # There was no match of '^Match', insert at # the end of the file. printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" >> "/etc/ssh/sshd_config" else head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config" printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" >> "/etc/ssh/sshd_config" tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config" fi # Clean up after ourselves. rm "/etc/ssh/sshd_config.bak" else >&2 echo 'Remediation is not applicable, nothing was done' fi # Remediation is applicable only in certain platforms if rpm --quiet -q sssd-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then found=false # set value in all files if they contain section or key for f in $(echo -n "/etc/sssd/sssd.conf"); do if [ ! -e "$f" ]; then continue fi # find key in section and change value if grep -qzosP "[[:space:]]*\[pam\]([^\n\[]*\n+)+?[[:space:]]*pam_cert_auth" "$f"; then sed -i "s/pam_cert_auth[^(\n)]*/pam_cert_auth = True/" "$f" found=true # find section and add key = value to it elif grep -qs "[[:space:]]*\[pam\]" "$f"; then sed -i "/[[:space:]]*\[pam\]/a pam_cert_auth = True" "$f" found=true fi done # if section not in any file, append section with key = value to FIRST file in files parameter if ! $found ; then file=$(echo "/etc/sssd/sssd.conf" | cut -f1 -d ' ') mkdir -p "$(dirname "$file")" echo -e "[pam]\npam_cert_auth = True" >> "$file" fi if [ -f /usr/bin/authselect ]; then if authselect check; then if ! authselect check; then echo " authselect integrity check failed. Remediation aborted! This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact. It is not recommended to manually edit the PAM files when authselect tool is available. In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended." exit 1 fi authselect enable-feature with-smartcard authselect apply-changes -b fi else if ! grep -qP '^\s*auth\s+'"sufficient"'\s+pam_sss.so\s*.*' "/etc/pam.d/smartcard-auth"; then # Line matching group + control + module was not found. Check group + module. if [ "$(grep -cP '^\s*auth\s+.*\s+pam_sss.so\s*' "/etc/pam.d/smartcard-auth")" -eq 1 ]; then # The control is updated only if one single line matches. sed -i -E --follow-symlinks 's/^(\s*auth\s+).*(\bpam_sss.so.*)/\1'"sufficient"' \2/' "/etc/pam.d/smartcard-auth" else echo 'auth '"sufficient"' pam_sss.so' >> "/etc/pam.d/smartcard-auth" fi fi # Check the option if ! grep -qP '^\s*auth\s+'"sufficient"'\s+pam_sss.so\s*.*\sallow_missing_name\b' "/etc/pam.d/smartcard-auth"; then sed -i -E --follow-symlinks '/\s*auth\s+'"sufficient"'\s+pam_sss.so.*/ s/$/ allow_missing_name/' "/etc/pam.d/smartcard-auth" fi if ! grep -qP '^\s*auth\s+'"\[success=done authinfo_unavail=ignore ignore=ignore default=die\]"'\s+pam_sss.so\s*.*' "/etc/pam.d/system-auth"; then # Line matching group + control + module was not found. Check group + module. if [ "$(grep -cP '^\s*auth\s+.*\s+pam_sss.so\s*' "/etc/pam.d/system-auth")" -eq 1 ]; then # The control is updated only if one single line matches. sed -i -E --follow-symlinks 's/^(\s*auth\s+).*(\bpam_sss.so.*)/\1'"\[success=done authinfo_unavail=ignore ignore=ignore default=die\]"' \2/' "/etc/pam.d/system-auth" else echo 'auth '"\[success=done authinfo_unavail=ignore ignore=ignore default=die\]"' pam_sss.so' >> "/etc/pam.d/system-auth" fi fi # Check the option if ! grep -qP '^\s*auth\s+'"\[success=done authinfo_unavail=ignore ignore=ignore default=die\]"'\s+pam_sss.so\s*.*\stry_cert_auth\b' "/etc/pam.d/system-auth"; then sed -i -E --follow-symlinks '/\s*auth\s+'"\[success=done authinfo_unavail=ignore ignore=ignore default=die\]"'\s+pam_sss.so.*/ s/$/ try_cert_auth/' "/etc/pam.d/system-auth" fi fi else >&2 echo 'Remediation is not applicable, nothing was done' fi