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
    
    Поделиться публикацией

    Комментарии 18

      +2
      хорошая работа проделана, ставлю плюс. Но я согласился бы с вашими коллегами, что нужно просто составить перечень серверов и сертификатов. Но ваш вариант может оказаться более точен и обширен.
        +1
        Все правильно сделал. Необслуживаемые системы рулят.
          0
          Третий раз читаю Ваш комментарий и не могу понять — негативный он или позитивный.
            0
            Позитивный, аллюзия с рекламным роликом случайна. )
          0
          удалять файлы отчетов, да еще через rm -f, как минимум недальновидно. можно дописывать, можно добавлять к имени дату и время.
            0
            а зачем хранить столько файлов от каждой прогонки скрипта?
            0
            Хорошо бы выхлоп скрипта намылить себе, любимому…
            А в случае, если до истечения срока валидности сертификата осталось ней, меньше разумного минимума, еще и в Jabber стукнуть…
            Опять же, можно не одного админа уведомить, а всех…
            Как показывает практика, отчеты сервера, автоматически сортированные sieve, продублированные в IM весьма полезны.
              0
              скрипт был прикручен к нагиос-у, который уже и уведомляет по почте и IM админчиков.
                0
                Этого слона я не заметил в тексте статьи ;)

                Nagios — хорошо, но и без него можно…
                Вы показали хороший пример…
                Но с предложенной доработкой напильником он может быть еще лучше…
                Или добавить пару абзацем про прикручивание его к…

              0
              ИМХО, правильное место для этой задачи — система мониторинга. Например, Nagios.
                0
                вышеприведенный скрипт легко прикручивается к нагиосу :)
                  0
                  Так все-таки «был прикручен» или «легко прикручивается»?
                  Если первое и у вас уже есть скрипт заточенный по выводу под nagios, то сделайте доброе дело.
                    0
                    я передал скрипт администратору, который ответственен за мониторинг серверов. дальше не интересовался.
                0
                > # куда отправлять stderr, по умолчанию в девнулл :)

                Так пишут только индусы, имхо. Гораздо правильнее писать в начале trap error ERR 1 2 3 4 5 6 7 8 9 10 и т д.
                  0
                  И отслеживать процесс по имени (а не по PID) — тоже криво.
                    0
                    поясните про имя и пид. у меня реализовано через пид.
                      0
                      Да, точно. Ошибся ((
                    0
                    почитал на эту тему. не подозревал даже, что баш может это хендлить. спасибо, буду использовать.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое