You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

463 lines
13 KiB
Bash

#!/bin/bash
set -e
# detect running from git tree
if [ -f ./common.sh ] && [ -f "$0" ]
then
source common.sh
else
source /usr/share/linux-infosec-setupper/common.sh
fi
# make temporary files not accessible to non-root
# like auditd config is not accessible
umask 0077
# $1 - action
# $2 - param name
_audit_action_config(){
local l_failed=0
case "$1" in
ignore ) : ;;
syslog ) : ;;
rotate ) : ;;
email ) : ;;
suspend ) : ;;
single ) : ;;
halt ) : ;;
exec* )
if [[ "$1" =~ ^exec([[:space:]])*$ ]]; then
error $"Entered %s=exec /path/to/script does not contain a path to script" "$2"
l_failed=1
elif [ "$(echo "$1" | tr '[:space:]' '\n' | wc -l)" != 2 ]; then
error $"%s=exec* can have only one agrument — path to script, example: %s=exec /path/to/script" "$2" "$2"
l_failed=1
else
local path_to_script="$(echo "$1" | awk '{print $2}')"
if ! test -x "$path_to_script"; then
error $"Script %s is not executable" "$path_to_script"
l_failed=1
fi
fi
;;
* )
error $"Possible values of %s are: %s" "exec" "ignore, syslog, rotate, email, exec /path/to/script, suspend, single, halt"
l_failed=1
;;
esac
return "$l_failed"
}
_mk_systemd_auditd_override(){
local do_verify=1
if [ -z "$DESTDIR" ]; then do_verify=0; fi
# auditd.service: Command /sbin/auditd is not executable: Permission denied
if [ "$(id -u)" -ne 0 ] ; then do_verify=0; fi
# --IPAddressAllow=xxx --IPAddressDeny=xxx may be specified multiple times
local IPAddressAllow=""
local IPAddressDeny=""
while [ -n "$1" ]
do
case "$1" in
"--verify-disable" )
do_verify=0
;;
"--IPAddressAllow" )
shift
IPAddressAllow="${IPAddressAllow} $1"
;;
"--IPAddressDeny" )
shift
IPAddressDeny="${IPAddressDeny} $1"
;;
esac
shift
done
local systemd_override_dir="$(dirname "$AUDIT_DAEMON_SYSTEMD_OVERRIDE")"
if ! mkdir -p "$systemd_override_dir" ; then
error $"Error creating directory %s" "$systemd_override_dir"
return 1
fi
cat > "$AUDIT_DAEMON_SYSTEMD_OVERRIDE" << EOF
[Unit]
# change Before and After according to comments in auditd.service
After=
After=network-online.target local-fs.target systemd-tmpfiles-setup.service
Before=
Before=shutdown.target
[Service]
$(for i in $IPAddressAllow
do
echo "IPAddressAllow=$i"
done)
$(for i in $IPAddressDeny
do
echo "IPAddressDeny=$i"
done)
EOF
systemd_allowed_ip_list="$IPAddressAllow"
systemd_denied_ip_list="$IPAddressDeny"
# Make it work inside e.g. Anaconda module where $DESTDIR is not empty
# probably by copying the file to the root of the LiveCD.
# Detection of being run from Anaconda here is a prototype.
if [ "${I_AM_ANACONDA:-0}" != 0 ]; then
local cp_dst="$(echo "$AUDIT_DAEMON_SYSTEMD_OVERRIDE" | sed -e "s,^${DESTDIR},,")"
if cp "$AUDIT_DAEMON_SYSTEMD_OVERRIDE" "$cp_dst"
then
do_verify=1
else
error $"Error copying systemd override file %s to %s" "$AUDIT_DAEMON_SYSTEMD_OVERRIDE" "$cp_dst"
return 1
fi
fi
if [ "$do_verify" = 1 ]; then
local systemd_analyze_result="$(systemd-analyze verify auditd.service 2>&1)"
if [ $? != 0 ]; then
error $"Systemd unit file auditd.service with setted up packet filtering has not passed verification!"
error $"The error was:"
error "$systemd_analyze_result"
fi
fi
}
# can be used to reset variables to default values after loading previously setted up ones
_audit_variables(){
local_events="yes"
log_file="/var/log/audit/audit.log"
write_logs="yes"
log_format="ENRICHED"
log_group="root"
priority_boost="4"
flush="incremental_async"
freq=""
max_log_fileaction="rotate"
num_logs=3
# default is lossy, but let's better prevent potential loss of audit events
disp_qos="lossless"
# XXX why does audispd not exist in Fedora and Red OS 7.3? It exists in rosa2019.1
dispatcher=""
distribute_network="no"
# default is "none", but let's make logs better parsable and readable, e.g. on syslog server
name_format="hostname"
name=""
max_log_file=8
action_mail_acct=""
space_left="10%"
# log if free space is low but still enough,
# Zabbix etc. can be used to make notification in such case
space_left_action="syslog"
# poweroff system if logs cannot be stored according to configured policy (paranoidal)
disk_full_action="halt"
disk_error_action="halt"
tcp_listen_port=""
tcp_max_per_addr=""
systemd_allowed_ip_list=""
systemd_denied_ip_list=""
}
_mk_auditd_config(){
_audit_variables
local failed=0
# Bellow we go through all cli options and print all errors,
# not failing on the first one, showing all errors to the user
while [ -n "$1" ]
do
case "$1" in
"--local_events" ) shift;
_check_argument_is_boolean "$1" "local_events" || failed=1
local_events="$1"
shift;
;;
# We recommend using default /var/log/audit/audit.log to avoid mess
# with SELinux, log rotation (auditd rotates the log by itself by default
# but admins/distrobuilders may additionally setup logrotate.d
"--log_file" ) shift;
if _check_argument_is_string "$1" "log_file"
then
local dir="$(dirname "$1")"
if ! test -w "$dir" ; then
error $"Directory %s does not exist" "$dir"
failed=1
fi
log_file="$1"
else
failed=1
fi
;;
"--write_logs" ) shift;
_check_argument_is_boolean "$1" "write_logs" || failed=1
write_logs="$1"
;;
"--log_format" ) shift;
if ! { [ "$1" = "ENRICHED" ] || [ "$1" = "RAW" ] ;}; then
error $"Value of %s must be %s or %s" "log_format" "ENRICHED" "RAW"
failed=1
fi
log_format="$1"
;;
"--log_group" ) shift;
_check_argument_is_string "$1" "log_group" || failed=1
# We could try to resolve the group here, but NSS (/etc/nsswitch.conf) may be
# not yet configured or a connection to a domain (FreeIPA, LDAP, AD etc.)
# may be not yet estabilished, e.g. in a chroot when being run via Anaconda installer,
# so such a check does not make sense
log_group="$1"
;;
"--priority_boost" ) shift;
_check_argument_is_non_negative_number "$1" "priority_boost" || failed=1
priority_boost="$1"
;;
"--flush" ) shift;
_check_argument_is_string "$1" "flush" || failed=1
case "$1" in
none ) : ;;
incremental ) : ;;
incremental_async ) : ;;
data ) : ;;
sync ) : ;;
* )
error $"Possible values of %s are: %s" "flush" "none, incremental, incremental_async, data, sync"
failed=1
;;
esac
flush="$1"
;;
"--freq" ) shift;
if [ "$flush" = "incremental_async" ]; then
_check_argument_is_non_negative_number "$1" "freq" || failed=1
freq="$1"
else
error $"Parameter %s makes sense only when %s" "freq" "flush=incremental_async"
failed=1
fi
;;
"--max_log_fileaction" ) shift;
if _check_argument_is_string "$1" "max_log_file_action"
then
case "$1" in
ignore ) : ;;
syslog ) : ;;
suspend ) : ;;
rotate ) : ;;
keep_logs ) : ;;
* )
error $"Possible values of %s are: %s" "max_log_file_action" "ignore, syslog, suspend, rotate, keep_logs"
failed=1
;;
esac
max_log_fileaction="$1"
else
failed=1
fi
;;
"--num_logs" ) shift;
if [ "$max_log_fileaction" != "rotate" ]
then
error $"Parameter %s makes sense only when %s" "num_logs" "max_log_file_action=rotate"
failed=1
else
_check_argument_is_non_negative_number "$1" "num_logs" || failed=1
num_logs="$1"
fi
;;
"--disp_qos" ) shift;
if _check_argument_is_string "$1" "disp_qos"
then
case "$1" in
lossy ) : ;;
lossless ) : ;;
* )
error $"Possible values of %s are: %s" "disp_qos" "lossy, lossless"
failed=1
;;
esac
disp_qos="$1"
else
failed=1
fi
;;
"--dispatcher" ) shift;
if _check_argument_is_string "$1" "dispatcher"
then
if ! test -x "$1" ; then
error $"File %s does not exist or is not executable, so %s cannot be set as a dispatcher executable" "$1" "$1"
failed=1
fi
dispatcher="$1"
else
failed=1
fi
;;
"--distribute_network" ) shift;
if [ -n "$dispatcher" ]
then
_check_argument_is_boolean "$1" "distribute_network" || failed=1
else
error $"%s requires %s to be configured" "distribute_network" "dispatcher"
failed=1
fi
distribute_network="$1"
;;
"--name_format" ) shift;
if _check_argument_is_string "$1" "name_format"
then
case "$1" in
none ) : ;;
hostname ) : ;;
fqd ) : ;;
numeric ) : ;;
user ) : ;;
* )
error $"Possible values of %s are: %s" "disp_qos" "none, hostname, fqd, numeric, user"
failed=1
;;
esac
name_format="$1"
else
failed=1
fi
;;
"--name" ) shift;
if [ "$name_format" != "user" ]
then
error $"Parameter %s makes sense only when %s" "name" "name_format != user"
failed=1
else
name="$1"
fi
;;
"--max_log_file" ) shift;
_check_argument_is_non_negative_number "$1" "max_log_file" || failed=1
max_log_file="$1"
;;
"--action_mail_acct" ) shift;
_validate_email "$1" || failed=1
action_mail_acct="$1"
;;
"--space_left" ) shift;
local tmp_space_left="$1"
# last character of string (https://stackoverflow.com/a/21635778)
if [ "${tmp_space_left: -1}" = "%" ]; then
tmp_space_left="$(echo "$tmp_space_left" | sed -e 's,%$,,')"
_check_argument_is_non_negative_number "$tmp_space_left" "space_left" || failed=1
fi
_check_argument_is_non_negative_number "$tmp_space_left" "space_left" || failed=1
space_left="$1"
unset tmp_space_left
;;
"--space_left_action" ) shift;
if ! _audit_action_config "$1" "space_left_action" ; then
failed=1
fi
space_left_action="$1"
;;
"--disk_full_action" ) shift;
if ! _audit_action_config "$1" "disk_full_action" ; then
failed=1
fi
disk_full_action="$1"
;;
"--disk_error_action" ) shift;
if ! _audit_action_config "$1" "disk_error_action" ; then
failed=1
fi
disk_error_action="$1"
;;
# TODO: admin_space_left
# TODO: admin_space_left_action
# + compare with space_left, must be less according to the manual
# auditd can use tcp_wrappers, but it is a vulnerable, depreceated and not secure mechanism;
# we will not configure tcp_wrappers, instead we will configure list of allowed and disallowed
# IP addresses via systemd (http://0pointer.net/blog/ip-accounting-and-access-lists-with-systemd.html)
# Working as an audit server (listening a port) is disabled by default
"--tcp_listen_port" ) shift;
if _check_argument_is_non_negative_number "$1" "tcp_listen_port"
then
# 1..65535
if [ "$1" -lt 1 ] || [ "$1" -gt 65535 ]; then
error $"%s must be an integer between %s and %s" "tcp_listen_port" 1 65535
failed=1
fi
else
failed=1
fi
tcp_listen_port="$1"
;;
"--tcp_max_per_addr" ) shift;
if _check_argument_is_non_negative_number "$1" "tcp_max_per_addr"
then
# 1..65535
if [ "$1" -lt 1 ] || [ "$1" -gt 1024 ]; then
error $"%s must be an integer between %s and %s" "tcp_listen_port" 1 1024
failed=1
fi
else
failed=1
fi
tcp_max_per_addr="$1"
;;
# TODO: tcp_client_ports
# TODO: tcp_client_max_idle
# TODO: kerberos authentication against a Kerberos/Samba/FreeIPA server
# https://listman.redhat.com/archives/linux-audit/2019-April/msg00110.html
"--systemd-firewalling-params" ) shift;
_mk_systemd_auditd_override $*
;;
esac
shift
done
if [ "$failed" != 0 ]; then
error $"Errors occured when trying to understand how to configure auditd"
return 1
fi
if ! mkdir -p "${VAR_DIR_AUDIT}" ; then
error $"Error creating directory %s" "$config_dir"
return 1
fi
# can be sourced from GUI to load previous settings
cat > "${VAR_DIR_AUDIT}/auditd-conf.sh" << EOF
# Generated by linux-infosec-setupper
local_events="$local_events"
log_file="$log_file"
write_logs="$write_logs"
log_format="$log_format"
log_group="$log_group"
priority_boost="$priority_boost"
flush="$flush"
freq="$freq"
max_log_fileaction="$max_log_fileaction"
num_logs="$num_logs"
disp_qos="$disp_qos"
dispatcher="$dispatcher"
distribute_network="$distribute_network"
name_format="$name_format"
name="$name"
max_log_file="$max_log_file"
action_mail_acct="$action_mail_acct"
space_left="$space_left"
space_left_action="$space_left_action"
disk_full_action="$disk_full_action"
disk_error_action="$disk_error_action"
tcp_listen_port="$tcp_listen_port"
tcp_max_per_addr="$tcp_max_per_addr"
systemd_allowed_ip_list="$systemd_allowed_ip_list"
systemd_denied_ip_list="$systemd_denied_ip_list"
EOF
}
_write_auditd_config(){
local config_dir="$(dirname "$AUDIT_DAEMON_CONFIG")"
if ! mkdir -p "$config_dir" ; then
error $"Error creating directory %s" "$config_dir"
return 1
fi
if ! sed "${VAR_DIR_AUDIT}/auditd-conf.sh" -e '/^systemd_/d' -e 's,=, = ,' -e 's,",,g' -e '/= $/d' > "$AUDIT_DAEMON_CONFIG" ; then
error $"Error writing auditd config file %s" "$AUDIT_DAEMON_CONFIG"
fi
# auditd.service cannot be restarted, a reboot is required
echo $"Reboot to apply changes to auditd config"
}