При работе с множеством сайтов возникает необходимость в постоянном контроле срока окончания доменов и особенно сертификатов т.к. в основном используются 90-дневненые «Let’s Encrypt», и к тому же на многих хостингах нет автоматического продления.
Для этих целей был написан PHP-скрипт для мониторинга (скачать с GitHub), который помещаются в родительскую категорию сайта и состоит из следующих файлов:
monitoring/ ├── config.php ├── cron.php ├── chache.json ├── calendar.php
Далее подробнее о каждом файле:
Конфигурационный файл, содержит настройки локали PHP, e-mail для уведомлений и списки проверяемых доменов в виде массивов.
<?php
// Ошибки PHP.
//error_reporting(E_ALL);
//ini_set('display_errors', 1);
// Локаль.
setlocale(LC_ALL, 'ru_RU.utf8');
date_default_timezone_set('Europe/Moscow');
header('Content-type: text/html; charset=utf-8');
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
mb_http_output('UTF-8');
mb_language('uni');
// E-mail для уведомлений.
$email = 'mail@example.com';
// За сколько отправлять уведомление.
$warn = 259200; // 3 дня
$domains = array(
'php.ru',
'php.su',
'php.net',
'habr.com',
'wikipedia.org',
);
$certificates = array(
'php.ru',
'php.su',
'php.net',
'habr.com',
'wikipedia.org',
);
PHP-обработчик, получает даты окончания делегирования доменов через серверы whois и сроки действия SSL-сертификатов, далее сохраняет полученные даты в файл chache.json. В случаи ошибки или приближающейся даты окончания отправит сообщение на почту.
<?php
require_once __DIR__ . '/config.php';
$chache = array();
$mail_text = array();
// Проверка доменов
foreach ($domains as $domain) {
$date = 0;
$zone = explode('.', $domain);
$zone = end($zone);
switch ($zone) {
case 'ru':
case 'su':
case 'рф': $server = 'whois.tcinet.ru'; break;
case 'com':
case 'net': $server = 'whois.verisign-grs.com'; break;
case 'org': $server = 'whois.pir.org'; break;
}
$socket = fsockopen($server, 43);
if ($socket) {
fputs($socket, $domain . PHP_EOL);
while (!feof($socket)) {
$res = fgets($socket, 128);
if (mb_stripos($res, 'paid-till:') !== false) {
$date = explode('paid-till:', $res);
$date = strtotime(trim($date[1]));
break;
}
if (mb_stripos($res, 'Registry Expiry Date:') !== false) {
$date = explode('Registry Expiry Date:', $res);
$date = strtotime(trim($date[1]));
break;
}
}
fclose($socket);
}
if (!empty($date) && time() + $warn > $date) {
$mail_text[] = $domain . ' - заканчивается ' . date('d.m.Y H:i', $date);
} elseif (empty($date)) {
$mail_text[] = $domain . ' - не удалось получить whois';
}
$chache['domains'][$domain] = $date;
}
// Проверка SSL-сертификатов
foreach ($certificates as $domain) {
$date = 0;
$url = 'ssl://' . $domain . ':443';
$context = stream_context_create(
array(
'ssl' => array(
'capture_peer_cert' => true,
'verify_peer' => false,
'verify_peer_name' => false
)
)
);
$fp = @stream_socket_client($url, $err_no, $err_str, 30, STREAM_CLIENT_CONNECT, $context);
$cert = @stream_context_get_params($fp);
if (empty($err_no)) {
$info = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
$date = $info['validTo_time_t'];
}
if (!empty($date) && time() + $warn > $date) {
$mail_text[] = $domain . ' - заканчивается сертификат ' . date('d.m.Y H:i', $date);
} elseif (empty($date)) {
$mail_text[] = $domain . ' - не удалось получить сертификат';
}
$chache['certificates'][$domain] = $date;
}
// Сохранение в файл.
file_put_contents(__DIR__ . '/chache.json', json_encode($chache));
// Вывод данных в браузер.
echo '<pre>' . print_r($chache, true) . '</pre>';
// Отправка уведомления.
if (!empty($mail_text)) {
mb_send_mail(
$email,
'Мониторинг срока действия доменов и SSL-сертификатов',
implode('<br>', $mail_text),
"MIME-Version: 1.0\r\nContent-Type: text/html;"
);
}
Whois-сервер для других доменных зон можно получить здесь.
Данный скрипт должен запускаться по крону, например раз в день, в 9 утра:
0 9 * * * /usr/local/bin/wget -O - -q "https://example.com/monitoring/cron.php"
Письмо уведомление будет примерно следующего вида:
* Чтобы письма не попадали в спам, лучше сделать отправку писем через SMTP.
Скрипт носит чисто информационный характер и выводит календарь с выделенными датами окончания сертификатов и доменов. Выполняется прямым вызовом https://ваш_домен/monitoring/calendar.php
.
<?php
require_once __DIR__ . '/config.php';
class Calendar
{
/**
* Вывод календаря на один месяц.
*/
public static function getMonth($month, $year, $events = array())
{
$months = array(
1 => 'Январь',
2 => 'Февраль',
3 => 'Март',
4 => 'Апрель',
5 => 'Май',
6 => 'Июнь',
7 => 'Июль',
8 => 'Август',
9 => 'Сентябрь',
10 => 'Октябрь',
11 => 'Ноябрь',
12 => 'Декабрь'
);
$month = intval($month);
$out = '
<div class="calendar-item">
<div class="calendar-head">' . $months[$month] . ' ' . $year . '</div>
<table>
<tr>
<th>Пн</th>
<th>Вт</th>
<th>Ср</th>
<th>Чт</th>
<th>Пт</th>
<th>Сб</th>
<th>Вс</th>
</tr>';
$day_week = date('N', mktime(0, 0, 0, $month, 1, $year));
$day_week--;
$out.= '<tr>';
for ($x = 0; $x < $day_week; $x++) {
$out.= '<td></td>';
}
$days_counter = 0;
$days_month = date('t', mktime(0, 0, 0, $month, 1, $year));
for ($day = 1; $day <= $days_month; $day++) {
if (date('j.n.Y') == $day . '.' . $month . '.' . $year) {
$class = 'today';
} elseif (time() > strtotime($day . '.' . $month . '.' . $year)) {
$class = 'last';
} else {
$class = '';
}
$event_show = false;
$event_text = array();
if (!empty($events)) {
foreach ($events as $date => $text) {
$date = explode('.', $date);
if (count($date) == 3) {
$y = explode(' ', $date[2]);
if (count($y) == 2) {
$date[2] = $y[0];
}
if ($day == intval($date[0]) && $month == intval($date[1]) && $year == $date[2]) {
$event_show = true;
$event_text[] = $text;
}
} elseif (count($date) == 2) {
if ($day == intval($date[0]) && $month == intval($date[1])) {
$event_show = true;
$event_text[] = $text;
}
} elseif ($day == intval($date[0])) {
$event_show = true;
$event_text[] = $text;
}
}
}
if ($event_show) {
$out.= '<td class="calendar-day ' . $class . ' event">' . $day;
if (!empty($event_text)) {
$out.= '<div class="calendar-popup">' . implode('<br>', $event_text) . '</div>';
}
$out.= '</td>';
} else {
$out.= '<td class="calendar-day ' . $class . '">' . $day . '</td>';
}
if ($day_week == 6) {
$out.= '</tr>';
if (($days_counter + 1) != $days_month) {
$out.= '<tr>';
}
$day_week = -1;
}
$day_week++;
$days_counter++;
}
$out .= '</tr></table></div>';
return $out;
}
/**
* Вывод календаря на несколько месяцев.
*/
public static function getInterval($start, $end, $events = array())
{
$curent = explode('.', $start);
$curent[0] = intval($curent[0]);
$end = explode('.', $end);
$end[0] = intval($end[0]);
$begin = true;
$out = '<div class="calendar-wrp">';
do {
$out .= self::getMonth($curent[0], $curent[1], $events);
if ($curent[0] == $end[0] && $curent[1] == $end[1]) {
$begin = false;
}
$curent[0]++;
if ($curent[0] == 13) {
$curent[0] = 1;
$curent[1]++;
}
} while ($begin == true);
$out .= '</div>';
return $out;
}
}
?><!DOCTYPE html>
<html lang="ru">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Мониторинг срока действия доменов и SSL-сертификатов</title>
<style type="text/css">
body {font: 14px/1.2 Arial, sans-serif;}
.wrapper {width: 940px;padding: 15px;margin: 0 auto;}
.errors {margin-bottom: 20px;border: 1px solid red;padding: 15px;background: #fff4f4;color: red;}
/* Стили календаря */
.calendar-item {width: 200px;display: inline-block;vertical-align: top;margin: 0 16px 20px;}
.calendar-head {text-align: center;padding: 5px;font-weight: 700;font-size: 14px;}
.calendar-item table {border-collapse: collapse;width: 100%;}
.calendar-item th {font-size: 12px;padding: 6px 7px;text-align: center;color: #888;font-weight: normal;}
.calendar-item td {font-size: 13px;padding: 6px 5px;text-align: center;border: 1px solid #ddd;}
.calendar-item tr th:nth-child(6), .calendar-item tr th:nth-child(7),
.calendar-item tr td:nth-child(6), .calendar-item tr td:nth-child(7) {color: #e65a5a;}
.calendar-day.last {color: #999 !important;}
.calendar-day.today {font-weight: bold;}
.calendar-day.event {background: #ffadad;position: relative;cursor: pointer;}
.calendar-day.event:hover .calendar-popup {display: block;}
.calendar-popup {display: none;position: absolute;top: 40px;left: 0;min-width: 200px;padding: 15px;background: #fff;text-align: left;font-size: 13px;z-index: 100;box-shadow: 0 0 10px rgba(0,0,0,0.5);color: #000;}
.calendar-popup:before {content: ""; border: solid transparent;position: absolute;left: 8px;bottom: 100%;border-bottom-color: #fff;border-width: 9px;margin-left: 0;}
</style>
</head>
<body>
<div class="wrapper">
<h1>Мониторинг доменов и SSL-сертификатов</h1>
<?php
// Загружаем данные из кеша.
$data = json_decode(file_get_contents(__DIR__ . '/chache.json'), true);
if (empty($data['domains']) && empty($data['certificates'])) {
echo '<div class="errors">Нет данных для отображения</div>';
} else {
// Формируем данные для календаря
$events = array();
$date_end = 0;
if (!empty($data['domains'])) {
foreach ($data['domains'] as $i => $row) {
if (empty($row)) {
$errors[] = 'Не удалось проверить домен ' . $i;
} else {
$events[date('d.m.Y H:i', $row)] = 'Заканчивается домен ' . $i;
if ($row > $date_end) {
$date_end = $row;
}
}
}
}
if (!empty($data['certificates'])) {
foreach ($data['certificates'] as $i => $row) {
if (empty($row)) {
$errors[] = 'Не удалось проверить сертификат ' . $i;
} else {
$events[date('d.m.Y H:i', $row)] = 'Заканчивается сертификат ' . $i;
if ($row > $date_end) {
$date_end = $row;
}
}
}
}
// Вывод ошибок.
if (!empty($errors)) {
echo '<div class="errors">' . implode('<br>', $errors) . '</div>';
}
// Вывод календаря.
if (!empty($date_end)) {
echo Calendar::getInterval(date('m.Y'), date('m.Y', $date_end), $events);
}
}
?>
</div>
</body>
</html>
Для зоны *.kz есть альтернативы whois.nic.kz? С этим не определяет дату.
При проверки доменов не возвращает дату!
Notice: Undefined variable: server in /usr/share/zabbix/cron.php on line 22
Warning: fsockopen(): php_network_getaddresses: getaddrinfo failed: Имя или служба не известны in /usr/share/zabbix/cron.php on line 22
Warning: fsockopen(): unable to connect to :43 (php_network_getaddresses: getaddrinfo failed: Имя или служба не известны) in /usr/share/zabbix/cron.php on line 22
---- cron.php -------------------------
switch ($zone) {
case 'com': $server = 'whois.crsnic.net'; break;
case 'net': $server = 'whois.crsnic.net'; break;
case 'org': $server = 'whois.crsnic.net'; break;
case 'com.ua': $server = 'whois.crsnic.net'; break;
case 'gov.ua': $server = 'whois.crsnic.net'; break;
case 'org.ua': $server = 'whois.crsnic.net'; break;
}
$socket = fsockopen($server, 43);
if ($socket) {
fputs($socket, $domain . PHP_EOL);
while (!feof($socket)) {
$res = fgets($socket, 128);
if (mb_stripos($res, 'paid-till:') !== false) {
$date = explode('paid-till:', $res);
$date = strtotime(trim($date[1]));
break;
}
if (mb_stripos($res, 'Registry Expiry Date:') !== false) {
$date = explode('Registry Expiry Date:', $res);
$date = strtotime(trim($date[1]));
break;
}
}
fclose($socket);
}
Но посылается в зашифрованные письма в bas64
Если хотите не шифровать то надо поменять mb_send_mail , на mail