DIY SSL-certificate monitoring script для ленивых
Продолжаю эпопею самодельных велосипедов.
Чуть-чуть истории. На работе срочно понадобился скрипт для мониторинга SSL-сертификатов наших веб-серверов. Мнения разделились, я предлагал вжиться в роль злоумышленника и просканировать все подсети компании, оппоненты — составить список и мониторить его.
Так как админчики зачастую ленивые и, бывает, не документируют свою работу, а так же любят что-то сделать и забыть(про существование сервера), я решил что мой способ лучше(и универсальней) и приступил к написанию скрипта.
Итак, что понадобится:
- OpenSSL
- Nmap
- Bash
- bc
- awk
Скрипт сканирует заданные подсети на наличие открытого 443-го порта и, с помощью openssl, проверяет сертификат. Потом выводит сертификаты, которые истекают в ближайший месяц. Так же проверяет в днсе обратные зоны и, если не находит (запись в обратной зоне), радостно об этом сообщает. Результаты складываются в отдельные файлы(«хорошие сертификаты», истекающие/просроченные, ошибка соединения, айпи-адреса без обратной зоны) и в один общий файл.
Скрипт под катом.
#!/bin/bash # Certificate monitoring script # monitors for expired ssl-certificates and missing reverse dns zones. # depends on: # 2010 lolipop.habrahabr.ru # зависимости, без них скрипт запускаться отказывается DEPS="host bc openssl nmap awk" # куда отправлять stderr, по умолчанию в девнулл :) DN=/dev/null # подробный вывод на экран (дублирует логи на экране), выключен по-умолчанию, выключить после # настройки скрипта VERBOSE=YES # таймаут ожидания tcp-коннекта openssl TIMEOUT=0.5 # максимальный таймаут вычисляется как 0.5 * максимум из TRIES. В данном случае # за 2 секунды соединение должно установиться TRIES="1 2 3 4" # название файлов с логами, по названию всё ясно LOGGOOD=log_good LOGFAIL=log_fail LOGNOREV=log_noreverse LOGEXPIRED=log_expired # внутренняя переменная NODESNAME=nodes # имя общего файла REPORTNAME=report # список исключений, эти айпи не сканировать EXCLUDELIST="192.168.6.36|192.168.6.48" # подсети, которые сканировать SUBNETS="192.168.6.0/24 123.45.67.0/24 1.2.3.0/24" # грубо :( export LANG=C EXPIRY='2678393' # ~ 1 month DATETODAY=`date +%s` RUNDATE=`date` # проверка на установленные зависимости for DEP in $DEPS; do which $DEP > $DN 2> $DN if [ "$?" == "1" ]; then echo "Binary $DEP is missing. Install it!"; exit; fi done # очистка rm -f $NODESNAME rm -f tmp.* rm -f $LOGGOOD rm -f $LOGFAIL rm -f $LOGNOREV rm -f $LOGEXPIRED # сканирование списка подсетей for NET in $SUBNETS; do if [ "$VERBOSE" == "YES" ]; then echo "Scanning $NET"; fi nmap -v $NET -PN -n -p 443 | grep "Discovered" | awk '{print $6}' | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 | grep -v -E "$EXCLUDELIST" >> $NODESNAME done # главный цикл for i in `cat $NODESNAME`; do # проверка на обратную зону, если есть ".", считаем что это валидное доменное имя HOSTNAME=`host $i | head -1 | awk '{print $5}' | grep "\."` if [ "$HOSTNAME" == "" ]; then HOSTNAME="NO-REVERSE-ZONE" echo $i >> $LOGNOREV fi # подключение к айпи echo | openssl s_client -connect $i:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -noout -dates 2>/dev/null | awk '/After/' | cut --delimiter="=" -s -f2 > tmp.$i & # получаем пид процесса OPENSSLPID=$! # ждём :) sleep 0.3 # проверяем, выполнился ли процесс и, если нет, прибиваем его по таймауту for j in $TRIES; do SIZE=`du tmp.$i | awk '{print $1}'` if [ "$SIZE" == "0" ]; then T=$j sleep $TIMEOUT else kill -9 $OPENSSLPID > $DN 2> $DN break fi done RESULT=`cat tmp.$i` rm -f tmp.$i # если попытка не удалась, отмечаем это в отдельном файле if [ "$RESULT" == "" ]; then if [ "$VERBOSE" == "YES" ]; then echo $i" "$HOSTNAME" NO_DATA"; fi echo $i" "$HOSTNAME" NO_DATA" >> $LOGFAIL # в противном случае, проверяем, просрочился ли сертификат или нет. else GETDATE=`echo $i" "$HOSTNAME" "$RESULT | awk '{print $3,$4,$5,$6}' ` DATECERT=`date +%s -d "$GETDATE"` DATERESULT=`echo $DATECERT - $DATETODAY | bc` # истек или скоро истечет if [ $EXPIRY -gt $DATERESULT ]; then BOOL="NOT OK!!!" echo $i" "$HOSTNAME" "$RESULT $BOOL >> $LOGEXPIRED else # всё в порядке :) BOOL="OK" fi if [ "$VERBOSE" == "YES" ]; then echo $i" "$HOSTNAME" "$RESULT $BOOL; fi if [ "$BOOL" == "OK" ]; then echo $i" "$HOSTNAME" "$RESULT $BOOL>> $LOGGOOD fi fi done # скрипт всегда выводит список истекших/истекающих сертификатов, вне зависмости от $VERBOSE cat $LOGEXPIRED 2>$DN # Report module echo REPORT FOR SSL SCAN >> $REPORTNAME echo $RUNDATE >> $REPORTNAME echo SUBNETS: $SUBNETS >> $REPORTNAME echo >> $REPORTNAME echo ===================================================== >> $REPORTNAME echo EXPIRED CERTIFICATES: >> $REPORTNAME cat $LOGEXPIRED >> $REPORTNAME 2>$DN echo >> $REPORTNAME echo ===================================================== >> $REPORTNAME echo GOOD CERTIFICATES: >> $REPORTNAME cat $LOGGOOD >> $REPORTNAME 2>$DN echo >> $REPORTNAME echo ===================================================== >> $REPORTNAME echo FAILED TO CONNECT: >> $REPORTNAME cat $LOGFAIL >> $REPORTNAME 2>$DN echo >> $REPORTNAME echo ===================================================== >> $REPORTNAME echo NO REVERSE DNS ZONE: >> $REPORTNAME cat $LOGNOREV >> $REPORTNAME 2>$DN echo >> $REPORTNAME