Tutti gli articoli di nazarenolatella

clean_zombies: script bash per ripulire il sistema dai processi zombie

Tenere sotto controllo il numero di processi attivi sulla nostra macchina è certamente una buona pratica, soprattutto quando si parla di sistemi in produzione. Tale monitoraggio può essere realizzato mediante il buon vecchio Nagios, il quale provvederà ad inviarci un’opportuna notifica nel caso in cui il numero dei processi superi una determinata soglia (definibile a piacere).

bashOccorre precisare, però, che nel computo totale rientrano anche i processi zombie, i quali, per definizione, non possono essere killati (sono già in stato defunct). Ergo, per sbarazzarsene occorrerà “fermare” il processo padre (o parent process). Ora, poichè non esiste pratica più noisa dell’andare a ricercare i padri dei vari processi zombie, ho deciso di realizzare il seguente scrip bash in grado di automatizzare il task in questione (con relativo respawning del parent process):

parents=`ps -ef | grep defunct | grep -v grep | awk '{print $3}' | sort -u | wc -l`

p=1

parentidlist=`ps -ef | grep defunct | grep -v grep | awk '{print $3}' | sort -u`

if [[ ! -z "$parents" ]] && [[ "$parents" -gt 0 ]];then
        while [[ "$p" -le "$parents" ]]
        do
                parentid=`echo $parentidlist | awk -v p="$p" '{print $p}'`
                parentname=`ps -o cmd -p $parentid | grep -v CMD`

                if [[ ! -z "$parentid" ]] && [[ ! -z "$parentname" ]];then
                        echo "Terminating $parentname with PID $parentid..."
                        kill -15 "$parentid"
                        sleep 10
                        running=`ps aux | grep "$parentname" | grep -v grep`
                        if [[ -z "$running" ]];then
                                echo "Starting $parentname..."
                                eval $parentname
                        else
                                echo "Error: the process $parentname is still running, switching to the next one..."
                        fi
                fi

                let p=p+1
        done
else
        echo "No defunct process, exiting..."
        exit 1
fi

Il suddetto codice è abbastanza intuitivo. Per prima cosa individuo il numero totale e la lista dei PPID (parent process id) associati a ciascun processo in stato defunct (se esistono). A questo punto, mediante un banale ciclo while, “termino” i processi padre ricavati in precedenza, con successivo respawning degli stessi (sempre che il kill -15 aka SIGTERM sia andato in buon fine, altrimenti skip e si procede con il PPID successivo). Da notare che ho utilizzato la direttiva eval piuttosto che exec, poichè, in quest’ultimo caso, veniva forzata l’uscita dal while.

Se volete fare qualche test potete utilizzare quest’altro scrip bash (fork_zombies) che ho creato allo scopo:

#!/bin/bash

(sleep 1 & exec /bin/sleep 60)

è sufficiente lanciarne qualche istanza in background mediante il comando:

[root@linuxbox ~]# ./fork_zombies &

Nel prossimo post vedremo come integrare il suddetto scrip con Nagios (sotto forma di event handler).

Alla prossima.

check_ntp_peers: script bash per il monitoraggio dei peer NTP mediante Nagios

Il protocollo NTP ci consente, in soldoni, di tenere aggiornate la data e l’ora di sistema, sincronizzandole con un’apposita sorgente di tempo. Per i sistemi *nix, nella stragrande maggioranza dei casi, è sufficiente configurare il servizio NTP (attivo mediante il demone ntpd, o, in alternativa, utilizzando l’accoppiata ntpdate + crontab) agendo sul suo file diconfigurazione (ovvero ntp.conf) e definendo le sorgenti alle quali sincronizzarsi.

ntpAd esempio:

server ntp1.inrim.it
server ntp2.inrim.it

Ora, per saggiare lo stato di sincronizzazione del nostro server, a parte il classico comando date, è possibile utilizzare un apposito tool per le interrogazioni NTP, ovvero ntpq:

[root@linuxbox ~]# ntpq
ntpq> pe
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*ntp1.inrim.it   .CTD.            1 u    4   64  377   45.819    0.219   2.655
+ntp2.inrim.it   .CTD.            1 u  903 1024  377   48.974    1.788   0.978

In particolare, con il comando pe, sono riuscito a recuperare la lista dei peers (ovvero le sorgenti di tempo) con i relativi valori di offset (i quali rappresentano la discrepanza, in ms, tra l’ora locale di sistema e quella del peer stesso), di jitter (ovvero il ritardo, anch’esso espresso in ms, accumulato tra 2 aggiornamenti immediatamente successivi) e di delay (RTT, in ms, associato alla comunicazione con il time server remoto). Da notare, inoltre, che il campo refid indica qual è (se esiste, dipende dallo stratum, specificato dal campo st) la sorgente di tempo utilizzata a loro volta dai server che sto interrogando (rispettivamente ntp1.inrim.it ed ntp2.inrim.it), mentre il carattere * mostra quale dei 2 server da me definiti all’interno del file ntp.conf sto utilizzando per sincronizzare la data e l’ora di sistema.

Dopo questa breve carrellata introduttiva veniamo al dunque, per cui riporto il contenuto dello scrip bash in grado di tenere sotto controllo i valori associati alle sorgenti NTP definite sul nostro sistema:

#!/bin/bash

warn_values=$1
crit_values=$2

warn_delay=`echo $warn_values | awk -F "," '{print $1}'`
warn_offset=`echo $warn_values | awk -F "," '{print $2}'`
warn_jitter=`echo $warn_values | awk -F "," '{print $3}'`

warn_delay_len=$((${#warn_delay} - 1))
warn_offset_len=$((${#warn_offset} - 1))
warn_jitter_len=$((${#warn_jitter} - 1))

crit_delay=`echo $crit_values | awk -F "," '{print $1}'`
crit_offset=`echo $crit_values | awk -F "," '{print $2}'`
crit_jitter=`echo $crit_values | awk -F "," '{print $3}'`

crit_delay_len=$((${#crit_delay} - 1))
crit_offset_len=$((${#crit_offset} - 1))
crit_jitter_len=$((${#crit_jitter} - 1))

if [[ "$warn_delay_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for warning delay"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$warn_offset_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for warning offset"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$warn_jitter_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for warning jitter"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$crit_delay_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for critical delay"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$crit_offset_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for critical offset"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$crit_jitter_len" -gt 5 ]];then
        echo "UNKNOWN: bad value for critical jitter"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

ntp_delay=`/usr/sbin/ntpq -p | grep '*' | awk '{print $8}'`
ntp_offset=`/usr/sbin/ntpq -p | grep '*' | awk '{print $9}'`
ntp_jitter=`/usr/sbin/ntpq -p | grep '*' | awk '{print $10}'`

if [[ ! -z $warn_values ]];then
         if [[ ! -z $crit_values ]];then
                if [[ ! -z $warn_delay ]];then
                        if [[ ! -z $crit_delay ]];then
                                if [[ "$(echo $ntp_delay '>=' $warn_delay | bc)" -eq 1 ]] && [[ "$(echo $ntp_delay '<' $crit_delay | bc)" -eq 1 ]];then
                                        delay="NTP delay is $ntp_delay ms";
                                        delay_perf="| ntp_delay=$ntp_delay"
                                        retval_1=1;
                                elif [[ "$(echo $ntp_delay '>=' $crit_delay | bc)" -eq 1 ]];then
                                        delay="NTP delay is $ntp_delay ms";
                                        delay_perf="| ntp_delay=$ntp_delay"
                                        retval_1=2;
                                else
                                        delay="NTP delay is $ntp_delay ms";
                                        delay_perf="| ntp_delay=$ntp_delay"
                                        retval_1=0;
                                fi
                        else
                                echo "UNKNOWN: NTP critical delay is unknown"
                                exit 3;
                        fi
                else
                        echo "UNKNOWN: NTP warning delay is unknown"
                        exit 3;
                fi
                if [[ ! -z $warn_offset ]];then
                        if [[ ! -z $crit_offset ]];then
                                if [[ "$(echo $ntp_offset '<' 0 | bc)" -eq 1 ]];then
                                        warn_offset=$(echo "-1 * $warn_offset" | bc)
                                        crit_offset=$(echo "-1 * $crit_offset" | bc)
                                        if [[ "$(echo $ntp_offset '<=' $warn_offset | bc)" -eq 1 ]] && [[ "$(echo $ntp_offset '>' $crit_offset | bc)" -eq 1 ]];then
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=1;
                                        elif [[ "$(echo $ntp_offset '<' $crit_offset | bc)" -eq 1 ]];then
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=2;
                                        else
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=0;
                                        fi
                                else
                                        if [[ "$(echo $ntp_offset '>=' $warn_offset | bc)" -eq 1 ]] && [[ "$(echo $ntp_offset '<' $crit_offset | bc)" -eq 1 ]];then
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=1;
                                        elif [[ "$(echo $ntp_offset '>' $crit_offset | bc)" -eq 1 ]];then
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=2;
                                        else
                                                offset="NTP offset is $ntp_offset ms";
                                                offset_perf=" ntp_offset=$ntp_offset"
                                                retval_2=0;
                                        fi
                                fi
                        else
                                echo "UNKNOWN: NTP critical offset is unknown"
                                exit 3;
                        fi
                else
                        echo "UNKNOWN: NTP warning offset is unknown"
                        exit 3;
                fi
                if [[ ! -z $warn_jitter ]];then
                        if [[ ! -z $crit_jitter ]];then
                                if [[ "$(echo $ntp_jitter '>' $warn_jitter | bc)" -eq 1 ]] && [[ "$(echo $ntp_jitter '<' $crit_jitter | bc)" -eq 1 ]];then
                                        jitter="NTP jitter is $ntp_jitter ms";
                                        jitter_perf=" ntp_jitter=$ntp_jitter"
                                        retval_3=1;
                                elif [[ "$(echo $ntp_offset '>' $crit_jitter | bc)" -eq 1 ]];then
                                        jitter="NTP jitter is $ntp_jitter ms";
                                        jitter_perf=" ntp_jitter=$ntp_jitter"
                                        retval_3=2;
                                else
                                        jitter="NTP jitter is $ntp_jitter ms";
                                        jitter_perf=" ntp_jitter=$ntp_jitter"
                                        retval_3=0;
                                fi
                        else
                                echo "UNKNOWN: NTP critical jitter is unknown"
                                exit 3;
                        fi
                else
                        echo "UNKNOWN: NTP warning jitter is unknown"
                        exit 3;
                fi
        else
                 echo "UNKNOWN: Critical values are unknown"
                 echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
                 exit 3;
        fi
else
        echo "UNKNOWN: Warning values are unknown"
        echo "Usage: check_ntp_peers warndelay,warnoffset,warnjitter critdelay,critoffset,critjitter"
        exit 3;
fi

if [[ "$retval_1" -eq 1 ]] || [[ "$retval_2" -eq 1 ]] || [[ "$retval_3" -eq 1 ]];then

        echo "WARNING: $delay $offset $jitter $delay_perf $offset_perf $jitter_perf"
        exit 1

elif [[ "$retval_1" -eq 2 ]] || [[ "$retval_2" -eq 2 ]] || [[ "$retval_3" -eq 2 ]];then

        echo "CRITICAL: $delay $offset $jitter $delay_perf $offset_perf $jitter_perf"
        exit 2

else

        echo "OK: $delay $offset $jitter $delay_perf $offset_perf $jitter_perf"
        exit 0

fi

Per prima cosa viene verificata la consistenza delle soglie di WARNING e CRITICAL per ciauscuno dei 3 valori monitorati (offset, jitter e delay), sia per quanto riguarda il numero di cifre utilizzate (5) che per ciò che concerne la coerenza logica (ad esempio il jitter/offset/delay di WARNING deve essere strettamente minore di quello CRITICAL).

A questo punto non ci rimane che configurare Nagios, editando, in prima istanza, il file /etc/nagios/objects/commands.cfg, dove verrà definito il comando che si avvarrà del suddetto plugin:

 # 'check_ntp_peers' command definition
define command{
        command_name check_ntp_peers
        command_line $USER1$/check_ntp_peers $ARG1$ $ARG2$
}

e successivamente associando uno specifico servizio all’host che rappresenta la nostra macchina, in modo da monitorare i valori associati all’NTP:

define service{
        use                             local-service         ; Name of service template to use
        host_name                       localhost
        service_description             NTP Peers Status
        check_command                   check_ntp_peers!80.000,2.000,2.000!90.000,3.000,3.000
        }

Facciamo il solito reload del nostro NMS:

[root@linuxbox ~]# service nagios reload

ed abbiamo finito.

Alla prossima.

PS: ho deciso di scrivere il suddetto scrip per sopperire alle limitazioni intrinseche del plugin Nagios (nativo) check_ntp_peer (senza la s finale). Nella fattispecie, quest’ultimo consente di ricavare solo ed esclusivamente il valore di offset associato alla sorgente di tempo remota e per funzionare è necessario che il server interrogato sia configurato in modo da rispondere alle query NTP (e quindi non solo alle richieste di sincronizzazione). Da notare che, nella stragrande maggioranza dei casi e per questioni di sicurezza, il server consentirà la sola sincronizzazione, ignorando gli altri tipi di query, come riportato nero su bianco (ad esempio) nella configurazione standard di ntpd:

restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery

CentOS 6: monitorare le regole di auditing in modo proattivo mediante Nagios, NRDP e swatch

In questo post ed in quest’altro ho illustrato, rispettivamente, come configurare Nagios, NRDP e swatch per la ricezione dei check passivi e dei security alert.

Adesso vedremo come fare a monitorare in modo proattivo le regole di auditing definite in precedenza mediante il tool auditd (vedi qui per ulteriori dettagli).

Nagios_logo_blackIn soldoni, la configurazione si avvale di due passaggi:

1) creazione di un file da dare in pasto a swatch, nel quale sono definite le espressioni regolari in grado di identificare in modo univoco ciascun evento di auditing;

2) definizione dei servizi di Nagios per la ricezione dei check passivi.

Ecco uno stralcio della configurazione di swatch:

#time_changes auditing rule
watchfor /time_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='time_changes auditing rule' --output\='$_ | time_changes\=1'"
#system_locale_changes auditing rule
watchfor /system_locale_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='system_locale_changes auditing rule' --output\='$_ | system_locale_changes\=1'"
#shadow_changes auditing rule
watchfor /shadow_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='shadow-file auditing rule' --output\='$_ | shadow_changes\=1'"
#passwd_changes auditing rule
watchfor /passwd_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='passwd_changes auditing rules' --output\='$_ | passwd_changes\=1'"
#group_changes auditing rule
watchfor /group_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='group_changes auditing rule' --output\='$_ | group_changes\=1'"
#sudoers_changes auditing rule
watchfor /sudoers_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='sudoers_changes auditing rule' --output\='$_ | sudoers_changes\=1'"
#selinux_changes auditing rule
watchfor /selinux_changes/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='selinux_changes auditing rule' --output\='$_ | selinux_changes\=1'"
#module_insertion auditing rule
watchfor /module_insertion/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='module_insertion auditing rule' --output\='$_ | module_insertion\=1'"
#webserver_watch_tmp auditing rule
watchfor /webserver_watch_tmp/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='webserver_watch_tmp auditing rules' --output\='$_ | webserver_watch_tmp\=1'"
#sshd_config auditing rule
watchfor /sshd_config/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='sshd_config auditing rules' --output\='$_ | sshd_config\=1'"
#httpd_config auditing rule
watchfor /httpd_config/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='httpd_config auditing rules' --output\='$_ | httpd_config\=1'"
#ntp_config auditing rule
watchfor /ntp_config/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='ntp_config auditing rules' --output\='$_ | ntp_config\=1'"
#iptables_config auditing rule
watchfor /iptables_config/
     echo
     exec "/usr/bin/php /usr/lib64/nagios/plugins/send_nrdp.php --url\=http://192.168.1.1/nrdp --token\=s3cr3t --host\=localhost --state\=1 --service\='iptables_config auditing rules' --output\='$_ | iptables_config\=1'"

Il comando da lanciare per rendere operativo il suddetto applicativo (che magari potremo inserire anche all’interno del file /etc/rc.local per l’avvio automatico dopo ogni riavvio della macchina) è il seguente:

swatch -c /etc/swatchaudit.conf -t /var/log/audit/audit.log --daemon

Di seguito, invece, riporto la configurazione dell’host di Nagios (localhost) per il quale occorre monitorare gli eventi di auditing:

define service{
        use                             local-service
        host_name                       localhost
        service_description             time_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             system_locale_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             shadow_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             group_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             sudoers_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             selinux_changes auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             module_insertion auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             webserver_watch_tmp auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             sshd_config auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             httpd_config auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             ntp_config auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

define service{
        use                             local-service
        host_name                       localhost
        service_description             iptables_config auditing rule
        check_command                   check_passive
        passive_checks_enabled          1
        active_checks_enabled           0
        max_check_attempts              1
        is_volatile                     1
        check_freshness                 1
        freshness_threshold             6
        flap_detection_enabled          0
        }

Come al solito, lanciamo un reload di Nagios per rendere operative le suddette modifiche:

[root@linuxbox ~]# service nagios reload

ed abbiamo finito.

Alla prossima.

cURL: interagire con l’interfaccia Web dei dispositivi di rete mediante linea di comando

Spesso e volentieri abbiamo a che fare con delle macchine *nix sprovviste di Desktop Enviroment (altrimenti conosciuto come server X), attraverso le quali è necessario accedere all’interfaccia Web di alcuni dispositivi di rete SOHO, in modo da effettuare le classiche operazioni di management (cambio di configurazione, riavvio, ecc.).

A tal proposito, esiste un tool molto potente da utilizzare mediante CLI, che prende il nome di cURL.

curlAdesso vedremo quali sono le analisi preliminari che ci permetteranno di capire se l’interfaccia Web con cui vogliamo interagire potrà essere manipolata tramite cURL o meno.

Analisi del codice sorgente

Il primo step consiste nello scaricare il codice sorgente dalla pagina, utilizzando le giuste credenziali di accesso (se previste). Ad esempio:

[root@linuxbox ~]# curl -u username:password http://192.168.1.1

A questo punto possiamo distinguere due macro casi:

1) la pagina Web contiene prevalentemente del codice HTML ed i cambi di configurazione possono essere effettuati mediante dei semplici form e l’invio dei dati attraverso HTTP POST o GET. In tal caso siamo stati fortunati e potremo procedere;

2) la pagina Web contiene soprattutto del codice javascrip e tutte le operazioni di management si avvalgono del suddetto codice. In questo caso siamo stati sfortunati e dovremo demordere, in quanto cURL non supporta javascrip.

Poniamoci quindi nel caso 1 e procediamo.

Per prima cosa occorre analizzare il codice sorgente della pagina di interesse andando alla ricerca dei tag:

<form></form>

all’interno dei quali è presente l’attributo action che punta alla pagina Web che si occuperà di processare i dati del form. Ad esempio:

 
<form action="apply.cgi" method="post">
<input type="hidden" name="page" value="device.asp">

    <input type="submit" name="action" value="Reboot">
</form>

Inoltre, bisogna capire quali e quanti campi di input (con relativo valore), identificati mediante gli attributi id o name, è necessario sottoporre alla suddetta pagina Web. Infatti, molto spesso, come misura molto blanda per contrastare alcuni attacchi Web quali il CSRF (vedi qui per ulteriori dettagli), vengono utilizzati dei campi di input di tipo hidden da inviare insieme al contenuto degli altri campi del form.

Una volta capito quali sono i campi da inoltrare alla pagina specificata dall’attributo action del form HTML (magari utilizzando il classico metodo trial-and-error), possiamo procedere con il loro inoltro vero e proprio, avvalendoci, ad esempio, del seguente comando:

curl -u username:password -s -d "action=Reboot&page=device.asp" http://192.168.1.1/apply.cgi

Nella fattispecie, la pagina che si occuperà di processare i dati è apply.cgi, mentre i campi di input inviati sono action, il cui valore è Reboot, e page, il cui valore è device.asp. Da notare che le suddette informazioni sono state inviate in formato querystring (ovvero utilizzando il carattere & di concatenazione).

Infine, occorre precisare che la flag -s evita di inviare allo standard output le statistiche relative al processamento della pagina richiesta, mentre la flag -d (data) è quella che ci permette di inviare i dati attraverso un semplice HTTP POST.

Per ora è tutto. Alla prossima.

CentOS 6: configurare delle regole di auditing mediante auditd

In questo post ho accennato all’importanza che le operazioni di auditing ricoprono durante la messa in sicurezza dei nostri server.

Nella fattispecie, per la distro CentOS 6, l’applicativo che si occupa di “monitorare” (inteso come controllo degli accessi ed accounting) il sistema operativo, prende il nome di auditd.

Vedremo adesso come fare a configurare delle regole di auditing secondo le nostre esigenze, come ricercare determinati eventi negli appositi file di log e come creare dei report giornalieri (da inviare via email al nostro indirizzo di posta) contenenti l’insieme degli eventi di auditing individuati.

audit

Creazione delle regole

Per effettuare tale operazione è necessario utilizzare il tool auditctl. Esso è in grado di creare delle regole il cui scopo è quello di monitorare le operazioni (lettura, scrittura, esecuzione e modifica degli attributi) su un dato file o su di una specifica directory. Non solo: sarà possibile anche configurare dei meccanismi di monitoraggio delle chiamate di sistema (in gergo SYSCALL) che vengono eseguite durante il normale funzionamento della nostra macchina.

Ecco alcuni esempi di regole per il monitoraggio delle SYSCALL:

[root@linuxbox ~]# auditctl -a always,exit -F arch=b64 -S settimeofday -k time_changes
[root@linuxbox ~]# auditctl -a always,exit -F arch=b64 -S clock_settime -k time_changes
[root@linuxbox ~]# auditctl -a always,exit -F arch=b64 -S sethostname -S setdomainname -k system_locale_changes

In particolare, vengono monitorate le chiamate di sistema settimeofday, clock_settime, sethostname e setdomainname (specificate mediante la flag -S), le quali indicano palesemente delle modifiche macroscopiche relative alla configurazione del sistema operativo. Occorre precisare, inoltre, che è stato necessario indicare (mediante la flag -F) l’architettura della CPU (recuperabile mediante il comando uname -m), poichè i codici numerici che identificano ciascuna SYSCALL variano a seconda che si tratti di una x86 (32 bit) oppure di una x86_64 (64 bit). Qui trovate la lista delle chiamate di sistema per x86 e qui trovate quella per x86_64.

Per il monitoraggio di file e directory l’opzione da utilizzare è -w, seguita dal nome del file (o della directory) di interesse. Inoltre, mediante la flag -p, potranno essere specificate le operazioni di cui tenere traccia (a per modifica degli attributi, x per esecuzione, w per scrittura ed r per lettura). Infine, mediante la flag -k sarà possibile associare un’etichetta a ciascuna regola di auditing precedentemente definita. Eccco alcuni esempi:

[root@linuxbox ~]# auditctl -w /etc/shadow -p wa -k shadow-file
[root@linuxbox ~]# auditctl -w /etc/passwd -p wa -k passwd_changes
[root@linuxbox ~]# auditctl -w /etc/group -p wa -k group_changes
[root@linuxbox ~]# auditctl -w /etc/sudoers -p wa -k sudoers_changes
[root@linuxbox ~]# auditctl -w /etc/selinux/ -p wa -k selinux_changes
[root@linuxbox ~]# auditctl -w /sbin/insmod -p x -k module_insertion
[root@linuxbox ~]# auditctl -w /tmp -p x -k webserver_watch_tmp
[root@linuxbox ~]# auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config
[root@linuxbox ~]# auditctl -w /etc/httpd/conf/httpd.conf -p wa -k httpd_config
[root@linuxbox ~]# auditctl -w /etc/ntp.conf -p wa -k ntp_config

Possiamo, inoltre, elencare le regole finora definite utilizzando il comando:

[root@linuxbox ~]# auditctl -l

il cui output sarà simile al seguente:

-a always,exit -F arch=x86_64 -S settimeofday -F key=time_changes
-a always,exit -F arch=x86_64 -S clock_settime -F key=time_changes
-a always,exit -F arch=x86_64 -S sethostname,setdomainname -F key=system_locale_changes
-w /etc/shadow -p wa -k shadow-file
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/selinux/ -p wa -k selinux_changes
-w /sbin/insmod -p x -k module_insertion
-w /tmp/ -p x -k webserver_watch_tmp
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/httpd/conf/httpd.conf -p wa -k httpd_config
-w /etc/ntp.conf -p wa -k ntp_config
-w /etc/sysconfig/iptables -p wa -k iptables_config

Ricerca degli eventi

Il file di log in cui vengono salvati gli eventi di auditing è /var/log/audit/audit.log. Attraverso l’applicativo ausearch, che punta al suddetto file, è possibile effettuare delle ricerche mirate specificando il file di interesse, ad esempio:

[root@linuxbox ~]# ausearch -f /etc/passwd

oppure l’etichetta precedentemente assegnata all’evento:

[root@linuxbox ~]# ausearch -k webserver_watch_tmp

Generazione dei report

E’ possibile generare dei report contenenti gli eventi di auditing di interesse. Per fare ciò è sufficiente utilizzare l’applicativo aureport, comprensivo di flag -k, -ts (time start) e -te (time end). Ad esempio:

aureport -k -ts yesterday 00:00:00 -te yesterday 23:59:59

Per schedulare il suddetto report ad intervalli di tempo regolari (ad esempio ogni giorno) e fare in modo di riceverlo sulla propria casella di posta elettronica, è necessario creare un apposito file di cronjob all’interno della directory /etc/cron.d:

[root@linuxbox cron.d]# nano ausearchreport

il cui contenuto dovrà essere:

MAILTO=vostro.indirizzo@email.it

00    00   * * *     root          /sbin/aureport -k -ts yesterday 00:00:00 -te yesterday 23:59:59

Da notare che tutte le regole precedentemente create mediante auditctl hanno durata temporanea (ovvero vengono eliminate dopo un reboot). Per renderle quindi permanenti, occorre inserirle all’interno del file /etc/audit/audit.rules, il cui contenuto dovrà essere simile al seguente:

# First rule - delete all
-D

# Increase the buffers to survive stress events.
# Make this bigger for busy systems
-b 320

# Feel free to add below this line. See auditctl man page

-a always,exit -F arch=b64 -S settimeofday -k time_changes
-a always,exit -F arch=b64 -S clock_settime -k time_changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system_locale_changes
-w /etc/shadow -p wa -k shadow-file
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/selinux/ -p wa -k selinux_changes
-w /sbin/insmod -p x -k module_insertion
-w /tmp -p x -k webserver_watch_tmp
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/httpd/conf/httpd.conf -p wa -k httpd_config
-w /etc/ntp.conf -p wa -k ntp_config
-w /etc/sysconfig/iptables -p wa -k iptables_config

# Disable adding any additional rules - note that adding new rules will require a reboot
-e 2

Come si può facilmente notare, in coda alla lista ho aggiunto l’opzione -e 2 che impedisce l’aggiunta di nuove regole on-the-fly (per questioni di sicurezza).

E’ sufficiente quindi restartare il demone auditd per rendere operative le suddette regole:

[root@linuxbox ~]# service auditd restart

Per ora è tutto. Nei prossimi post vedremo come monitorare in modo proattivo le regole di auditing mediante swatch, NRDP e Nagios.

Alla prossima.

CentOS 6: kernel tuning volto al system hardening

Qualunque amministratore di sistema sa bene che 2 regole di firewalling in croce ed una password sufficientemente complessa non sono in grado di garantire un livello di sicurezza accettabile. Occorre, infatti, avere ben presente quali sono i pericoli a cui si dovrà far fronte mettendo un dato sistema in produzione, rischi che possono essere mitigati (a volte anche molto efficacemente) utilizzando qualche applicativo creato ad hoc (come, ad esempio, SELinux) e monitorando il sistema 24/7, sia a livello di risorse che a livello di operazioni sospette su determinati file o directory (in quest’ultimo caso parliamo di auditing).

Detto ciò, è altrettanto importante sapere che il kernel linux prevede alcuni meccanismi in grado di aumentare notevolmente la sicurezza del sistema operativo stesso, mettendolo al riparo da attacchi di tipo overflow, ip spoofing, ecc.

kernel

Tali meccanismi possono essere abilitati in modo permanente editando file /etc/sysctl.conf (in tal caso per rendere i settaggi operativi sarà necessario utilizzare il comando sysctl -p oppure effettuare un reboot), o, in alternativa, operando mediante /proc (rendendo subito effettive le modifiche ma non permanenti).

Per quanto mi riguarda, di solito opero in questo modo:

1) modifico i parametri di interesse mediante /proc (uno per uno, in modo da essere sicuro che , dopo ogni step, il sistema continua a funzionare come dovrebbe);

2) modifico il file sysctl.conf popolandolo con le direttive necessarie.

Ecco le modifiche da apportare al kernel on-the-fly:

#Misure anti overflow

echo 1 > /proc/sys/kernel/exec-shield
echo 2 > /proc/sys/kernel/randomize_va_space

#Misure anti ip spoofing e source route

echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter
echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
echo 1 > /proc/sys/net/ipv4/conf/default/log_martians
echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
echo 0 > /proc/sys/net/ipv4/conf/default/accept_source_route

#Misure anti ICMP redirect

echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
echo 0 > /proc/sys/net/ipv4/conf/default/accept_redirects
echo 0 > /proc/sys/net/ipv4/conf/all/secure_redirects
echo 0 > /proc/sys/net/ipv4/conf/default/secure_redirects

#Misure anti DOS/DDOS basati su TCP (SYN FLOOD)

echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 2 > /proc/sys/net/ipv4/tcp_synack_retries

#Misure anti smurf

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

Nella fattispecie,  le misure anti overlflow hanno come obiettivo quello di impedire l’esecuzuone di shellcode su aree di memoria marcate come “non eseguibili” (exec-shield), facendo anche in modo che tutti i riferimenti a particolari funzioni presenti in memoria non siano accessibili facilmente da un utente malevolo, randomizzandone gli indirizzi di memoria (randomize_va_space)

Tra le misure anti ip spoofing e source route sono elencati 12 parametri, ovvero:

echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter

che abilitano il return path filter, ovvero se l’IP sorgente di un dato pacchetto ricevuto su una specifica interfaccia non è raggiungibile mediante quest’ultima, il suddetto pacchetto verrà scartato;

echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
echo 1 > /proc/sys/net/ipv4/conf/default/log_martians

che abilitano il logging dei pacchetti spoofati;

echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
echo 0 > /proc/sys/net/ipv4/conf/default/accept_source_route

il cui compito è quello di disabilitare il cosiddetto source route (ovvero “instradare” il pacchetto appena ricevuto lungo il “cammino” di rete ben preciso – comprensivo di hop – specificato al suo interno);

echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects 
echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects

che disabilitano l’invio dei pacchetti ICMP redirect;

echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects 
echo 0 > /proc/sys/net/ipv4/conf/default/accept_redirects

che disabilitano la ricezione degli ICMP redirect;

echo 0 > /proc/sys/net/ipv4/conf/all/secure_redirects 
echo 0 > /proc/sys/net/ipv4/conf/default/secure_redirects

affinchè anche gli ICMP redirect provenienti dal default gateway (e quindi teoricamente “sicuri”) vengano scartati.

Per quanto riguarda, invece, le misure anti attacchi DOS/DDOS basati sul procotollo TCP, ho abilitato i cosiddetti syncookies e limitato l’invio dei SYN/ACK a 2 soli tentativi (in modo da mitigare i SYN FLOOD):

echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 2 > /proc/sys/net/ipv4/tcp_synack_retries

Infine, nelle misure anti smurf ho semplicemente disabilitato la ricezione dei broadcast ICMP:

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

Per quanto riguarda, invece, il file /etc/sysctl.conf, le direttive da inserire sono le seguenti:

#Enable Exec-Shield
kernel.exec-shield = 1

# Enable ASLR
kernel.randomize_va_space = 1

# Controls source route verification
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Controls the use of TCP syncookies
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2

# Do not accept source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Do not accept or send ICMP redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# Log spoofed packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

#Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

Fine del kernel tuning. Alla prossima.

yum-cron: verificare automaticamente la disponibilità degli aggiornamenti su CentOS 6

In questo post ho reso disponibile uno scrip bash (scritto di mio pugno) in grado di verificare (tramite yum) la disponibilità di security fix, bug fix e CVE.

Adesso vedremo come estendere tale funzionalità a qualsiasi tipo di update, usufruendo di un pacchetto software creato appositamente per CentOS 6, ovvero yum-cron.

yum

Per prima cosa installiamo il suddetto applicativo:

[root@linuxbox ~]# yum install yum-cron

Ad installazione completa modifichiamo il file di configurazione /etc/sysconfig/yum-cron secondo le nostre esigenze. In particolare, vogliamo fare in modo che ci venga notificata tramite email la disponibilità di eventuali update, senza installare nulla automaticamente:

# Don't install, just check (valid: yes|no)
CHECK_ONLY=yes

# Check to see if you can reach the repos before updating (valid: yes|no)
CHECK_FIRST=yes

MAILTO=vostro.indirizzo@email.it

A questo punto non ci rimane che avviare il servizio yum-cron:

[root@linuxbox ~]# service yum-cron start

e fare in modo che venga eseguito in automatico dopo ogni reboot della nostra macchina:

[root@linuxbox ~]# chkconfig yum-cron on

E’ tutto. Alla prossima.

checksmart: script bash per effettuare le query S.M.A.R.T.

Gli hard disk di ultima generazione (ATA e SCSI) supportano una funzionalità molto interessante, denominata S.M.A.R.T. (Self-Monitoring, Analysis and Reporting Technology)

Il significato del suddetto acronimo è già di per sé chiaro. Infatti, tale tecnologia può effettuare alcuni test di auto diagnostica rivolti alle componenti elettromeccaniche del disco (o solo elettriche nel caso in cui si avesse a che fare con gli SSD). I risultati di tali test potranno quindi essere visualizzati dall’utente con un grado di dettaglio variabile a seconda delle sue esigenze, il quale potrà anche consultare lo storico degli ultimi test effettuati ed il log degli errori individuati durante le ore di normale esercizio del disco.

harddisk

Di tool in grado di interagire con la suddetta funzionalità ne esistono a bizzeffe e per tutti i sistemi operativi (vedi qui per ulteriori dettagli). Tra questi merita sicuramente una particolare menzione smartmontools, soprattutto se si vogliono effettuare le query S.M.A.R.T. in ambienti Unix like. Utilizzando proprio il suddetto tool ho deciso di creare uno scrip bash (checksmart) da richiamare mediante crontab in modo da schedulare dei check più o meno approfonditi ad intervalli di tempo regolari. I risultati ottenuti potranno quindi essere inviati alla casella email dell’utente, utilizzando la flag opzionale –email.

Ecco lo scrip in todo:

#!/bin/bash

bin=`/usr/bin/which smartctl`

logfile=/var/log/checksmart/checksmart.log

ROOT_UID=0

if [ "$UID" -ne "$ROOT_UID" ];then

        data=`date +'%F %H:%M:%S'`
        echo "$data - Error: you need to be root to run this scrip"
        exit 1

fi

if [[ ! -d /var/log/checksmart ]];then

        mkdir -p /var/log/checksmart
fi

if [[ ! -f /var/log/checksmart/checksmart.log ]];then

        touch /var/log/checksmart/checksmart.log
fi

diskno=`df -h | awk '{print $1}' | grep sd | wc -l`

type=$1

subtype=$2

address=$3

if [[ ! -z $address ]];then

        regex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$"

        checkmail() {
                if [[ ! $address =~ $regex ]];then
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - Error: invalid email address" | tee -a $logfile
                        exit 1;
                fi
        }

        checkmail;
fi

pre_running_controls() {

if [[ ! -z $bin ]];then
        if [[ ! -z $diskno ]];then
                data=`date +'%F %H:%M:%S'`
                echo "$data - Executing pre-running controls..." | tee -a $logfile
                sleep 2
                data=`date +'%F %H:%M:%S'`
                echo "$data - Checking for S.M.A.R.T. capabilities..." | tee -a $logfile
                for (( d=1; d<=$diskno; d++ ))
                do
                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                        smart_capable=`$bin -a $disk | grep "SMART support is: Available"`
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - 1. Checking if S.M.A.R.T. support for disk $disk is available" | tee -a $logfile
                        if [[ ! -z $smart_capable ]];then
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - S.M.A.R.T. support for $disk is available" | tee -a $logfile
                        else
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - Error: S.M.A.R.T. support for $disk is NOT available, exiting..." | tee -a $logfile
                                exit 1;
                        fi

                        smart_enabled=`$bin -a $disk | grep "SMART support is: Enabled"`
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - 2. Checking if S.M.A.R.T. support for disk $disk is enabled" | tee -a $logfile
                        if [[ ! -z $smart_enabled ]];then
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - S.M.A.R.T. support for $disk is enabled" | tee -a $logfile
                        else
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - Error: S.M.A.R.T. support for $disk disk is NOT enabled, trying to enable it..." >> $logfile
                                smart_enabled=`$bin -s on $disk |  grep "SMART support is: Enabled"`
                                if [[ ! -z $smart_enabled ]];then
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - S.M.A.R.T. support for $disk disk is enabled NOW enabled" | tee -a $logfile
                                else
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - Error: Unable to turn on S.M.A.R.T. support for disk $disk, exiting ..." | tee -a $logfile
                                        exit 1;
                                fi
                        fi
                        smart_self=`$bin -a $disk | grep "No self-tests have been logged"`
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - 3. Checking if S.M.A.R.T. self-tests have been logged" | tee -a $logfile
                        if [[ ! -z $smart_self ]];then
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - No S.M.A.R.T. self-tests have been logged yet, trying to run a short one..." | tee -a $logfile
                                smart_self=`$bin -t short $disk | grep "No self-tests have been logged"`
                                if [[ ! -z $smart_self ]];then
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - Error: Unable to run a short self-test for disk $disk, exiting..." | tee -a $logfile
                                        exit 1;
                                else
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - S.M.A.R.T. self-tests have been logged" | tee -a $logfile
                                fi
                        else
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - S.M.A.R.T. support for $disk disk is enabled" | tee -a $logfile
                        fi
                done
        else
                data=`date +'%F %H:%M:%S'`
                echo "$data - Error: no ATA disks found" | tee -a $logfile
                exit 1;
        fi
else
        data=`date +'%F %H:%M:%S'`
        echo "$data - Error: no smartctl binary found - please install smarmontools" | tee -a $logfile
        exit 1;
fi
}

get_time_required() {

if [[ ! -z $bin ]];then
        if [[ ! -z $diskno ]];then
                for (( d=1; d<=$diskno; d++ ))
                do
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - Getting time required by each test for each disk" | tee -a $logfile
                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                        time_short=`$bin -c $disk | grep -A 1 'Short' | grep minutes | awk '{print $5}' | sed -e 's/)//g'`
                        if [[ -z $time_short ]];then
                                echo "$data - Error: unable to get the time required by the short test for disk $disk" | tee -a $logfile
                        else
                                time_short_seconds=$((time_short*60 + 2))
                        fi
                        time_long=`$bin -c $disk | grep -A 1 'Extended' | grep minutes | awk '{print $5}' | sed -e 's/)//g'`
                        if [[ -z $time_long ]];then
                                echo "$data - Error: unable to get the time required by the long test for disk $disk" | tee -a $logfile
                        else
                                time_long_seconds=$((time_long * 60 + 2))
                        fi
                        time_conveyance=`$bin -c $disk | grep -A 1 'Conveyance' | grep minutes | awk '{print $5}' | sed -e 's/)//g'`
                        if [[ -z $time_conveyance ]];then
                                echo "$data - Error: unable to get the time required by the conveyance test for disk $disk" | tee -a $logfile
                        else
                                time_conveyance_seconds=$((time_conveyance * 60 + 2))
                        fi

                done
        else

                data=`date +'%F %H:%M:%S'`
                echo "$data - Error: no ATA disks found" | tee -a $logfile
                exit 1;

        fi
else

        data=`date +'%F %H:%M:%S'`
        echo "$data - Error: no smartctl binary found - please install smarmontools" | tee -a $logfile
        exit 1;

fi
}

get_time_required;

if [[ ! -z $bin ]];then
        if [[ ! -z $diskno ]];then
                pre_running_controls;
                if [[ ! -z $type ]];then
                        if [[ $type == "--brief" ]];then
                                for (( d=1; d<=$diskno; d++ ))
                                do
                                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - checking S.M.A.R.T entities for $disk disk" | tee -a $logfile
                                        if [[ $subtype == "--email" ]];then
                                                if [[ ! -z $address ]];then
                                                        data=`date +'%F %H:%M:%S'`
                                                        echo $data >> $logfile
                                                        $bin -a $disk | grep -E "SMART overall-health self-assessment test result:|Reallocated_Sector|Spin_Retry_Count|Runtime_Bad_Block|End-to-End_Error|Reported_Uncorrect|Command_Timeout|Current_Pending_Sector|Offline_Uncorrectable" >> result
                                                        cat result | mail -iv -s "brief S.M.A.R.T report for disk $disk" $address
                                                        cat result >> $logfile
                                                        rm -rf result
                                                else
                                                        echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary | --help>  [ --email <address>]" | tee -a $logfile
                                                        exit 1;
                                                fi
                                         elif [[ ! $subtype =~ "--email" ]] && [[ ! -z $subtype ]];then
                                                data=`date +'%F %H:%M:%S'`
                                                echo "$data - Error: unknown subtype $subtype" | tee -a $logfile
                                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary | --help> [ --email <address>]" | tee -a $logfile
                                                exit 1;
                                        else
                                                $bin -a $disk | grep -E "SMART overall-health self-assessment test result:|Reallocated_Sector|Spin_Retry_Count|Runtime_Bad_Block|End-to-End_Error|Reported_Uncorrect|Command_Timeout|Current_Pending_Sector|Offline_Uncorrectable" | tee -a $logfile
                                        fi
                                done
                                exit 0;
                        elif [[ $type == "--short" ]];then
                                for (( d=1; d<=$diskno; d++ ))
                                do
                                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - checking S.M.A.R.T entities for $disk disk" | tee -a $logfile
                                        if [[ $subtype == "--email" ]];then
                                                if [[ ! -z $address ]];then
                                                        data=`date +'%F %H:%M:%S'`
                                                        echo $data >> $logfile
                                                        $bin -t short $disk
                                                        if [[ ! -z $time_short_seconds ]];then
                                                                sleep $time_short_seconds
                                                        else
                                                                sleep 120
                                                        fi
                                                        $bin -a $disk >> result
                                                        cat result | mail -iv -s "short test S.M.A.R.T report for disk $disk" $address
                                                        cat result >> $logfile
                                                        rm -rf result
                                                else
                                                        echo "$data - Error: wrong email address format" | tee -a $logfile
                                                        echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]" | tee -a $logfile
                                                        exit 1;
                                                fi
                                        elif [[ ! $subtype =~ "--email" ]] && [[ ! -z $subtype ]];then
                                                data=`date +'%F %H:%M:%S'`
                                                echo "$data - Error: unknown subtype $subtype" | tee -a $logfile
                                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]" | tee -a $logfile
                                                exit 1;
                                        else
                                                $bin -t short $disk | tee -a $logfile
                                                if [[ ! -z $time_short_seconds ]];then
                                                        sleep $time_short_seconds
                                                else
                                                        sleep 120
                                                fi
                                                $bin -a $disk | tee -a $logfile
                                        fi
                                done
                                exit 0;
                         elif [[ $type == "--long" ]];then
                                for (( d=1; d<=$diskno; d++ ))
                                do
                                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data - checking S.M.A.R.T entities for $disk disk" | tee -a $logfile
                                        if [[ $subtype == "--email" ]];then
                                                if [[ ! -z $address ]];then
                                                        data=`date +'%F %H:%M:%S'`
                                                        echo $data >> $logfile
                                                        $bin -t long $disk
                                                        if [[ ! -z $time_long_seconds ]];then
                                                                sleep $time_long_seconds
                                                        else
                                                                sleep 3600
                                                        fi
                                                        $bin -a $disk >> result
                                                        cat result | mail -iv -s "long test S.M.A.R.T report for disk $disk" $address
                                                        cat result >> $logfile
                                                        rm -rf result
                                                else
                                                        echo "$data - Error: wrong email address format" | tee -a $logfile
                                                        echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help>  [ --email <address>]" | tee -a $logfile
                                                        exit 1;
                                                fi
                                        elif [[ ! $subtype =~ "--email" ]] && [[ ! -z $subtype ]];then
                                                data=`date +'%F %H:%M:%S'`
                                                echo "$data - Error: unknown subtype $subtype" | tee -a $logfile
                                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]" | tee -a $logfile
                                                exit 1;
                                        else
                                                $bin -t long $disk | tee -a $logfile
                                                if [[ ! -z $time_long_seconds ]];then
                                                        sleep $time_long_seconds
                                                else
                                                        sleep 3600
                                                fi
                                                $bin -a $disk | tee -a $logfile
                                        fi
                                done
                                exit 0;
                        elif [[ $type == "--conveyance" ]];then
                                for (( d=1; d<=$diskno; d++ ))
                                do
                                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data: checking S.M.A.R.T entities for $disk disk" | tee -a $logfile
                                        if [[ $subtype == "--email" ]];then
                                                if [[ ! -z $address ]];then
                                                        data=`date +'%F %H:%M:%S'`
                                                        echo $data >> $logfile
                                                        $bin -t conveyance $disk
                                                        if [[ ! -z $time_conveyance_seconds ]];then
                                                                sleep $time_conveyance_seconds
                                                        else
                                                                sleep 240
                                                        fi
                                                        $bin -a $disk >> result
                                                        cat result | mail -iv -s "conveyance test S.M.A.R.T report for disk $disk" $address
                                                        cat result >> $logfile
                                                        rm -rf result
                                                else
                                                        echo "$data - Error: wrong email address format" | tee -a $logfile
                                                        echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]" | tee -a $logfile
                                                        exit 1;
                                                fi
                                        elif [[ ! $subtype =~ "--email" ]] && [[ ! -z $subtype ]];then
                                                data=`date +'%F %H:%M:%S'`
                                                echo "$data - Error: unknown subtype $subtype" | tee -a $logfile
                                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]" | tee -a $logfile
                                                exit 1;
                                        else
                                                $bin -t conveyance $disk | tee -a $logfile
                                                if [[ ! -z $time_conveyance_seconds ]];then
                                                        sleep $time_conveyance_seconds
                                                else
                                                        sleep 240
                                                fi
                                                $bin -a $disk | tee -a $logfile
                                        fi
                                done
                                exit 0;
                        elif [[ $type == "--summary" ]];then
                                for (( d=1; d<=$diskno; d++ ))
                                do
                                        disk=`df -h | awk '{print $1}' | grep sd | sed s'/.$//' | sed -n "$d p" | uniq`
                                        data=`date +'%F %H:%M:%S'`
                                        echo "$data: checking S.M.A.R.T entities for $disk disk" | tee -a $logfile
                                        if [[ $subtype == "--email" ]];then
                                                if [[ ! -z $address ]];then
                                                        data=`date +'%F %H:%M:%S'`
                                                        echo $data >> $logfile
                                                        $bin -l selftest $disk >> result
                                                        cat result | mail -iv -s "summary test S.M.A.R.T. report for disk $disk" $address
                                                        cat result >> $logfile
                                                        rm -rf result
                                                else
                                                        echo "$data - Error: wrong email address format" | tee -a $logfile
                                                        echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary | --help> [ --email <address>]"
                                                        exit 1;
                                                fi
                                        elif [[ ! $subtype =~ "--email" ]] && [[ ! -z $subtype ]];then
                                                data=`date +'%F %H:%M:%S'`
                                                echo "$data - Error: unknown subtype $subtype" | tee -a $logfile
                                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary | --help> [ --email <address>]"
                                                exit 1;
                                        else
                                                $bin -l selftest $disk | tee -a $logfile
                                        fi
                                done
                                exit 0;
                        elif [[ $type == "--help" ]];then
                                echo "Usage: checksmart < --brief| --short| --long| --conveyance| --summary| --help> [ --email <address>]"
                                echo "       --brief will display only critical pre-failure S.M.A.R.T. indicators"
                                echo "       --short will run a short S.M.A.R.T. test"
                                echo "       --long will run a long S.M.A.R.T. test"
                                echo "       --conveyance will run a conveyance S.M.A.R.T. test"
                                echo "       --summary will display only the self test history report"
                                echo "       --help will display this help"
                                echo "       --email <address> will send the S.M.A.R.T. report via email to the specified address"
                                echo "Example: checksmart --brief --email youraddress@yourdomain.com"
                                exit 0;
                        else
                                data=`date +'%F %H:%M:%S'`
                                echo "$data - Error: unknown type $type" | tee -a $logfile
                                echo "Usage: checksmart < --brief| --short| --long| --conveyance|  --summary| --help> [ --email <address>]"
                                exit 0;
                        fi
                else
                        data=`date +'%F %H:%M:%S'`
                        echo "$data - Error: type not defined" | tee -a $logfile
                        echo "Usage: checksmart < --brief| --short| --long| --conveyance|  --summary| --help | [ --email <address>]"
                        exit 0;
                fi
        else
                data=`date +'%F %H:%M:%S'`
                echo "$data - Error: no ATA disks found" | tee -a $logfile
                exit 1;
        fi
else
        data=`date +'%F %H:%M:%S'`
        echo "$data - Error: no smartctl binary found - please install smarmontools" | tee -a $logfile
        exit 1;
fi

Per prima cosa verifico che i dischi supportino la funzionalità S.M.A.R.T, che sia abilitata e che siano stati eseguiti in precedenza alcuni test di autodiagnostica. L’insieme delle 3 condizioni appena enunciate costituisce i pre-running controls (contenuti all’interno dell’omonima funzione).

Sucessivamente (mediante la funzione get_time_required()) ricavo i tempi richiesti per l’esecuzione di ciascun test (short, long e conveyance), da dare in pasto al comando sleep, così da consentire agli smartmontools di elaborarne i risultati ed al mio scrip di restituirli all’utente.

Completati i pre-running controls ed individuati i tempi di esecuzione, vengono lanciati i test veri e propri. Nello specifico, lo scrip supporta 5 modalità di funzionamento, ovvero:

1) –brief; viene restituito all’utente solo l’overall status ed i valori associati alle voci che potrebbero indicare un guasto imminente del disco (per ulteriori dettagli potete consultare questa pagina), quali Read Error Rate, Reallocated Sectors Count, Spin Retry Count, Runtime Bad Block, End to End Error, Reported Uncorrect, Command Timeout, Current Pending Sector, Offline Uncorrectable. A tal proposito occorre fare una precisazione: poichè la tecnologia S.M.A.R.T. non è uno standard, le suddette voci potrebbero non essere presenti nei risultati restituiti dallo scrip, ergo, in questo caso, potrete modificarlo in funzione delle voci supportate.

2) –short; esegue uno short test.

3) –long; esegue un long test (molto più approfondito).

4) –conveyance; interroga il disco sugli eventuali danni che si sono verificati durante le operazioni di trasporto dello stesso. Non tutti i dischi supportano tale opzione.

5) –summary; equivale ad uno smartctl -l e consente di visualizzare solo lo storico dei test pregressi.

Come già accennato in precedenza, il parametro –email è opzionale ma, nel caso in cui l’utente decidesse di utilizzarlo, lo scrip verifica che il formato dell’indirizzo di posta a cui il report dovrà essere indirizzato sia corretto.

Infine, per consentire un maggiore controllo sui passaggi cruciali eseguiti dallo scrip, ho fatto in modo che essi vengano “registrati” all’interno di un apposito file di log.

E’ tutto. Alla prossima.

PMTUD e TCP MSS

Topologia di massima

Due filiali interconnesse mediante un tunnel GRE/IPSEC instaurato sfruttando le rispettive linee ADSL. In particolare, sul sito A sono presenti diversi client che hanno necessità di “dialogare” con un server Web posto nel sito B.

Problema

I client accedono al server Web remoto con estrema lentezza e molte delle richieste effettuate vanno in timeout. Nella fattispecie, trattasi di un problema legato alla MTU (Maximum Transmission Unit) configurata sulle interfacce dei router delle 2 filiali, che provoca la suddetta sintomatologia.

Un po’ di teoria

Ma cos’è l’MTU? Essa, molto semplicemente, definisce la dimensione massima (in byte) di un pacchetto (o di un frame) che può attraversare una determinata interfaccia di rete. Ad esempio, nel caso del protocollo IP (layer 3) parliamo generalmente di 1500 byte, mentre nel caso del protocollo Ethernet (layer 2) la MTU è pari a 1526 byte (1500 byte “ereditati” dal protocollo IP + 22 byte di instestazione Ethernet e 4 byte di CRC). Quest’ultima è sicuramente la più rognosa, in quanto molto spesso gli switch sono unmanaged e quindi non è possibile accorgersi immediatamente dei cosiddetti frame giant, che vegnono quindi scartati “silenziosamente” provocando una comunicazione “a singhiozzo” (vedi questo post per ulteriori dettagli).

Per quanto riguarda invece la MTU IP, si avranno sicuramente dei problemi nel caso in cui:

1) i valori configurati sulle interfacce del router A e del router B differiscono tra di loro (in gergo parliamo di MTU mismatch);

2) lungo il cammino tra router A e il router B esiste un segmento la cui MTU è inferiore rispetto a quella configurata sui 2 router.

Nel primo caso possiamo correre ai ripari molto velocemente, configurando un valore di MTU identico sulle interfacce dei router delle 2 filiali. Ad esempio, possiamo definire la MTU di una data interfaccia utilizzando il comando:

Router(config-if)# ip mtu <valore in byte>

Nel secondo caso, invece, occorre fare un po’ di diagnostica ed eventualmente utilizzare delle configurazioni ad hoc.

In entrambi i casi i pacchetti che superano la MTU più bassa verranno “frammentati” e ricostruiti in fase di ricezione. Ovviamente tale operazione, oltre a causare un overhead sui router coinvolti, provocherà dei seri problemi a livello 4 (TCP) e superiori, inficiando in modo notevole l’accesso al servizio da parte degli utenti.

PMTUD

Per evitare quindi che si verifichi un’eccessiva frammentazione è stato messo a punto un meccanismo tale per cui un router riuscirà ad accorgersi automaticamente della presenza di un valore di MTU inferiore rispetto a quello configurato sulla propria interfaccia, riducendo di conseguenza la dimensione dei pacchetti che intende inoltrare. Tale meccanismo prende il nome di Path MTU Discovery, abbreviato in PMTUD. Più in dettaglio, essa si basa sull’invio/ricezione di messaggi ICMP unreachable type 3 code 4 ( vedi qui per ulteriori dettagli): se la MTU dei pacchetti ricevuti da un router supera la dimensione massima definita sulla propria interfaccia in ricezione, esso procederà con l’inoltro del suddetto messaggio verso il router mittente. In tal modo, quest’ultimo si accorgerà di quanto sta avvenendo e ridurrà di conseguenza la MTU in modo da riuscire ad “attraversare” il router interessato.

PMTUDOra, occorre precisare che non è tutto oro ciò che luccica, nel senso che molto spesso i pacchetti ICMP unreachable vengono disabilitati sia in TX che in RX (in questo caso si parla di PMTUD blackhole). Ad esempio, nel caso dei dispositivi di casa Cisco, essi sono disabilitati di default:

Router#sh ip int dia0 | i ICMP
  ICMP redirects are never sent
  ICMP unreachables are never sent
  ICMP mask replies are never sent

Il perchè di tale configurazione è presto detto: per loro stessa natura, i messaggi ICMP consentono di ottenere informazioni “sensibili” sulla rete target e possono anche essere utilizzati per generare attacchi di tipo DoS/DDoS. Infatti, se un utente malevolo facesse in modo di “sollecitare” la generazione dei messaggi ICMP unreachable da parte del router oggetto dell’attacco, riuscirebbe certamente a sovraccaricarlo, poichè la generazione del suddetto tipo di messagistica avviene a livello di CPU. Come workaround è comunque possibile settare un rate limit specifico, ad esempio:

Router(config)# ip icmp rate-limit unreachable df 500

dove 500 rappresenta il numero di millisecondi che deve intercorrere tra la generazione di un messaggio ICMP unreachable e quello successivo.

A titolo di cronaca, per abilitare i suddetti messaggi su una data interfaccia è sufficiente digitare il comando:

Router(config-if)# ip unreachable

Identificare il valore di MTU corretto: metodo trial and error

Occorre precisare che esiste un modo empirico per determinare la dimensione della MTU più piccola presente lungo il percorso tra il router A ed il router B. Esso si basa sull’utilizzo del comando ping dotato delle flag -f (che imposta a 1 il bit do not fragment nell’header IP) e -l (che imposta la dimensione del solo payload). Se ad esempio la MTU configurata sulla scheda di rete del nostro PC (con Windows 7 a bordo) fosse pari a 1500 byte, potremmo utilizzare il comando:

C:\Users\pippo> ping 192.168.1.1 -f -l 1472

dove 1472 è il massimo payload definibile senza che vi sia frammentazione, poichè ad esso verranno sommati i 20 byte di intestazione IP e gli 8 byte di instestazione ICMP (per un totale di 1500 byte). Utilizzando un payload di 1473 byte, la nostra scheda ci indicherà che abbiamo superato la MTU attraverso il seguente output:

C:\Users\pippo>ping 192.168.1.1 -l 1473 -f

Esecuzione di Ping 192.168.1.1 con 1473 byte di dati:
E' necessario frammentare il pacchetto ma DF è attivo.
E' necessario frammentare il pacchetto ma DF è attivo.
E' necessario frammentare il pacchetto ma DF è attivo.
E' necessario frammentare il pacchetto ma DF è attivo.

Tale metodo può essere utilizzato per identificare la MTU di interesse, abbassando di volta in volta la dimensione del payload fino a quando al posto del suddetto messaggio verranno visualizzati dei semplici ICMP echo reply.

Una piccola nota a margine: nel caso in cui si volesse utilizzare questa metodologia direttamente dalla CLI del router Cisco, è possibile utilizzare il seguente comando:

ping 192.168.1.1 source fa0/0 df-bit size 1500

dove 1500, in questo caso, rappresenta la dimensione totale del messaggio ICMP echo request (e non del solo payload come avveniva nel caso di Windows 7).

TCP MSS

L’acronimo MSS sta per Maximum Segment Size e rappresenta la dimensione massima (in byte) che può avere il payload (e solo il payload) di un segmento TCP. Il valore di default è 1460, ovvero 1500 byte di MTU meno i 20 byte di instestazione TCP ed i 20 byte di intestazione IP. Nel caso in cui la MTU più piccola relativa al percorso tra A a B fosse inferiore a quella di default, è possibile “forzare” il valore di MSS (in gergo parliamo di MSS clamping). Ad esempio, per i router Cisco potremo utilizzare il comando:

Router(config-if)# ip tcp adjust-mss <byte>

dove i byte da utilizzare sono dati dalla MTU più piccola meno 40 byte.

Un caso pratico (e molto diffuso tra gli end user) in cui l’MSS clamping torna davvero utile riguarda il protocollo PPPoE, che usa una MTU pari a 1492 byte (a differenza del PPPoA la cui MTU è quella classica, ovvero 1500 byte). In tal caso si può procedere con questa configurazione sull’interfaccia dia0:

Router(config-if)# ip mtu 1492
Router(config-if)# ip tcp adjust-mss 1452

Spero di aver fatto un minimo di chiarezza sul concetto di MTU e su quanto esso sia importante nell’ambito della comunicazione client/server basata sul protocollo TCP e superiori.

Alla prossima.