IR Stack su Linux: rilevare modifiche, accessi e anomalie prima che diventino incidenti

Dalla teoria dell'incident detection alla pratica: auditd, systemd path/timer, identity checker, alert SSH, anti-tamper, guardian, maintenance window, lockdown opzionale e installer completo.

Quando un file sensibile cambia, la domanda utile non è soltanto “chi lo ha modificato?”. Prima ancora dobbiamo capire se quella modifica fosse prevista, se riguardasse contenuto o permessi, se altri elementi siano cambiati nello stesso intervallo di tempo e soprattutto se gli strumenti che avrebbero dovuto avvisarci fossero ancora vivi.

È da questo problema che nasce l’IR Stack. Non è un singolo programma e non vuole sostituire un SIEM. È piuttosto un insieme relativamente piccolo di componenti Linux già maturi, collegati in modo da coprire alcuni punti ciechi tipici di un server: modifiche agli account, variazioni di privilegi, chiavi SSH, accessi riusciti, alterazioni dello stesso stack di difesa e spegnimento dei servizi che dovrebbero sorvegliarlo.

Prevenzione e rilevamento sono due mestieri diversi

Un firewall può impedire una connessione. I permessi Unix possono impedire a un processo di leggere un file. SSH può rifiutare una password. Ma nessuno di questi strumenti, da solo, ci racconta bene che /etc/sudoers è stato modificato alle 03:14, che contemporaneamente è comparsa una nuova chiave in authorized_keys e che pochi secondi dopo il watcher è stato fermato.

Un sistema di incident response parte quindi da un principio diverso: assumiamo che qualcosa possa comunque cambiare e costruiamo abbastanza telemetria da accorgercene e ricostruirlo. Da qui l’uso contemporaneo di auditd, systemd path/timer, controlli logici sull’identità e watchdog sui guardian.

Perché non basta fare SHA256 di tutto

Il classico File Integrity Monitoring confronta una baseline con lo stato corrente. È utile, ma un hash da solo ci dice soltanto che due contenuti sono diversi. Non ci dice se la modifica sia avvenuta durante un aggiornamento autorizzato, chi abbia eseguito il comando, se il file abbia cambiato solo mode/owner oppure se l’evento faccia parte di una sequenza più ampia.

Nell’IR Stack usiamo quindi livelli differenti. auditd registra operazioni su file e comandi sensibili; systemd.path trasforma alcune modifiche in trigger immediati; un timer ripete comunque i controlli periodicamente; il checker ragiona sulla struttura degli account; il watcher protegge gli stessi script di difesa; il guardian verifica che path, timer e servizi non siano stati spenti.

Architettura dello stack

/etc/passwd, shadow, group, sudoers, sshd_config, authorized_keys
                 |
          +------+------+
          |             |
       auditd       systemd.path
          |             |
          +------+------+
                 |
        identity checker
                 |
        incident handler
                 |
        +--------+---------+
        |                  |
      mail/log       lockdown opzionale
                           |
                    rollback firewall

In parallelo:
journal SSH -> alert login riusciti
file stack  -> watcher anti-tamper
unità IR    -> guardian watchdog

1. auditd: la memoria forense

Auditd lavora a livello kernel e può registrare accessi o modifiche a file sensibili e l’esecuzione di determinati programmi. Le chiavi identity, privilege, sshcfg, sshkeys, user_mgmt e ir_stack permettono poi di isolare rapidamente gli eventi con ausearch.

auditctl -l
ausearch -k identity -ts recent -i
ausearch -k privilege -ts recent -i
ausearch -k ir_stack -ts recent -i

Il vantaggio rispetto a un semplice watcher è il contesto: non vediamo soltanto che il file è cambiato, ma possiamo spesso risalire al processo, all’utente e alla syscall coinvolta.

2. Il checker di identità

Alcune anomalie non sono “file cambiato = incidente”. Un secondo account con UID 0, invece, è quasi sempre qualcosa che merita attenzione. Lo stesso vale per un account di sistema che improvvisamente ottiene una shell interattiva reale. Il checker non si limita quindi agli hash: interpreta /etc/passwd, login.defs, le shell e una piccola whitelist locale per gli account di servizio legittimi.

È importante la whitelist. In un PBX, per esempio, l’utente asterisk può avere caratteristiche differenti da quelle attese da una regola generica. Un buon controllo non deve fingere che tutti i server siano uguali: deve consentire eccezioni documentate e specifiche.

3. Path e timer: immediatezza più ridondanza

systemd.path ci dà reazione quasi immediata: quando cambia uno dei file sorvegliati parte l’handler. Ma basarsi solo sugli eventi è fragile. Per questo esiste anche un timer periodico che ripete il controllo. Se un evento viene perso, se il watcher viene riavviato o se una condizione anomala non dipende da una singola scrittura, il controllo periodico ci offre una seconda possibilità.

4. Alert sugli accessi SSH riusciti

Molti sistemi avvisano sui tentativi falliti e ignorano il dato più interessante: un login riuscito. Lo script SSH conserva il cursor di journald e analizza soltanto le nuove righe Accepted publickey, Accepted password o equivalenti. L’alert contiene utente, IP, metodo e riga originale del log.

Il cursor è importante: senza stato, un parser lanciato periodicamente rischia di inviare più volte la stessa segnalazione. Al primo avvio lo script inizializza il cursor senza notificare eventi storici; da quel momento procede in avanti.

5. Chi sorveglia i sorveglianti?

Se un attaccante ottiene privilegi sufficienti, una delle prime mosse sensate è spegnere gli strumenti che potrebbero denunciarlo. Per questo lo stack controlla anche se stesso. Le modifiche a script, configurazioni e unità systemd generano eventi audit con chiave ir_stack e attivano un watcher dedicato.

Il guardian aggiunge un altro livello: verifica continuamente che path, timer e servizi fondamentali siano attivi. Nella versione produttiva alcuni guardian possono essere autoriparati, mentre servizi esterni importanti possono essere lasciati in modalità alert-only: riavviare automaticamente tutto ciò che cade sarebbe comodo, ma potrebbe anche nascondere la causa reale di un guasto.

6. Maintenance window

Il caso più fastidioso per un sistema del genere è l’aggiornamento legittimo. Un deploy o un upgrade può modificare proprio i file che stiamo proteggendo. La soluzione non è spegnere auditd: durante una maintenance window continuiamo a registrare gli eventi, ma sopprimiamo escalation, mail ripetitive e lockdown. In produzione usiamo un marker temporaneo con una scadenza; se il marker viene dimenticato, scade da solo.

7. Lockdown: separato e disabilitato di default

L’isolamento automatico è potente e pericoloso. Una falsa rilevazione non deve trasformarsi in un server che si taglia fuori dalla rete. L’installer pubblico crea gli strumenti di lockdown e rollback, ma ENABLE_LOCKDOWN parte su no. Prima di abilitarlo bisogna testare indirizzo amministrativo fidato, porta SSH, backup delle regole firewall e procedura di rollback da console.

Installer completo

Quello seguente deriva dall’installer realmente usato durante lo sviluppo dello stack. È stato reso standalone: non dipende dal setup NewVoip, usa valori neutri per IP/mail e non applica alcun lockdown durante l’installazione. Prima di usarlo in produzione va comunque letto e adattato ai servizi del server.

Nota importante: questa è la baseline standalone pubblicabile dello stack. La variante NewVoip attualmente in produzione aggiunge prefissi newvoip-*, maintenance window con marker temporaneo, guardian specifici per i servizi del PBX/Edge e integrazioni che non avrebbe senso imporre a un server Linux generico. L’architettura resta la stessa, ma la parte host-specific va sempre adattata.

Salviamolo come install-ir-stack.sh, rendiamolo eseguibile e lanciamolo impostando almeno mail di alert e, se vogliamo predisporre il lockdown, l’IP amministrativo fidato:

chmod 700 install-ir-stack.sh

TRUSTED_IPV4="192.0.2.10" ALERT_EMAIL="alert@example.net" ENABLE_LOCKDOWN="no" bash ./install-ir-stack.sh
#!/usr/bin/env bash

set -euo pipefail

# Installer standalone: nessuna dipendenza da librerie esterne



# ============================================================

# Incident Response / Identity Protection Stack Installer

# Debian / Raspberry / systemd / auditd / iptables

# ============================================================

TRUSTED_IPV4="${TRUSTED_IPV4:-}"

TRUSTED_IPV6="${TRUSTED_IPV6:-}"

SSH_PORT="${SSH_PORT:-22}"

ALERT_EMAIL="${ALERT_EMAIL:-alert@example.net}"

ENABLE_LOCKDOWN="${ENABLE_LOCKDOWN:-no}"

RELAYHOST="${RELAYHOST:-mail.example.net}"

RELAYPORT="${RELAYPORT:-25}"

if [[ -z "$TRUSTED_IPV4" ]]; then

  echo "Errore: devi esportare TRUSTED_IPV4 prima di lanciare lo script."

  echo 'Esempio: TRUSTED_IPV4="192.0.2.10" ALERT_EMAIL="alert@example.net" bash install-ir-stack.sh'

  exit 1

fi

echo "[*] Preconfiguro Postfix..."

MAIL_FQDN="$(hostname -f 2>/dev/null || true)"

case "$MAIL_FQDN" in

  ""|localhost|localhost.localdomain) MAIL_FQDN="$(hostname)" ;;

esac

echo "$MAIL_FQDN" >/etc/mailname

export DEBIAN_FRONTEND=noninteractive

debconf-set-selections <<EOF

postfix postfix/mailname string ${MAIL_FQDN}

postfix postfix/main_mailer_type select Satellite system

postfix postfix/relayhost string [${RELAYHOST}]:${RELAYPORT}

EOF

echo "[*] Installo pacchetti..."

apt update

apt install -y auditd audispd-plugins iptables bsd-mailx postfix util-linux

echo "[*] Creo directory..."

install -d -m 700 /etc/incident-response

install -d -m 700 /root/firewall-backups

install -d -m 700 /var/lib/incident-response

install -d -m 700 /var/lib/incident-response/guardians-watchdog

echo "[*] Creo configurazione..."

cat >/etc/incident-response/lockdown.conf <<EOF

TRUSTED_IPV4="${TRUSTED_IPV4}"

TRUSTED_IPV6="${TRUSTED_IPV6}"

SSH_PORT="${SSH_PORT}"

ENABLE_LOCKDOWN="${ENABLE_LOCKDOWN}"

ALERT_EMAIL="${ALERT_EMAIL}"

EOF

chmod 600 /etc/incident-response/lockdown.conf

touch /etc/incident-response/identity-shell-whitelist

cat > /etc/incident-response/identity-shell-whitelist <<EOF

asterisk

EOF

chmod 600 /etc/incident-response/identity-shell-whitelist

echo "[*] Creo regole audit..."

cat >/etc/audit/rules.d/40-identity-watch.rules <<'EOF'

## File identità e privilegi

-w /etc/passwd                -p wa -k identity

-w /etc/shadow                -p wa -k identity

-w /etc/group                 -p wa -k identity

-w /etc/gshadow               -p wa -k identity

-w /etc/sudoers               -p wa -k privilege

-w /etc/sudoers.d/            -p wa -k privilege

## SSH

-w /etc/ssh/sshd_config       -p wa -k sshcfg

-w /root/.ssh/authorized_keys -p wa -k sshkeys

## Comandi sensibili

-w /usr/sbin/useradd          -p x -k user_mgmt

-w /usr/sbin/usermod          -p x -k user_mgmt

-w /usr/sbin/userdel          -p x -k user_mgmt

-w /usr/bin/passwd            -p x -k passwd_mgmt

-w /usr/sbin/visudo           -p x -k sudo_change

EOF

cat >/etc/audit/rules.d/41-ir-stack.rules <<'EOF'

## Protezione stack di difesa

-w /usr/local/sbin/check-identity-anomalies.sh    -p wa -k ir_stack

-w /usr/local/sbin/emergency-lockdown.sh          -p wa -k ir_stack

-w /usr/local/sbin/emergency-lockdown-rollback.sh -p wa -k ir_stack

-w /usr/local/sbin/identity-incident-handler.sh   -p wa -k ir_stack

-w /usr/local/sbin/ssh-login-alert.sh             -p wa -k ir_stack

-w /usr/local/sbin/ir-stack-watch.sh              -p wa -k ir_stack

-w /usr/local/sbin/ir-guardians-watchdog.sh       -p wa -k ir_stack

-w /usr/local/sbin/irctl                          -p wa -k ir_stack

-w /etc/incident-response/lockdown.conf             -p wa -k ir_stack

-w /etc/incident-response/identity-shell-whitelist  -p wa -k ir_stack

-w /etc/systemd/system/identity-incident-from-path.service   -p wa -k ir_stack

-w /etc/systemd/system/identity-incident-from-timer.service  -p wa -k ir_stack

-w /etc/systemd/system/identity-incident-manual.service      -p wa -k ir_stack

-w /etc/systemd/system/identity-incident-handler.path        -p wa -k ir_stack

-w /etc/systemd/system/identity-incident-handler.timer       -p wa -k ir_stack

-w /etc/systemd/system/ssh-login-alert.service               -p wa -k ir_stack

-w /etc/systemd/system/ssh-login-alert.path                  -p wa -k ir_stack

-w /etc/systemd/system/ir-stack-watch.service                -p wa -k ir_stack

-w /etc/systemd/system/ir-stack-watch.path                   -p wa -k ir_stack

-w /etc/systemd/system/ir-guardians-watchdog.service         -p wa -k ir_stack

-w /etc/systemd/system/ir-guardians-watchdog.timer           -p wa -k ir_stack

EOF

echo "[*] Creo checker identità..."

cat >/usr/local/sbin/check-identity-anomalies.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

PASSWD_FILE="/etc/passwd"

LOGIN_DEFS="/etc/login.defs"

SSHD_CFG="/etc/ssh/sshd_config"

LOCAL_WHITELIST="/etc/incident-response/identity-shell-whitelist"

UID_MIN=$(awk '/^[[:space:]]*UID_MIN[[:space:]]+/ {print $2}' "$LOGIN_DEFS" 2>/dev/null | tail -n1)

SYS_UID_MIN=$(awk '/^[[:space:]]*SYS_UID_MIN[[:space:]]+/ {print $2}' "$LOGIN_DEFS" 2>/dev/null | tail -n1)

SYS_UID_MAX=$(awk '/^[[:space:]]*SYS_UID_MAX[[:space:]]+/ {print $2}' "$LOGIN_DEFS" 2>/dev/null | tail -n1)

UID_MIN="${UID_MIN:-1000}"

SYS_UID_MIN="${SYS_UID_MIN:-101}"

SYS_UID_MAX="${SYS_UID_MAX:-$((UID_MIN-1))}"

ALERT=0

HOST="$(hostname -f 2>/dev/null || hostname)"

is_interactive_shell() {

    local shell="${1:-}"

    case "$shell" in

        /bin/bash|/bin/sh|/bin/dash|/bin/zsh|/bin/ksh|/bin/tcsh|/bin/csh|/usr/bin/fish)

            return 0

            ;;

        *)

            return 1

            ;;

    esac

}

is_whitelisted_user() {

    local user="${1:-}"

    [ -f "$LOCAL_WHITELIST" ] || return 1

    grep -qxF "$user" "$LOCAL_WHITELIST"

}

echo "[$(date --iso-8601=seconds)] host=$HOST"

echo "== Account con UID 0 =="

awk -F: '($3 == 0) {print}' "$PASSWD_FILE"

UID0_COUNT=$(awk -F: '($3 == 0) {c++} END {print c+0}' "$PASSWD_FILE")

if [ "$UID0_COUNT" -gt 1 ]; then

    echo "ALERT: trovati $UID0_COUNT account con UID 0"

    ALERT=1

fi

if ! awk -F: '($1=="root" && $3==0) {found=1} END {exit !found}' "$PASSWD_FILE"; then

    echo "ALERT: account root mancante o con UID non 0"

    ALERT=1

fi

echo

echo "== System account con shell interattiva reale =="

while IFS=: read -r user _ uid gid gecos home shell; do

    if [ "$uid" -ge "$SYS_UID_MIN" ] && [ "$uid" -le "$SYS_UID_MAX" ]; then

        if is_whitelisted_user "$user"; then

            continue

        fi

        if is_interactive_shell "$shell"; then

            echo "${user}\:x:${uid}:${gid}:${gecos}:${home}:${shell}"

            echo "ALERT: system account '$user' ha shell interattiva reale ($shell)"

            ALERT=1

        fi

    fi

done < "$PASSWD_FILE"

echo

echo "== Account sotto UID_MIN con shell interattiva reale (escluso root) =="

while IFS=: read -r user _ uid gid gecos home shell; do

    if [ "$user" != "root" ] && [ "$uid" -lt "$UID_MIN" ]; then

        if is_whitelisted_user "$user"; then

            continue

        fi

        if is_interactive_shell "$shell"; then

            echo "${user}\:x:${uid}:${gid}:${gecos}:${home}:${shell}"

            echo "ALERT: account '$user' sotto UID_MIN con shell interattiva reale ($shell)"

            ALERT=1

        fi

    fi

done < "$PASSWD_FILE"

echo

echo "== authorized_keys sensibili =="

for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do

    [ -f "$f" ] || continue

    stat --printf '%n owner=%U group=%G mode=%a mtime=%y\n' "$f"

done

echo

echo "== sshd_config =="

if [ -f "$SSHD_CFG" ]; then

    grep -Ei '^(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|AllowUsers|AllowGroups|Match|LogLevel)' "$SSHD_CFG" || true

fi

exit "$ALERT"

EOF

chmod 700 /usr/local/sbin/check-identity-anomalies.sh

echo "[*] Creo script lockdown..."

cat >/usr/local/sbin/emergency-lockdown.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

TRUSTED_IPV4="${1:-}"

TRUSTED_IPV6="${2:-}"

SSH_PORT="${SSH_PORT:-22}"

BACKUP_DIR="/root/firewall-backups"

TS="$(date +%F_%H%M%S)"

if [[ -z "$TRUSTED_IPV4" ]]; then

  echo "Uso: $0 <trusted_ipv4> [trusted_ipv6]"

  exit 1

fi

mkdir -p "$BACKUP_DIR"

iptables-save  > "${BACKUP_DIR}/iptables_${TS}.rules"

ip6tables-save > "${BACKUP_DIR}/ip6tables_${TS}.rules"

iptables -F

iptables -X

iptables -t nat -F

iptables -t nat -X

iptables -t mangle -F

iptables -t mangle -X

iptables -P INPUT DROP

iptables -P FORWARD DROP

iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

iptables -A INPUT -p tcp -s "$TRUSTED_IPV4" --dport "$SSH_PORT" -m conntrack --ctstate NEW -j ACCEPT

iptables -A INPUT -p icmp -s "$TRUSTED_IPV4" -j ACCEPT

ip6tables -F

ip6tables -X

ip6tables -t mangle -F

ip6tables -t mangle -X

ip6tables -P INPUT DROP

ip6tables -P FORWARD DROP

ip6tables -P OUTPUT ACCEPT

ip6tables -A INPUT -i lo -j ACCEPT

ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

if [[ -n "$TRUSTED_IPV6" ]]; then

  ip6tables -A INPUT -p tcp -s "$TRUSTED_IPV6" --dport "$SSH_PORT" -m conntrack --ctstate NEW -j ACCEPT

  ip6tables -A INPUT -p ipv6-icmp -s "$TRUSTED_IPV6" -j ACCEPT

fi

logger -t emergency-lockdown "Lockdown applicato. SSH consentito solo da IPv4=${TRUSTED_IPV4} IPv6=${TRUSTED_IPV6:-none} porta=${SSH_PORT}"

echo "Lockdown applicato."

EOF

chmod 700 /usr/local/sbin/emergency-lockdown.sh

cat >/usr/local/sbin/emergency-lockdown-rollback.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

RULES_V4="${1:-}"

RULES_V6="${2:-}"

if [[ -z "$RULES_V4" || -z "$RULES_V6" ]]; then

  echo "Uso: $0 <backup_ipv4.rules> <backup_ipv6.rules>"

  exit 1

fi

iptables-restore  < "$RULES_V4"

ip6tables-restore < "$RULES_V6"

logger -t emergency-lockdown "Firewall ripristinato da backup"

echo "Regole ripristinate."

EOF

chmod 700 /usr/local/sbin/emergency-lockdown-rollback.sh

echo "[*] Creo handler principale..."

cat >/usr/local/sbin/identity-incident-handler.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

CONF="/etc/incident-response/lockdown.conf"

LOGFILE="/var/log/identity-incident-handler.log"

SOURCE="${1:-unknown}"

[ -f "$CONF" ] && . "$CONF"

TRUSTED_IPV4="${TRUSTED_IPV4:-}"

TRUSTED_IPV6="${TRUSTED_IPV6:-}"

SSH_PORT="${SSH_PORT:-22}"

ENABLE_LOCKDOWN="${ENABLE_LOCKDOWN:-no}"

ALERT_EMAIL="${ALERT_EMAIL:-}"

HOST="$(hostname -f 2>/dev/null || true)"

case "$HOST" in

  ""|localhost|localhost.localdomain) HOST="$(hostname)" ;;

esac

send_mail() {

  local subject="$1"

  local body="$2"

  if [[ -n "$ALERT_EMAIL" ]] && command -v mail >/dev/null 2>&1; then

    printf '%s\n' "$body" | mail -s "$subject" "$ALERT_EMAIL" || true

  fi

}

{

  echo "============================================================"

  echo "[$(date --iso-8601=seconds)] source=${SOURCE} avvio controllo identità"

} >> "$LOGFILE" 2>&1

if /usr/local/sbin/check-identity-anomalies.sh >> "$LOGFILE" 2>&1; then

  logger -t identity-incident-handler "SOURCE=${SOURCE} Nessuna anomalia critica"

  exit 0

fi

logger -t identity-incident-handler "SOURCE=${SOURCE} ANOMALIA CRITICA rilevata"

BODY="$(tail -n 120 "$LOGFILE")"

send_mail "[ALERT] Identity anomaly su ${HOST}" "$BODY"

if [[ "$ENABLE_LOCKDOWN" == "yes" && -n "$TRUSTED_IPV4" ]]; then

  SSH_PORT="$SSH_PORT" /usr/local/sbin/emergency-lockdown.sh "$TRUSTED_IPV4" "$TRUSTED_IPV6" >> "$LOGFILE" 2>&1

  logger -t identity-incident-handler "SOURCE=${SOURCE} Lockdown applicato"

  send_mail "[LOCKDOWN] Server isolato ${HOST}" "$(tail -n 120 "$LOGFILE")"

else

  logger -t identity-incident-handler "SOURCE=${SOURCE} Lockdown NON applicato: config incompleta o disabilitata"

fi

EOF

chmod 700 /usr/local/sbin/identity-incident-handler.sh

echo "[*] Creo script alert login SSH riusciti..."

cat >/usr/local/sbin/ssh-login-alert.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

CONF="/etc/incident-response/lockdown.conf"

STATE_DIR="/var/lib/ssh-login-alert"

STATE_FILE="${STATE_DIR}/last_cursor"

LOCK_FILE="/run/ssh-login-alert.lock"

TMP_FILE="$(mktemp)"

cleanup() {

    rm -f "$TMP_FILE"

}

trap cleanup EXIT

exec 9>"$LOCK_FILE"

flock -n 9 || exit 0

[ -f "$CONF" ] && . "$CONF"

ALERT_EMAIL="${ALERT_EMAIL:-}"

mkdir -p "$STATE_DIR"

chmod 700 "$STATE_DIR"

HOST="$(hostname -f 2>/dev/null || true)"

case "$HOST" in

  ""|localhost|localhost.localdomain) HOST="$(hostname)" ;;

esac

# Piccolo ritardo per lasciare sedimentare tutte le righe del login

sleep 2

if [[ ! -f "$STATE_FILE" ]] || [[ ! -s "$STATE_FILE" ]]; then

    journalctl _COMM=sshd -n 1 --show-cursor -o short-iso 2>/dev/null \\

        | sed -n 's/^-- cursor: //p' > "$STATE_FILE" || true

    chmod 600 "$STATE_FILE" 2>/dev/null || true

    exit 0

fi

CURSOR_OPT=(--after-cursor "$(cat "$STATE_FILE")")

journalctl _COMM=sshd -o short-iso --show-cursor "${CURSOR_OPT[@]}" > "$TMP_FILE" 2>/dev/null || true

while read -r line; do

    [ -n "$line" ] || continue

    TS="$(echo "$line" | awk '{print $1" "$2}')"

    METHOD="$(echo "$line" | sed -n 's/.*Accepted \\([^ ]*\\).*/\1/p')"

    USERNAME="$(echo "$line" | sed -n 's/.* for \\([^ ]*\\) from .*/\1/p')"

    IPADDR="$(echo "$line" | sed -n 's/.* from \\([^ ]*\\) port .*/\1/p')"

    logger -t ssh-login-alert "login riuscito utente=${USERNAME} metodo=${METHOD} ip=${IPADDR}"

    if [[ -n "${ALERT_EMAIL:-}" ]] && command -v mail >/dev/null 2>&1; then

        cat <<MAIL | mail -s "[SSH LOGIN] ${HOST} utente=${USERNAME} ip=${IPADDR}" "$ALERT_EMAIL" || true

Host: ${HOST}

Data: ${TS}

Utente: ${USERNAME}

IP: ${IPADDR}

Metodo: ${METHOD}

Riga log:

${line}

MAIL

    fi

done < <(grep -E 'Accepted (publickey|password|keyboard-interactive|keyboard-interactive/pam)' "$TMP_FILE" || true)

sed -n 's/^-- cursor: //p' "$TMP_FILE" | tail -n 1 > "$STATE_FILE" || true

chmod 600 "$STATE_FILE" 2>/dev/null || true

exit 0

EOF

chmod 700 /usr/local/sbin/ssh-login-alert.sh

echo "[*] Creo watcher stack IR..."

cat >/usr/local/sbin/ir-stack-watch.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

CONF="/etc/incident-response/lockdown.conf"

LOGFILE="/var/log/ir-stack-watch.log"

[ -f "$CONF" ] && . "$CONF"

ALERT_EMAIL="${ALERT_EMAIL:-}"

HOST="$(hostname -f 2>/dev/null || true)"

case "$HOST" in

  ""|localhost|localhost.localdomain) HOST="$(hostname)" ;;

esac

send_mail() {

  local subject="$1"

  local body="$2"

  if [[ -n "${ALERT_EMAIL:-}" ]] && command -v mail >/dev/null 2>&1; then

    printf '%s\n' "$body" | mail -s "$subject" "$ALERT_EMAIL" || true

  fi

}

{

  echo "============================================================"

  echo "[$(date --iso-8601=seconds)] modifica rilevata nello stack IR su ${HOST}"

  echo

  echo "Ultimi eventi audit ir_stack:"

  ausearch -k ir_stack -ts recent 2>/dev/null || true

} >> "$LOGFILE" 2>&1

logger -t ir-stack-watch "Modifica rilevata nello stack IR"

send_mail "[IR STACK] Modifica stack difesa su ${HOST}" "$(tail -n 200 "$LOGFILE")"

EOF

chmod 700 /usr/local/sbin/ir-stack-watch.sh

echo "[*] Creo watchdog dei guardiani..."

cat >/usr/local/sbin/ir-guardians-watchdog.sh <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

CONF="/etc/incident-response/lockdown.conf"

STATE_DIR="/var/lib/incident-response/guardians-watchdog"

LOCK_FILE="/run/ir-guardians-watchdog.lock"

[ -f "$CONF" ] && . "$CONF"

ALERT_EMAIL="${ALERT_EMAIL:-}"

mkdir -p "$STATE_DIR"

chmod 700 "$STATE_DIR"

exec 9>"$LOCK_FILE"

flock -n 9 || exit 0

HOST="$(hostname -f 2>/dev/null || true)"

case "$HOST" in

  ""|localhost|localhost.localdomain) HOST="$(hostname)" ;;

esac

send_mail() {

  local subject="$1"

  local body="$2"

  if [[ -n "${ALERT_EMAIL:-}" ]] && command -v mail >/dev/null 2>&1; then

    printf '%s\n' "$body" | mail -s "$subject" "$ALERT_EMAIL" || true

  fi

}

now="$(date +%s)"

GUARDIANS=(

  "identity-incident-handler.path"

  "identity-incident-handler.timer"

  "ssh-login-alert.path"

  "ir-stack-watch.path"

  "ir-guardians-watchdog.timer"

)

handle_guardian() {

  local unit="$1"

  local safe="${unit//[^A-Za-z0-9_.-]/_}"

  local down_since_file="${STATE_DIR}/${safe}.down_since"

  local alert_sent_file="${STATE_DIR}/${safe}.alert_sent"

  local active_state failed_state

  active_state="$(systemctl is-active "$unit" 2>/dev/null || true)"

  failed_state="$(systemctl is-failed "$unit" 2>/dev/null || true)"

  if [[ "$active_state" == "active" && "$failed_state" != "failed" ]]; then

    rm -f "$down_since_file" "$alert_sent_file"

    return 0

  fi

  if [[ ! -f "$down_since_file" ]]; then

    echo "$now" > "$down_since_file"

  fi

  systemctl reset-failed "$unit" >/dev/null 2>&1 || true

  systemctl start "$unit" >/dev/null 2>&1 || true

  sleep 2

  local active_state2 failed_state2

  active_state2="$(systemctl is-active "$unit" 2>/dev/null || true)"

  failed_state2="$(systemctl is-failed "$unit" 2>/dev/null || true)"

  if [[ "$active_state2" == "active" && "$failed_state2" != "failed" ]]; then

    rm -f "$down_since_file" "$alert_sent_file"

    return 0

  fi

  local down_since down_for

  down_since="$(cat "$down_since_file" 2>/dev/null || echo "$now")"

  down_for=$(( now - down_since ))

  if [[ "$down_for" -ge 60 && ! -f "$alert_sent_file" ]]; then

    local body

    body="$(

      {

        echo "Host: $HOST"

        echo "Guardian: $unit"

        echo "active=$active_state2"

        echo "failed=$failed_state2"

        echo "Down da almeno ${down_for} secondi"

        echo

        echo "===== systemctl status $unit ====="

        systemctl status "$unit" --no-pager -l || true

        echo

        echo "===== journalctl -u $unit -n 50 ====="

        journalctl -u "$unit" -n 50 --no-pager || true

      }

    )"

    send_mail "[IR] Guardian down oltre 60s su ${HOST}: ${unit}" "$body"

    touch "$alert_sent_file"

  fi

}

for unit in "${GUARDIANS[@]}"; do

  handle_guardian "$unit"

done

exit 0

EOF

chmod 700 /usr/local/sbin/ir-guardians-watchdog.sh

echo "[*] Hardening sshd logging..."

if grep -qE '^[#[:space:]]*LogLevel[[:space:]]+' /etc/ssh/sshd_config; then

  sed -i 's/^[#[:space:]]*LogLevel[[:space:]].*/LogLevel VERBOSE/' /etc/ssh/sshd_config

else

  echo 'LogLevel VERBOSE' >> /etc/ssh/sshd_config

fi

echo "[*] Configuro Postfix per relay alert + consegna locale..."

MAIL_FQDN="$(hostname -f 2>/dev/null || true)"

case "$MAIL_FQDN" in

  ""|localhost|localhost.localdomain) MAIL_FQDN="$(hostname)" ;;

esac

echo "$MAIL_FQDN" >/etc/mailname

postconf -e "myhostname = ${MAIL_FQDN}"

postconf -e 'myorigin = /etc/mailname'

postconf -e 'inet_interfaces = loopback-only'

postconf -e 'mynetworks = 127.0.0.0/8 [::1]/128'

postconf -e 'relayhost = ['"${RELAYHOST}"']:'"${RELAYPORT}"

postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost, $myorigin'

postconf -X local_transport || true

touch /etc/aliases

grep -q '^root:' /etc/aliases 2>/dev/null || echo 'root: root' >> /etc/aliases

newaliases || true

systemctl enable postfix

systemctl restart postfix || true

echo "[*] Creo unità systemd..."

cat >/etc/systemd/system/identity-incident-from-path.service <<'EOF'

[Unit]

Description=Identity anomaly check triggered by path change

After=network-online.target auditd.service

Wants=network-online.target

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/identity-incident-handler.sh path

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

ReadWritePaths=/var/log /var/spool/postfix /root/firewall-backups

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/identity-incident-from-timer.service <<'EOF'

[Unit]

Description=Identity anomaly check triggered by periodic timer

After=network-online.target auditd.service

Wants=network-online.target

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/identity-incident-handler.sh timer

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

ReadWritePaths=/var/log /var/spool/postfix /root/firewall-backups

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/identity-incident-manual.service <<'EOF'

[Unit]

Description=Manual identity anomaly check

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/identity-incident-handler.sh manual

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

ReadWritePaths=/var/log /var/spool/postfix /root/firewall-backups

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/identity-incident-handler.path <<'EOF'

[Unit]

Description=Watch identity and ssh files for critical changes

StartLimitIntervalSec=300

StartLimitBurst=40

[Path]

PathModified=/etc/passwd

PathModified=/etc/shadow

PathModified=/etc/group

PathModified=/etc/gshadow

PathModified=/etc/sudoers

PathModified=/etc/sudoers.d

PathModified=/etc/ssh/sshd_config

PathModified=/root/.ssh/authorized_keys

Unit=identity-incident-from-path.service

[Install]

WantedBy=multi-user.target

EOF

cat >/etc/systemd/system/identity-incident-handler.timer <<'EOF'

[Unit]

Description=Periodic identity anomaly check

StartLimitIntervalSec=300

StartLimitBurst=20

[Timer]

OnBootSec=2min

OnUnitActiveSec=5min

Unit=identity-incident-from-timer.service

[Install]

WantedBy=timers.target

EOF

cat >/etc/systemd/system/ssh-login-alert.service <<'EOF'

[Unit]

Description=Parse successful SSH logins and send mail alerts

After=network-online.target

Wants=network-online.target

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/ssh-login-alert.sh

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

StateDirectory=ssh-login-alert

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/ssh-login-alert.path <<'EOF'

[Unit]

Description=Watch SSH auth logs and trigger login alert parser

StartLimitIntervalSec=300

StartLimitBurst=60

[Path]

PathModified=/var/log/auth.log

Unit=ssh-login-alert.service

[Install]

WantedBy=multi-user.target

EOF

cat >/etc/systemd/system/ir-stack-watch.service <<'EOF'

[Unit]

Description=IR stack modification alert

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/ir-stack-watch.sh

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

ReadWritePaths=/var/log /var/spool/postfix

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/ir-stack-watch.path <<'EOF'

[Unit]

Description=Watch IR ecosystem files for modifications

StartLimitIntervalSec=300

StartLimitBurst=40

[Path]

PathModified=/usr/local/sbin/check-identity-anomalies.sh

PathModified=/usr/local/sbin/emergency-lockdown.sh

PathModified=/usr/local/sbin/emergency-lockdown-rollback.sh

PathModified=/usr/local/sbin/identity-incident-handler.sh

PathModified=/usr/local/sbin/ssh-login-alert.sh

PathModified=/usr/local/sbin/ir-stack-watch.sh

PathModified=/usr/local/sbin/ir-guardians-watchdog.sh

PathModified=/usr/local/sbin/irctl

PathModified=/etc/incident-response/lockdown.conf

PathModified=/etc/incident-response/identity-shell-whitelist

PathModified=/etc/systemd/system/identity-incident-from-path.service

PathModified=/etc/systemd/system/identity-incident-from-timer.service

PathModified=/etc/systemd/system/identity-incident-manual.service

PathModified=/etc/systemd/system/identity-incident-handler.path

PathModified=/etc/systemd/system/identity-incident-handler.timer

PathModified=/etc/systemd/system/ssh-login-alert.service

PathModified=/etc/systemd/system/ssh-login-alert.path

PathModified=/etc/systemd/system/ir-stack-watch.service

PathModified=/etc/systemd/system/ir-stack-watch.path

PathModified=/etc/systemd/system/ir-guardians-watchdog.service

PathModified=/etc/systemd/system/ir-guardians-watchdog.timer

Unit=ir-stack-watch.service

[Install]

WantedBy=multi-user.target

EOF

cat >/etc/systemd/system/ir-guardians-watchdog.service <<'EOF'

[Unit]

Description=Incident Reporting guardians watchdog

After=network-online.target

Wants=network-online.target

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/ir-guardians-watchdog.sh

User=root

Group=root

NoNewPrivileges=no

PrivateTmp=yes

ProtectSystem=full

ProtectHome=read-only

ReadWritePaths=/var/lib/incident-response /var/spool/postfix /run

ProtectControlGroups=yes

ProtectKernelModules=yes

ProtectKernelTunables=yes

LockPersonality=yes

MemoryDenyWriteExecute=yes

EOF

cat >/etc/systemd/system/ir-guardians-watchdog.timer <<'EOF'

[Unit]

Description=Run Incident Reporting guardians watchdog every 10 seconds

StartLimitIntervalSec=300

StartLimitBurst=20

[Timer]

OnBootSec=20s

OnUnitActiveSec=10s

AccuracySec=1s

Unit=ir-guardians-watchdog.service

[Install]

WantedBy=timers.target

EOF

echo "[*] Abilito auditd..."

systemctl enable --now auditd

echo "[*] Carico regole audit..."

augenrules --load || true

echo "[*] Ricarico systemd..."

systemctl daemon-reload

echo "[*] Rimuovo vecchia unità legacy se presente..."

systemctl disable --now identity-incident-handler.service 2>/dev/null || true

rm -f /etc/systemd/system/identity-incident-handler.service

echo "[*] Abilito unità nuove..."

systemctl enable --now identity-incident-handler.path

systemctl enable --now identity-incident-handler.timer

systemctl enable --now ssh-login-alert.path

systemctl enable --now ir-stack-watch.path

systemctl enable --now ir-guardians-watchdog.timer

echo "[*] Reload ssh..."

systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null || true

echo "[*] Creo utility irctl..."

cat >/usr/local/sbin/irctl <<'EOF'

#!/usr/bin/env bash

set -euo pipefail

PATH_SERVICE="identity-incident-from-path.service"

TIMER_SERVICE="identity-incident-from-timer.service"

MANUAL_SERVICE="identity-incident-manual.service"

PATH_UNIT="identity-incident-handler.path"

TIMER_UNIT="identity-incident-handler.timer"

SSH_ALERT_PATH="ssh-login-alert.path"

SSH_ALERT_SERVICE="ssh-login-alert.service"

IR_STACK_PATH="ir-stack-watch.path"

IR_STACK_SERVICE="ir-stack-watch.service"

WATCHDOG_SERVICE="ir-guardians-watchdog.service"

WATCHDOG_TIMER="ir-guardians-watchdog.timer"

show_status() {

    echo "==================== STATUS ===================="

    systemctl status "$PATH_UNIT" --no-pager || true

    echo

    systemctl status "$TIMER_UNIT" --no-pager || true

    echo

    systemctl status "$PATH_SERVICE" --no-pager || true

    echo

    systemctl status "$TIMER_SERVICE" --no-pager || true

    echo

    systemctl status "$MANUAL_SERVICE" --no-pager || true

    echo

    systemctl status "$SSH_ALERT_PATH" --no-pager || true

    echo

    systemctl status "$SSH_ALERT_SERVICE" --no-pager || true

    echo

    systemctl status "$IR_STACK_PATH" --no-pager || true

    echo

    systemctl status "$IR_STACK_SERVICE" --no-pager || true

    echo

    systemctl status "$WATCHDOG_TIMER" --no-pager || true

    echo

    systemctl status "$WATCHDOG_SERVICE" --no-pager || true

}

show_audit() {

    echo "==================== AUDIT RULES ===================="

    auditctl -l || true

    echo

    echo "==================== AUSEARCH: identity ===================="

    ausearch -k identity || true

    echo

    echo "==================== AUSEARCH: privilege ===================="

    ausearch -k privilege || true

    echo

    echo "==================== AUSEARCH: sshkeys ===================="

    ausearch -k sshkeys || true

    echo

    echo "==================== AUSEARCH: user_mgmt ===================="

    ausearch -k user_mgmt || true

    echo

    echo "==================== AUSEARCH: passwd_mgmt ===================="

    ausearch -k passwd_mgmt || true

    echo

    echo "==================== AUSEARCH: sudo_change ===================="

    ausearch -k sudo_change || true

    echo

    echo "==================== AUSEARCH: ir_stack ===================="

    ausearch -k ir_stack || true

}

show_logs() {

    echo "==================== JOURNAL: identity-incident-handler ===================="

    journalctl -t identity-incident-handler -n 50 --no-pager || true

    echo

    echo "==================== JOURNAL: ssh-login-alert ===================="

    journalctl -t ssh-login-alert -n 50 --no-pager || true

    echo

    echo "==================== JOURNAL: ir-stack-watch ===================="

    journalctl -t ir-stack-watch -n 50 --no-pager || true

    echo

    echo "==================== SERVICE LOG: PATH ===================="

    journalctl -u "$PATH_SERVICE" -n 30 --no-pager || true

    echo

    echo "==================== SERVICE LOG: TIMER ===================="

    journalctl -u "$TIMER_SERVICE" -n 30 --no-pager || true

    echo

    echo "==================== SERVICE LOG: MANUAL ===================="

    journalctl -u "$MANUAL_SERVICE" -n 30 --no-pager || true

    echo

    echo "==================== SERVICE LOG: SSH ALERT ===================="

    journalctl -u "$SSH_ALERT_SERVICE" -n 30 --no-pager || true

    echo

    echo "==================== SERVICE LOG: IR STACK ===================="

    journalctl -u "$IR_STACK_SERVICE" -n 30 --no-pager || true

    echo

    echo "==================== SERVICE LOG: WATCHDOG ===================="

    journalctl -u "$WATCHDOG_SERVICE" -n 30 --no-pager || true

}

run_checker() {

    echo "==================== CHECK-IDENTITY ===================="

    /usr/local/sbin/check-identity-anomalies.sh || true

}

test_path() {

    echo "==================== TEST PATH ===================="

    systemctl stop "$TIMER_UNIT" || true

    touch /etc/passwd

    sleep 2

    journalctl -u "$PATH_SERVICE" -n 20 --no-pager || true

    systemctl start "$TIMER_UNIT" || true

}

test_manual() {

    echo "==================== TEST MANUAL ===================="

    systemctl start "$MANUAL_SERVICE"

    journalctl -u "$MANUAL_SERVICE" -n 20 --no-pager || true

}

test_ssh_alert() {

    echo "==================== TEST SSH ALERT ===================="

    systemctl start "$SSH_ALERT_SERVICE"

    journalctl -u "$SSH_ALERT_SERVICE" -n 20 --no-pager || true

}

test_ir_stack() {

    echo "==================== TEST IR STACK ===================="

    touch /usr/local/sbin/identity-incident-handler.sh

    sleep 2

    journalctl -u "$IR_STACK_SERVICE" -n 20 --no-pager || true

}

test_watchdog() {

    echo "==================== TEST WATCHDOG ===================="

    systemctl start "$WATCHDOG_SERVICE"

    journalctl -u "$WATCHDOG_SERVICE" -n 30 --no-pager || true

}

show_all() {

    show_status

    echo

    run_checker

    echo

    show_logs

}

usage() {

    cat <<'EOH'

Uso: irctl <comando>

Comandi:

  status       Mostra stato unità systemd principali

  audit        Mostra regole audit e ricerche ausearch

  logs         Mostra log utili

  check        Esegue check-identity-anomalies.sh

  test-path    Test rapido del trigger path

  test-manual  Test rapido del trigger manuale

  test-ssh     Test rapido parser alert login SSH

  test-ir      Test rapido watcher stack IR

  test-watch   Test rapido watchdog

  all          Mostra status + check + logs

  help         Mostra questo aiuto

EOH

}

cmd="${1:-help}"

case "$cmd" in

    status)      show_status ;;

    audit)       show_audit ;;

    logs)        show_logs ;;

    check)       run_checker ;;

    test-path)   test_path ;;

    test-manual) test_manual ;;

    test-ssh)    test_ssh_alert ;;

    test-ir)     test_ir_stack ;;

    test-watch)  test_watchdog ;;

    all)         show_all ;;

    help|-h|--help) usage ;;

    *)

        echo "Comando non riconosciuto: $cmd" >&2

        usage

        exit 1

        ;;

esac

EOF

chmod 700 /usr/local/sbin/irctl

echo "[*] Creo alias ir..."

cat >/etc/profile.d/irctl.sh <<'EOF'

alias ir='/usr/local/sbin/irctl'

EOF

chmod 644 /etc/profile.d/irctl.sh

echo "[*] Test iniziali..."

/usr/local/sbin/check-identity-anomalies.sh || true

mkdir -p /var/lib/ssh-login-alert

journalctl -u ssh -u sshd -n 1 --show-cursor -o short-iso 2>/dev/null | sed -n 's/^-- cursor: //p' > /var/lib/ssh-login-alert/last_cursor || true

chmod 700 /var/lib/ssh-login-alert

chmod 600 /var/lib/ssh-login-alert/last_cursor 2>/dev/null || true

systemctl start ssh-login-alert.service || true

systemctl start ir-stack-watch.service || true

systemctl start ir-guardians-watchdog.service || true


echo

echo "[OK] Installazione completata."

echo "Config: /etc/incident-response/lockdown.conf"

echo "Whitelist: /etc/incident-response/identity-shell-whitelist"

echo

echo "Verifiche utili:"

echo "  auditctl -l"

echo "  systemctl status identity-incident-handler.path"

echo "  systemctl status identity-incident-handler.timer"

echo "  systemctl status ssh-login-alert.path"

echo "  systemctl status ir-guardians-watchdog.timer"

echo "  journalctl -u identity-incident-from-path.service -n 20 --no-pager"

echo "  journalctl -u identity-incident-from-timer.service -n 20 --no-pager"

echo "  journalctl -u ssh-login-alert.service -n 20 --no-pager"

echo "  journalctl -u ir-guardians-watchdog.service -n 20 --no-pager"

echo "  ausearch -k identity"

echo "  ausearch -k ir_stack"

Verifica dopo l’installazione

irctl status
irctl check
irctl audit
irctl logs

systemctl status identity-incident-handler.path
systemctl status identity-incident-handler.timer
systemctl status ssh-login-alert.path
systemctl status ir-stack-watch.path
systemctl status ir-guardians-watchdog.timer

auditctl -l

Test senza provocare un incidente vero

Lo stack contiene test espliciti proprio per evitare di “provare” la protezione creando un utente root fasullo o modificando chiavi reali. Possiamo forzare il controllo manuale, il path watcher, il parser SSH, il watcher dello stack e il guardian separatamente.

irctl test-manual
irctl test-path
irctl test-ssh
irctl test-ir
irctl test-watch

Come leggere un alert

Una segnalazione non è una sentenza. La sequenza corretta è: identificare il file o l’account interessato; cercare l’evento audit; verificare processi e utente; controllare se esisteva una manutenzione autorizzata; correlare SSH, sudo, package manager e deploy; solo a quel punto decidere se aggiornare la baseline, ripristinare un file oppure aprire un incidente.

Questo è il punto centrale dell’IR Stack: non cerca di rendere il server magicamente invulnerabile. Cerca di fare in modo che, quando qualcosa cambia, non dobbiamo ricostruire la storia partendo dal buio.