Archivi tag: x.509

CentOS 6 e Nagios: script bash per monitorare la scadenza dei certificati X.509

A quanti di voi sarà capitato di dover fare i conti con un certificato X.509 scaduto (magari installato su un server in produzione) che avete scordato (per un motivo o per un altro) di rinnovare?

Ebbene, tramite il seguente scrip bash (da integrare a Nagios) non dovremo più “appuntarci” la data di scadenza dei suddetti certificati, poichè sarà proprio l’NMS a ricordarci (a tempo debito) di procedere con il rinnovo.

nagiosEcco lo scrip:

#!/bin/bash

############Scrip powered by nightfly. Default values for warn and crit############
############(if not defined by command line) are 10 and 3 respectivley##############

host=$1
port=$2
warn=$3
crit=$4

function get_cert_datetime() {

        date=`echo | openssl s_client -connect $host:$port 2>/dev/null | openssl x509 -noout -dates | grep notAfter | awk -F "=" '{print $2 }'`

        day=`echo $date | awk '{print $2}'`

        case $day in

        1)

                day=01;
                ;;

        2)

                day=02;
                ;;

        3)

                day=03;
                ;;

        4)

                day=04;
                ;;

        5)

                day=05;
                ;;

        6)

                day=06;
                ;;

        7)

                day=07;
                ;;

        8)

                day=08;
                ;;

        9)

                day=09;
                ;;

        esac

        month=`echo $date | awk '{print $1}'`

        case $month in

        Jan)

                 month=01;
                ;;

        Feb)

                month=02;
                ;;

        Mar)

                month=03;
                ;;

        Apr)

                month=04;
                ;;

        May)

                month=05;
                ;;

        Jun)

                month=06;
                ;;

        Jul)

                month=07;
                ;;

        Aug)

                month=08;
                ;;

        Sep)

                month=09;
                ;;

        Oct)

                month=10;
                ;;

        Nov)

                month=11;
                ;;

        Dec)

                month=12;
                ;;
        esac

        year=`echo $date | awk '{print $4}'`

        time=`echo $date | awk '{print $3}'`

        hours=`echo $time | awk -F ":" '{print $1}'`

        minutes=`echo $time | awk -F ":" '{print $2}'`

        seconds=`echo $time | awk -F ":" '{print $3}'`

        datecheck="$year$month$day"

        datecheckepoch=`date -d "$datecheck" +"%s"`

        datenow=`date +"%Y%m%d"`

        datenowepoch=`date -d "$datenow" +"%s"`

}

get_cert_datetime;

get_time_diff() {

        #time difference in hours, minutes and seconds

        hoursnow=`date +"%H"`

        minutesnow=`date +"%M"`

        secondsnow=`date +"%S"`

        hourstoseconds=`expr $hoursnow "*" 3600`

        minutestosecond=`expr $minutesnow "*" 60`

        secondsnowtot=`expr $hourstoseconds + $minutestosecond + $secondsnow`

        hourschecktoseconds=`expr $hours "*" 3600`

        minuteschecktoseconds=`expr $minutes "*" 60`

        secondschecktot=`expr $hourschecktoseconds + $minuteschecktoseconds + $seconds`

        secondsdiff=`expr $secondschecktot - $secondsnowtot`

        secondstoexpire=`expr 86400 - $secondsnowtot + $secondschecktot`

        minutestoexpire=`expr $secondstoexpire / 60`

        spareseconds=`expr $secondstoexpire % 60`

        hourstoexpire=`expr $minutestoexpire / 60`

        spareminutes=`expr $minutestoexpire % 60`
}

get_time_diff;

timestamp_to_days() {

        #unix timestamp to days conversion

        secondsleft=`expr $datecheckepoch - $datenowepoch`

        minutesleft=`expr $secondsleft / 60`

        hoursleft=`expr $minutesleft / 60`

        daysleft=`expr $hoursleft / 24`

}

timestamp_to_days;

if [[ ! -z $host && ! -z $port ]];then

        if [[ $datecheckepoch -gt $datenowepoch ]];then

                if [[ -z $warn && -z $crit ]];then

                        if [[ $daysleft -ge 10 ]];then

                                echo "OK: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                exit 0

                        elif [[ $daysleft -lt 10 && $daysleft -gt 3 ]];then

                                echo "WARNING: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                exit 1

                        elif [[ $daysleft -lt 3 && $daysleft -gt 0 ]];then

                                echo "CRITICAL: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                exit 2

                        elif [[ $daysleft -eq 0 && $secondsdiff -gt 0 ]];then

                                echo "CRITICAL: the X.509 certificate will expire in $hourstoexpire hours $spareminutes minutes $spareseconds seconds"

                                exit 2

                        fi

                elif [[ ! -z $warn && ! -z $crit ]];then

                        if [[ $warn -gt $crit ]];then

                                if [[ $daysleft -ge $warn ]];then

                                        echo "OK: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                        exit 0

                                elif [[ $daysleft -lt $warn && $daysleft -gt $crit ]];then

                                        echo "WARNING: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                        exit 1

                                elif [[ $daysleft -lt $crit && $daysleft -gt 0 ]];then

                                        echo "CRITICAL: the X.509 certificate will expire in $daysleft days $hourstoexpire hours $spareminutes minutes $spareseconds seconds | days_left=$daysleft"

                                        exit 2

                                elif [[ $daysleft -eq 0 && $secondsdiff -gt 0 ]];then

                                        echo "CRITICAL: the X.509 certificate will expire in $hourstoexpire hours $spareminute minutes $spareseconds seconds | days_left=$daysleft"

                                        exit 2

                                fi

                        fi

                        else

                                echo "Usage: check_ssl_cert_exp <host> <port> [<warn left days> <crit left days>]"
                                echo "Warn threshold has to be greater then the Crit one"
                                exit 3
                        fi

                else

                        echo "Usage: check_ssl_cert_exp <host> <port> [<warn left days> <crit left days>]"
                        echo "Both thresholds have to be used"

                        exit 3
                fi
        else

                echo "CRITICAL: the X.509 certificate is expired"

                exit 2

        fi

else

        echo "Usage: check_ssl_cert_exp <host> <port> [<warn left days> <crit left days>]"
        echo "Please set the hostname and the port at least"

        exit 3
fi

Ho deciso di adoperare le funzioni in modo da suddividere logicamente le varie operazioni che vengono effettuate per il calcolo del tempo che intercorre alla data di scadenza del certificato.

I parametri da utilizzare sono 4 in tutto, di cui 2 mandatori (hostname/FQDN e porta) e 2 facoltativi (soglia di WARNING e soglia di CRITICAL).

Per prima cosa (tramite la funzione get_cert_datetime()) individuo la data di scadenza riportata sul certificato  (campo notAfter), utilizzando l’applicativo s_client della suite openssl. Successivamente effettuo un parsing dell’output, ricavando giorno, mese, anno, ora, minuto e secondo di scadenza, convertendo quindi tali informazioni in Unix epoch.

Successivamente, mediante la funzione get_time_diff(), calcolo la differenza (in termini di ore, minuti e secondi), tra l’ora attuale e quella di scadenza del certificato. Inoltre, utilizzando la variabile secondsdiff, nel caso in cui mi dovessi trovare proprio nel giorno di scadenza, riesco a capire se il certificato sta per scadere oppure è già scaduto.

A questo punto procedo con tutta una serie di confronti tra variabili e nella fattispecie verifico che la data attuale sia anteriore a quella di scadenza:

if [[ $datecheckepoch -gt $datenowepoch ]];then

Se è effettivamente questo il caso, controllo, rispettivamente, che le soglie di WARNING e di CRITICAL siano state definite e che siano logicamente coerenti (con WARNING > CRITICAL):

 elif [[ ! -z $warn && ! -z $crit ]];then                        
      if [[ $warn -gt $crit ]];then

Dopodichè faccio un confronto tra il giorno attuale e quello di scadenza, tenendo in considerazione le suddette soglie oppure i valori di default (10 e 3 nel caso in cui esse non siano state definite esplicitamente). Se mi trovo proprio nel giorno di scadenza verifico che la variabile secondsdiff sia un intero strettamente positivo, restituendo un errore di tipo CRITICAL ed il tempo mancante alla scadenza.

Ovviamente, anche nel caso in cui la data attuale sia successiva a quella di scadenza verrà restituito un errore di tipo CRITICAL segnalando che il certificato è già scaduto.

Configurazione di Nagios

Per prima cosa occorre definire il comando che fa uso del suddetto scrip (check_ssl_cert_exp), ovvero:

# 'check_ssl_exp' command definition
define command{
        command_name    check_ssl_exp
        command_line    $USER1$/check_ssl_cert_exp $HOSTADDRESS$ $ARG1$ $ARG2$ $ARG3$
        }

Successivamente possiamo creare il servizio che si occuperà di controllare la scadenza del certificato X.509:

define service{
        use                             local-service         ; Name of service template to use
        host_name                       localhost
        service_descripion              SSL Certificate Expiration Date
        check_command                   check_ssl_exp!443!10!3
        notifications_enabled           0
        }

Infine riavviamo Nagios per rendere effettive le suddette modifiche:

[root@NMS ~]# service nagios reload

E’ tutto. Alla prossima.

Apache, virtual host ed SSL su CentOS

La cosa fantastica di Apache è che ti consente di gestire i cosiddetti virtual host, ovvero N server Web virtuali, ognuno dei quali presenta le proprie caratteristiche peculiari.

Essi risultano particolarmente comodi quando si ha la necessità di imbastire dei frontend che rispondano per alcuni domini su HTTPS e per altri su HTTP.

logo_apache22.jpg

Come al solito, la presente guida si focalizzerà sulla configurazione del server vera e propria (httpd.conf), e successivamente sulla configurazione di ciascun virtual host.

Configurazione del server

La prima modifica da effettuare nel file di configurazione di Apache riguarda l’abilitazione del protocollo HTTPS. In particolare, esso verrà gestito grazie ad uno specifico modulo, ovvero mod_ssl.so, non presente nell’installazione di default relativa ad Apache.

Ergo, sarà necessario dapprima scaricarlo mediante il comando:

[root@serverweb conf]# yum install mod_ssl

Non ho installato anche openssl in quanto sono partito dal presupposto che siate già in possesso dei certificati X.509 (nel formato Apache) relativi al vostro dominio.

A questo punto è necessario abilitare il suddetto modulo aggiungendo la seguente direttiva nel file /etc/conf/httpd.conf:

LoadModule ssl_module modules/mod_ssl.so

Successivamente, si dovrà procedere con la messa in ascolto sul server della porta TCP 443 (HTTPS).

Per fare ciò si può aggiungere la seguente stringa nel file di configurazione di Apache:

Listen 443

Infine, sempre nel file di configurazione in questione, si dovrà specificare in quale directory sono presenti le direttive relative ai virtual host:

# Include the virtual host configurations:
Include /etc/httpd/vhosts.d/*.conf

Configurazione del virtual host

Posizioniamoci nella directory oggetto dell’include e creiamo un nuovo file di configurazione per il virtual host:

[root@serverweb vhosts.d]# nano virtualhost1.conf

Il cui contenuto sarà simile al seguente:

<VirtualHost *:443>
  ServerName www.miodominio.com

  DocumentRoot /var/www/virtualhosts/www.miodominio.com/htdocs

  SSLEngine on
  SSLCertificateFile /etc/ssl/sslcert.crt
  SSLCertificateKeyFile /etc/ssl/sslkey.pem
  SSLCertificateChainFile /etc/ssl/sf_bundle.crt

  <Directory /var/www/virtual/static.betuniq.info/htdocs>
      AllowOverride All
      Order allow,deny
      Allow from all
   </Directory>

  ErrorLog /var/www/virtualhosts/www.miodominio.com/logs/ssl_error.log
  CustomLog /var/www/virtualhosts/www.miodominio.com/logs/ssl_access.log combined
  #ServerSignature Off

  Redirect 404 /favicon.ico

  <Location /favicon.ico>
   ErrorDocument 404 “No favicon”
  </Location>

</VirtualHost>

Come avrete intuito, il virtual host in questione è in ascolto sulla porta TCP 443, quindi è in grado di servire solo ed esclusivamente il traffico HTTPS:

<VirtualHost *:443>

Con ServerName specifico il dominio associato al virtual host in questione mentre con DocumentRoot indico la direcotry in cui sono presenti le pagine Web (e relativi file, come immagini, fogli di stile, ecc.).

Con SSLEngine on abilito il motore SSL e con le direttive:

SSLCertificateFile /etc/ssl/sslcert.crt
SSLCertificateKeyFile /etc/ssl/sslkey.pem
SSLCertificateChainFile /etc/ssl/sf_bundle.crt

indico rispettivamente il pathname relativo al certificato X.509 (sslcert.crt), della chiave privata RSA (sslkey.pem) e del certificato di terze parti (sf_bundle.crt).

Le altre stringe riguardano semplicemente i file di log e le politiche di overwrite sulla directory (ad esempio mediante file .htaccess).

Ora, affinchè il suddetto dominio rimanga in ascolto sia sulla 443 che sulla 80 (HTTP puro), si può creare un ulteriore file di configurazione, contenente però la direttiva:

<VirtualHost *:80>

nessuna entry associata all’SSL ed ai certificati ma la stessa DocumentRoot del vhost precedente.

Per rendere effettive le modifiche senza creare disservizio è sufficiente lanciare il comando:

[root@serverweb conf]# service httpd reload

Se tale comando non restituisce errori è un ottimo segno.

Per avere ulteriore conferma del corretto funzionamento relativo al nostro server Web, si possono fare due check mediante netstat:

[root@serverweb conf]# netstat -anp | grep :80

e

[root@serverweb conf]# netstat -anp | grep :443

Se il server risulta in Listen è molto probabile che stia funzionando correttamente. Fate comunque delle prove con un browser per esserne certi.

E’ tutto. Alla prossima.

Abilitare FTPS esplicito su VSFTPD

Scenario

Il server FTPS esplicito (ovvero che utilizza la porta canonica TCP 21 per la ricezione dei comandi) si trova dietro un firewall, che filtra sia i tentativi di connessione in ingresso che quelli in uscita. Inoltre, tale firewall è nattato su indirizzo IP pubblico mediante uno screened router.

Anche il client è posizionato dietro un’accoppiata firewall/screened router e proprio per questo motivo si è deciso di utilizzare la modalità FTP passiva piuttosto che quella attiva.

Generazione del certificato

Ecco il comando per generare il certificato X.509 da utilizzare per l’autenticazione della fonte e per la confidenzialità:

nightfly@nightbox:~$ sudo openssl req -new -x509 -nodes -out vsftpd.pem -keyout vsftpd.pem

 

vsftpd, ftps, x.509, ssl, tls, filezilla client, fireftp, SmartFTP client

Configurazione del demone vsftpd

La configurazione del demone vsftpd per abilitare FTPS prevede le seguenti direttive:

rsa_cert_file=/etc/vsftpd.pem
rsa_private_key_file=/etc/vsftpd.pem

ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=NO
force_local_logins_ssl=YES

ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO

require_ssl_reuse=NO
ssl_ciphers=HIGH

pasv_address=<IP Pubblico Screened Router>
pasv_min_port=5012
pasv_max_port=5012

Le prime due righe servono a specificare il pathname del certificato e della chiave privata (a sua volta contenuta nel certificato).

La direttiva:

force_local_data_ssl=NO

fa in modo che il traffico dati non venga cifrato (preferisco usare questa impostazione perchè garantisce una maggiore velocità di trasferimento, l’importante è che vengano cifrati i comandi inviati al server e dunque anche le credenziali di accesso).

Come si evince dallo stralcio di configurazione che ho riportato, il protocollo di cifratura utilizzato è TLS (più robusto del suo predecessore SSL, il quale è stato esplicitamente disabilitato), mentre nelle ultime due righe ho specificato la porta su cui il client potrà instaurare la connessione dati. Tale porta dovrà essere consentita sul firewall, lanciando, ad esempio, il seguente comando (nel caso in cui si abbia a che fare con Netfilter):

iptables -A INPUT -i eth1 -m state --state NEW -m tcp -p tcp --dport 5012 -j ACCEPT

Inoltre, utilizzando il seguente paramentro di configurazione:

pasv_address=<IP Pubblico Screened Router>

si fa in modo che il server risponda al comando PASV inviando l’indirizzo IP pubblico dello screened router anzichè il proprio indirizzo privato. Inoltre, sarà necessario creare, direttamente sul router, un’opportuna regola di port forwarding per la porta TCP 5012.

Occorre precisare che la suddetta direttiva implica che su Netfilter venga abilitato il traffico in ingresso sulla porta in questione, poichè il modulo nf_conntrack_ftp funziona solo ed esclusivamente per i pacchetti che contengono gli indirizzi IP privati della linux box che funge da firewall.

Riavviamo vsftpd:

nightfly@nightbox:~$ sudo service vsftpd restart

e proviamo a connetterci al server, utilizzando un client che supporta FTPS. A tal proposito, vi sconsiglio di utilizzare FireFTP (in quanto presenta diversi bug relativi al protocollo FTPS) e FileZilla Client (perchè pialla la connessione se individua un certificato X.509 non firmato da una CA).

Solo con scopi di test (e per un periodo limitato) potete utilizzare un prodotto shareware, ovvero SmartFTP Client (lo trovate qui).

Enjoy.

Aggiornamento #1

A quanto pare le ultime versioni di FileZilla Client (quella che ho testato personalmente è la 3.17.0.1), consentono le connessioni verso il server FTPS anche in presenza di un certificato X.509 self-signed, a patto che all’interno della configurazione di VSFTPD vi sia la direttiva ssl_ciphers=HIGH.