Добавить в корзинуПозвонить
Найти в Дзене

Zabbix. Python. Проверка SSL сертификатов.

Продолжаем насущную тему по мониторингу собственных сайтов. В предыдущей статье мы написали проверку кода и времени ответа наших сайтов. Сегодня немного доработаем функционал, добавив проверки SSL сертификатов. Данная проверка будет полезна и тем, кто пользуется сертификатами от letsencrypt, обновляя их с помощью certbot, и для тех, кто покупает сертификаты на год и устанавливает их вручную. import ssl
import socket # Создание подключения к домену
socket.create_connection((url, 443)) # Получение данных о сертификате домена
cert = ssock.getpeercert() {'subject': ((('countryName', 'RU'),), (('stateOrProvinceName', 'Moscow'),), (('localityName', 'Moscow'),), (('organizationName', 'VK LLC'),), (('commonName', '*.dzen.ru'),)), 'issuer': ((('countryName', 'BE'),), (('organizationName', 'GlobalSign nv-sa'),), (('commonName', 'GlobalSign RSA OV SSL CA 2018'),)), 'version': 3, 'serialNumber': '2C3249F4856CE7A2FB94F1A0', 'notBefore': 'Jun 3 09:16:26 2025 GMT', 'notAfter': 'Jul 5 09:16:25 202

Продолжаем насущную тему по мониторингу собственных сайтов.

В предыдущей статье мы написали проверку кода и времени ответа наших сайтов. Сегодня немного доработаем функционал, добавив проверки SSL сертификатов.

Данная проверка будет полезна и тем, кто пользуется сертификатами от letsencrypt, обновляя их с помощью certbot, и для тех, кто покупает сертификаты на год и устанавливает их вручную.

  • Начнем с простого, получим данные о SSL сертификате сайта. Для этого нам понадобятся библиотеки ssl и socket.
import ssl
import socket
  • Подключимся через socket к указанному домену по 443 порту и запросим сертификат:
# Создание подключения к домену
socket.create_connection((url, 443))
# Получение данных о сертификате домена
cert = ssock.getpeercert()

  • На выводе мы получим:
{'subject': ((('countryName', 'RU'),), (('stateOrProvinceName', 'Moscow'),), (('localityName', 'Moscow'),), (('organizationName', 'VK LLC'),), (('commonName', '*.dzen.ru'),)), 'issuer': ((('countryName', 'BE'),), (('organizationName', 'GlobalSign nv-sa'),), (('commonName', 'GlobalSign RSA OV SSL CA 2018'),)), 'version': 3, 'serialNumber': '2C3249F4856CE7A2FB94F1A0', 'notBefore': 'Jun 3 09:16:26 2025 GMT', 'notAfter': 'Jul 5 09:16:25 2026 GMT', 'subjectAltName': (('DNS', '*.dzen.ru'), ('DNS', 'dzen.ru')), 'OCSP': ('http://ocsp.globalsign.com/gsrsaovsslca2018',), 'caIssuers': ('http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt',)}

notBefore - Начало действия сертификата

notAfter - Конец действия сертификата

  • Добавим вычисление дней валидности:
-2

Поскольку в notAfter передается дата строкой, нам необходимо перевести ее в формат datetime. Далее - мы вычисляем, сколько дней осталось до окончания действия.

  • Добавляем все это в наш скрипт:
-3

Заметьте, в переменной url я специально удаляю вхождение "https://", так как сокет не сможет подключиться по https схеме, а она в свою очередь важная для запросов через requests.

  • Добавляем параметр в конфигурацию zabbix агента /etc/zabbix/zabbix_agentd.conf
-4
UserParameter=site_ssl_expire[*],/etc/zabbix/scripts/site_discover.py -ssl $1
  • Заходим в веб интерфейс zabbix и начинаем редактировать наш шаблон, добавив новый прототип элемента данных:
-5
  • Видим, что на клиенте список элементов данных уже обновился:
-6
  • Добавим триггеры на истечение:
-7
-8
  • И проверяем, что на клиенте данные уже обновились:
-9

P.S. Забавно, что в момент написания статьи сразу на двух доменах сработал триггер на истечение сертификата.

Если вам не хочется писать шаблон по данному гайду, можете просто скачать его себе, сохранить в формате .json и импортировать на сервере:

{"zabbix_export": {"version": "7.0","template_groups": [{"uuid": "7df96b18c230490a9a0a9e2307226338","name": "Templates"}],"templates": [{"uuid": "0fa4a87904c04c46b22236144299d66d","template": "Check sites","name": "Check sites","groups": [{"name": "Templates"}],"discovery_rules": [{"uuid": "f9232e54f35c4c0c8f13adf89c0bc2e5","name": "Discover sites list","key": "sites.discover","item_prototypes": [{"uuid": "7c456a488fc14c71a55e3ae6da230c17","name": "Check site {#SITE}","type": "TRAP","key": "site_request_time[{#SITE}]","delay": "0"},{"uuid": "b75e45684cbb422e84eb45712bd91b5f","name": "Check {#SITE} SSL cert","key": "site_ssl_expire[{#SITE}]","trigger_prototypes": [{"uuid": "bbdd5c61efc149ea89845b0d93a4ed6a","expression": "last(/Check sites/site_ssl_expire[{#SITE}])<=15","name": "{#SITE} certificate will expire in 15 days","priority": "DISASTER"},{"uuid": "c09cfd88a72d4370b5f8604268758940","expression": "last(/Check sites/site_ssl_expire[{#SITE}])<=60","name": "{#SITE} certificate will expire in 60 days","priority": "AVERAGE"}]},{"uuid": "cca4d40156bf4fd88b0c5194607536cb","name": "Check site {#SITE}","key": "site_status_code[{#SITE}]","trigger_prototypes": [{"uuid": "efbe639a35714506af624a8b75d9b056","expression": "last(/Check sites/site_status_code[{#SITE}])<>200","name": "Site {#SITE} returned not 200 status code","priority": "AVERAGE"}]}]}]}]}}

Скрипт проверки выложил здесь