Небольшой скрипт в виде сценария (или метода) для контроля сроков действия SSL сертификата сайта с отправкой статуса в Telegram.
Возникла задача мониторить сроки действия SSL-сертификатов некоторых сайтов. Что особенно актуально при использовании сертификатов от бесплатных сервисов Let's Encrypt и ему подобных, которые выпускают сертификаты на короткий срок (3 месяца).
В основе лежит функция checkSSL ($domain, $port)
, которая в качестве обязательного параметра принимает доменное имя (DNS) конкретного сайта. Опционально можно указать порт, если используется не стандартный 443-ий. Функция возвращает ассоциативный массив со значениями:
Пример использования функции checkSSL.
$report = checkSSL('connect.smartliving.ru');
echo 'Статус = ' . $report['status'] . '<br>';
echo 'Действителен до = ' . $report['validTo'] . '<br>';
echo 'Осталось дней = ' . $report['validDays'] . '<br>';
Полный код функции checkSSL выглядит следующим образом.
function checkSSL ($domain, $port = 443)
{
$result = array('status' => false, 'validTo' => '', 'validDays' => '');
$stream = stream_context_create(array('ssl' => array('capture_peer_cert' => true)));
$socket = stream_socket_client("ssl://$domain:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $stream);
if ($socket) {
$cont = stream_context_get_params($socket);
$cert_ressource = $cont['options']['ssl']['peer_certificate'];
$cert = openssl_x509_parse($cert_ressource);
$namepart = explode('CN=', $cert['name']);
if (count($namepart) == 2) {
$cert_domain = trim($namepart[1], '*. ');
$check_domain = substr($domain, -strlen($cert_domain));
$result['status'] = ($cert_domain == $check_domain);
$result['validTo'] = date('Y-m-d H:i:s', $cert['validTo_time_t']);
$result['validDays'] = date_diff(new DateTime(), new DateTime($result['validTo']))->days;
}
}
return $result;
}
Для удобства использования обернул все это дело в класс SSLChecker, в котором объекты - это сайты, которые требуется мониторить. В свойствах объектов указывается доменное имя сайта (domain) и хранятся текущие состояния его сертификата (status, validTo, validDays). Классовый метод checkSSL отвечает непосредственно за выполнение контроля с помощью функции checkSSL() и запись в свойства объекта актуальных данных.
Код классового метода checkSSL.
DebMes('Checking SSL status for ' . $this->getProperty('domain'));
$port = $this->getProperty('port');
$report = checkSSL($this->getProperty('domain'), ($port != '' && $port > 0) ? $port : 443);
$this->setProperty('status', ($report['status'] && $report['validDays'] > 0) ? 1 : 0);
$this->setProperty('validTo', $report['validTo']);
$this->setProperty('validDays', $report['validDays']);
$this->setProperty('lastCheck', date('Y-m-d H:i:s'));
function checkSSL ($domain, $port = 443)
{
$result = array('status' => false, 'validTo' => '', 'validDays' => '');
$stream = stream_context_create(array('ssl' => array('capture_peer_cert' => true)));
$socket = stream_socket_client("ssl://$domain:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $stream);
if ($socket) {
$cont = stream_context_get_params($socket);
$cert_ressource = $cont['options']['ssl']['peer_certificate'];
$cert = openssl_x509_parse($cert_ressource);
$namepart = explode('CN=', $cert['name']);
if (count($namepart) == 2) {
$cert_domain = trim($namepart[1], '*. ');
$check_domain = substr($domain, -strlen($cert_domain));
$result['status'] = ($cert_domain == $check_domain);
$result['validTo'] = date('Y-m-d H:i:s', $cert['validTo_time_t']);
$result['validDays'] = date_diff(new DateTime(), new DateTime($result['validTo']))->days;
}
}
return $result;
}
Далее любым удобным способом настраивается вызов методов checkSSL по расписанию. Я раз в сутки выполняю сценарий, который берет все объекты класса и последовательно (в фоне) запускает метод каждого объекта.
$sites = getObjectsByClass('SSLChecker');
foreach ($sites as $site) {
callMethodSafe($site['TITLE'] . '.checkSSL');
}
Чтобы получать отчеты в Telegram использую такой сценарий.
$report = "<b>Состояние SSL-сертификатов</b> \n\n";
$sites = getObjectsByClass('SSLChecker');
foreach ($sites as $site) {
$status = getGlobal($site['TITLE'] . '.status');
$domain = getGlobal($site['TITLE'] . '.domain');
$days = getGlobal($site['TITLE'] . '.validDays');
$validTo = getGlobal($site['TITLE'] . '.validTo');
$lastCheck = getGlobal($site['TITLE'] . '.lastCheck');
if ($status == 1) {
$report .= "✅";
} else {
$report .= "‼️";
}
$report .= ' ' . $domain . ' (' . getNumberWord($days, array('день', 'дня', 'дней')) . ") \n";
$report .= ' <code>' . $validTo . '</code>';
$report .= "\n";
}
$report .= " \n Последняя проверка:<i> $lastCheck</i>";
include_once (DIR_MODULES . 'telegram/telegram.class.php');
$telegram_module = new telegram();
$telegram_module->sendMessageToAdmin($report);
Результат.