|
#!/usr/bin/env bash |
|
# |
|
# install_auditd.sh |
|
# |
|
# Description: |
|
# Installs and configures auditd on a Debian-based system to log ALL commands |
|
# executed by users—both locally (TTY) and over SSH. Configures the forwarding |
|
# of audit events into syslog (which, by default, is then handled by rsyslog). |
|
# If a remote server is specified, a corresponding rsyslog snippet is created |
|
# to forward all logs (*.*) to that remote. |
|
# |
|
# This script checks for two possible plugin configurations: |
|
# 1) /etc/audisp/plugins.d/syslog.conf (older, separate audisp-syslog plugin). |
|
# 2) /etc/audit/plugins.d/af_unix.conf (newer, built-in “af_unix” plugin). |
|
# |
|
# Whichever plugin file exists will be configured to forward Auditd logs to |
|
# syslog. If neither file exists, an error is triggered. |
|
# |
|
# Each configuration step is idempotent—running this script multiple times |
|
# will not add duplicate lines or break the system’s final state. |
|
# |
|
# Usage: |
|
# curl -sS https://your.example.url/install_auditd.sh | sudo bash -s -- --remote "@graylog.example.com:514" |
|
# (Ensure you run as root or with sudo.) |
|
# |
|
# You can pass the remote server in one of two ways: |
|
# 1) As an environment variable LOGGING_REMOTE_SERVER="@graylog.example.com:514" |
|
# 2) Via the --remote CLI parameter. CLI takes precedence over environment. |
|
# |
|
# Example: |
|
# sudo bash install_auditd.sh --remote "@logserver.mydomain:6514;RSYSLOG_SyslogProtocol23Format" |
|
# |
|
# This will install and configure Auditd, enable either syslog.conf or |
|
# af_unix.conf depending on which is present, and it will place a snippet |
|
# under /etc/rsyslog.d/99-remote-syslog.conf if that remote is not already |
|
# part of the rsyslog configuration. |
|
# |
|
|
|
# Shell Options: |
|
# -E Inherit trap on functions |
|
# -e Exit immediately if a command fails |
|
# -u Treat unset variables as errors |
|
# -o pipefail Return the exit code of the first failed command in a pipeline |
|
set -Eeuo pipefail |
|
|
|
# Trap function to catch errors and unexpected exits. |
|
trap 'catchError $?' ERR |
|
|
|
############################################################################### |
|
# Globals |
|
############################################################################### |
|
readonly SCRIPT_NAME="${0##*/}" |
|
readonly AUDIT_RULES_FILE="/etc/audit/rules.d/audit.rules" |
|
readonly AUDIT_RULES_B64="-a always,exit -F arch=b64 -S execve -k exec_commands" |
|
readonly AUDIT_RULES_B32="-a always,exit -F arch=b32 -S execve -k exec_commands" |
|
|
|
# Paths for old-style vs. new-style plugin configuration: |
|
readonly AUDISP_SYSLOG_CONF="/etc/audisp/plugins.d/syslog.conf" |
|
readonly AF_UNIX_CONF="/etc/audit/plugins.d/af_unix.conf" |
|
|
|
# ----------------------------------------------------------------------------- |
|
# Remote logging global variables |
|
# ----------------------------------------------------------------------------- |
|
# Store the CLI-provided remote value (if any). |
|
REMOTE_SERVER_CLI="" |
|
# Store the environment-based remote value (if any). |
|
: "${LOGGING_REMOTE_SERVER:=""}" |
|
|
|
############################################################################### |
|
# Functions |
|
############################################################################### |
|
|
|
# ----------------------------------------------------------------------------- |
|
# catchError: Print an error message upon a script error or unexpected exit. |
|
# |
|
# Args: |
|
# $1 (int): The exit code of the command that triggered the ERR trap. |
|
# ----------------------------------------------------------------------------- |
|
catchError() { |
|
local -r exit_code="$1" |
|
echo "[ERROR] The script '${SCRIPT_NAME}' exited unexpectedly with code ${exit_code}." |
|
echo " Check the output above for details." |
|
exit "${exit_code}" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# checkRoot: Verify that the script is run as root (or via sudo). |
|
# ----------------------------------------------------------------------------- |
|
checkRoot() { |
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then |
|
echo "[ERROR] This script must be run as root or with sudo privileges." |
|
exit 1 |
|
fi |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# checkDebian: Verify we're running on a Debian-based system. |
|
# ----------------------------------------------------------------------------- |
|
checkDebian() { |
|
if [[ ! -f "/etc/debian_version" ]]; then |
|
echo "[ERROR] This script is intended for Debian-based systems only." |
|
exit 1 |
|
fi |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# installPackages: Install auditd and audispd-plugins if not already installed. |
|
# |
|
# This is idempotent because apt-get handles already-installed packages gracefully. |
|
# ----------------------------------------------------------------------------- |
|
installPackages() { |
|
echo "[INFO] Installing required packages: auditd, audispd-plugins..." |
|
apt-get update -y |
|
DEBIAN_FRONTEND=noninteractive apt-get install -y auditd audispd-plugins |
|
echo "[INFO] Package installation completed." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# configureAuditRules: Append rules for execve calls if missing, capturing |
|
# all commands (both local and SSH). Idempotent by checking for existing lines. |
|
# ----------------------------------------------------------------------------- |
|
configureAuditRules() { |
|
echo "[INFO] Configuring audit rules..." |
|
|
|
# Make sure the file exists before appending. |
|
if [[ ! -f "${AUDIT_RULES_FILE}" ]]; then |
|
touch "${AUDIT_RULES_FILE}" |
|
fi |
|
|
|
# Only add if missing. |
|
if ! grep -q "${AUDIT_RULES_B64}" "${AUDIT_RULES_FILE}" 2>/dev/null; then |
|
echo "${AUDIT_RULES_B64}" >> "${AUDIT_RULES_FILE}" |
|
echo "[INFO] Added b64 execve audit rule." |
|
else |
|
echo "[INFO] b64 execve audit rule already present. Skipping..." |
|
fi |
|
|
|
# Only add if missing. |
|
if ! grep -q "${AUDIT_RULES_B32}" "${AUDIT_RULES_FILE}" 2>/dev/null; then |
|
echo "${AUDIT_RULES_B32}" >> "${AUDIT_RULES_FILE}" |
|
echo "[INFO] Added b32 execve audit rule." |
|
else |
|
echo "[INFO] b32 execve audit rule already present. Skipping..." |
|
fi |
|
|
|
echo "[INFO] Audit rules have been appended if necessary." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# configureAudispSyslogPlugin: Enable the “syslog.conf” plugin if found. |
|
# This older plugin is used by distributions providing /etc/audisp/plugins.d/syslog.conf. |
|
# Example lines typically include: |
|
# active = yes |
|
# direction = out |
|
# path = /sbin/audisp-syslog |
|
# type = always |
|
# args = LOG_INFO |
|
# format = string |
|
# |
|
# We only fix up “active” and “args” because path, direction, type, and format |
|
# can differ per distribution. |
|
# ----------------------------------------------------------------------------- |
|
configureAudispSyslogPlugin() { |
|
echo "[INFO] Found old-style plugin at '${AUDISP_SYSLOG_CONF}'. Enabling it." |
|
|
|
# Ensure "active = yes" (idempotent). |
|
if grep -Eq '^[[:space:]]*active[[:space:]]*=' "${AUDISP_SYSLOG_CONF}"; then |
|
sed -i 's|^[#[:space:]]*active[[:space:]]*=.*|active = yes|' "${AUDISP_SYSLOG_CONF}" |
|
else |
|
echo "active = yes" >> "${AUDISP_SYSLOG_CONF}" |
|
fi |
|
echo "[INFO] 'active = yes' set in '${AUDISP_SYSLOG_CONF}'." |
|
|
|
# Ensure "args = LOG_INFO" (idempotent). |
|
if grep -Eq '^[[:space:]]*args[[:space:]]*=' "${AUDISP_SYSLOG_CONF}"; then |
|
sed -i 's|^[#[:space:]]*args[[:space:]]*=.*|args = LOG_INFO|' "${AUDISP_SYSLOG_CONF}" |
|
else |
|
echo "args = LOG_INFO" >> "${AUDISP_SYSLOG_CONF}" |
|
fi |
|
echo "[INFO] 'args = LOG_INFO' set in '${AUDISP_SYSLOG_CONF}'." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# configureAfUnixPlugin: Enable the newer “af_unix” plugin at /etc/audit/plugins.d/af_unix.conf |
|
# used by Debian 12 (Bookworm) with auditd 3.x. This plugin typically looks like: |
|
# active = yes |
|
# direction = out |
|
# path = builtin_af_unix |
|
# type = builtin |
|
# args = LOG_INFO |
|
# format = string |
|
# ----------------------------------------------------------------------------- |
|
configureAfUnixPlugin() { |
|
echo "[INFO] Found newer built-in af_unix plugin at '${AF_UNIX_CONF}'. Enabling it." |
|
|
|
# Ensure "active = yes". |
|
if grep -Eq '^[[:space:]]*active[[:space:]]*=' "${AF_UNIX_CONF}"; then |
|
sed -i 's|^[#[:space:]]*active[[:space:]]*=.*|active = yes|' "${AF_UNIX_CONF}" |
|
else |
|
echo "active = yes" >> "${AF_UNIX_CONF}" |
|
fi |
|
echo "[INFO] 'active = yes' set in '${AF_UNIX_CONF}'." |
|
|
|
# Ensure "args = LOG_INFO". |
|
if grep -Eq '^[[:space:]]*args[[:space:]]*=' "${AF_UNIX_CONF}"; then |
|
sed -i 's|^[#[:space:]]*args[[:space:]]*=.*|args = LOG_INFO|' "${AF_UNIX_CONF}" |
|
else |
|
echo "args = LOG_INFO" >> "${AF_UNIX_CONF}" |
|
fi |
|
echo "[INFO] 'args = LOG_INFO' set in '${AF_UNIX_CONF}'." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# configureAuditToSyslog: Since Debian Bookworm’s audisp-syslog plugin may not |
|
# exist, check for either syslog.conf or af_unix.conf, and configure whichever |
|
# is present. If neither is found, exit with an error. This ensures we can |
|
# forward audit events to syslog in an idempotent way. |
|
# ----------------------------------------------------------------------------- |
|
configureAuditToSyslog() { |
|
# If old-style syslog.conf is present: |
|
if [[ -f "${AUDISP_SYSLOG_CONF}" ]]; then |
|
configureAudispSyslogPlugin |
|
return |
|
fi |
|
|
|
# Else if the newer af_unix.conf is present: |
|
if [[ -f "${AF_UNIX_CONF}" ]]; then |
|
configureAfUnixPlugin |
|
return |
|
fi |
|
|
|
# If neither conf is present, user may be on a system with different packaging. |
|
echo "[ERROR] No recognized Auditd-to-Syslog plugin found." |
|
echo "[ERROR] Neither '${AUDISP_SYSLOG_CONF}' nor '${AF_UNIX_CONF}' is present." |
|
echo " Verify the correct plugin is installed for your distribution." |
|
exit 1 |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# restartAuditd: Ensure auditd is enabled and restarted to apply changes. |
|
# Idempotent because systemd doesn't duplicate services if they're already enabled. |
|
# ----------------------------------------------------------------------------- |
|
restartAuditd() { |
|
echo "[INFO] Enabling and restarting auditd..." |
|
systemctl enable auditd |
|
systemctl restart auditd |
|
echo "[INFO] auditd service is now active and running." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# parseArgs: Parse command line arguments for remote logging or help. |
|
# ----------------------------------------------------------------------------- |
|
parseArgs() { |
|
while [[ "$#" -gt 0 ]]; do |
|
case "$1" in |
|
--remote) |
|
if [[ -z "${2:-}" ]]; then |
|
echo "[ERROR] --remote requires a server argument." |
|
exit 1 |
|
fi |
|
REMOTE_SERVER_CLI="$2" |
|
shift 2 |
|
;; |
|
-h|--help) |
|
echo "Usage: ${SCRIPT_NAME} [--remote <server>]" |
|
echo "Optional environment variable LOGGING_REMOTE_SERVER can also be set." |
|
echo "If both are set, the command line parameter takes precedence." |
|
exit 0 |
|
;; |
|
*) |
|
echo "[ERROR] Unknown option: $1" |
|
echo "Use --help for usage details." |
|
exit 1 |
|
;; |
|
esac |
|
done |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# configureRemoteLogging: If a remote server is provided (CLI or environment), |
|
# create a snippet under /etc/rsyslog.d/99-remote-syslog.conf if it’s not |
|
# already in non-commented lines of any existing config. This is idempotent. |
|
# ----------------------------------------------------------------------------- |
|
configureRemoteLogging() { |
|
local remote="${REMOTE_SERVER_CLI:-${LOGGING_REMOTE_SERVER}}" |
|
|
|
if [[ -z "${remote}" ]]; then |
|
echo "[INFO] Remote logging not configured because no remote server was provided." |
|
return |
|
fi |
|
|
|
echo "[INFO] Attempting to configure remote rsyslog to: ${remote}" |
|
|
|
# If the line already exists (non-commented), do nothing. |
|
if grep -RsI -v '^[[:space:]]*#' "/etc/rsyslog.conf" "/etc/rsyslog.d" 2>/dev/null \ |
|
| grep -q "${remote}"; then |
|
echo "[INFO] Remote server '${remote}' is already present in the configuration." |
|
else |
|
echo "[INFO] Remote server not found in existing configuration." |
|
echo "[INFO] Creating /etc/rsyslog.d/99-remote-syslog.conf with '*.* ${remote}'" |
|
cat <<EOF > /etc/rsyslog.d/99-remote-syslog.conf |
|
*.* ${remote} |
|
EOF |
|
|
|
# Reload/r estart rsyslog to apply. |
|
echo "[INFO] Reloading rsyslog service..." |
|
systemctl reload rsyslog || systemctl restart rsyslog |
|
echo "[INFO] Remote logging to '${remote}' has been configured." |
|
fi |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# main: Main function to orchestrate the steps. |
|
# ----------------------------------------------------------------------------- |
|
main() { |
|
parseArgs "$@" |
|
checkRoot |
|
checkDebian |
|
installPackages |
|
configureAuditRules |
|
configureAuditToSyslog |
|
restartAuditd |
|
configureRemoteLogging |
|
|
|
echo "[INFO] All commands (local & SSH) are now being audited." |
|
echo "[INFO] Logs are stored in /var/log/audit/audit.log (by default)." |
|
echo "[INFO] If remote logging was configured, logs (including audit events) are forwarded via rsyslog." |
|
echo "[INFO] Script completed successfully." |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# Script entry point |
|
# ----------------------------------------------------------------------------- |
|
main "$@" |