Недавно у нас встала задача мониторинга срока действия сертификатов на серверах с Windows. Ну как встала, после того, как сертификаты несколько раз превращались в тыкву, в то самое время, когда ответственный за их продление бородатый коллега находился в отпуске. После этого мы с ним что-то заподозрили решили над этим задуматься. Так как у нас не спеша внедряется система мониторинга NetXMS, она и стала главным и, в принципе, единственным кандидатом на эту задачу.
Результат в итоге был получен в таком виде:
А сам процесс далее.
Поехали. Встроенного счетчика заканчивающегося срока сертификатов в NetXMS нет, поэтому нужно создавать свой и использовать скрипты для обеспечения его данными. Конечно, на Powershell, это же Windows. Скрипт должен прочитать все сертификаты в операционной системе, взять оттуда срок их истечения в днях и передать это число в NetXMS. Через его агента. Вот с него и начнем.
Вариант первый, самый простой. Просто получить количество дней до окончания срока действия сертификата с ближайшей датой.
Чтобы сервер NetXMS узнал о существовании нашего кастомного параметра, он должен получить его от агента. Иначе этот параметр невозможно будет добавить, по причине его отсутствия. Поэтому, в конфигурационный файл агента nxagentd.conf мы добавляем строку внешнего параметра с именем HTTPS.CertificateExpireDateSimple, в которой прописываем запуск скрипта:
Учитывая, что скрипт запускается по сети, нужно не забыть про Execution Policy, а так же не забыть прочие "-NoLogo -NoProfile -NonInteractive", которые я опустил для лучшей читаемости кода.
В результате чего конфиг агента выглядит примерно так:
После этого нужно сохранить конфиг и перезапустить агента. Можно это сделать из консоли NetXMS: открыть конфиг (Edit agent’s confuguration file), отредактировать, выполнить Save&Apply, в результате чего, собственно, произойдет то же самое. Потом перечитать конфигурацию (Poll > Configuration), если совсем нет сил подождать. После этих действий должна появиться возможность добавить наш кастомный параметр.
В консоли NetXMS идем в Data Collection Configuration подопытного сервера, на котором мы собрались мониторить сертификаты и создаем там новый параметр (в дальнейшем, после настройки, есть смысл перенести его в шаблоны). Выбираем HTTPS.CertificateExpireDateSimple из списка, вписываем Description с понятным именем, тип ставим Integer и настраиваем интервал опроса. Слишком коротким его нет смысла делать, чтобы не забивать базу лишней информацией, слишком длинным неудобно будет ждать при проверке. Для сертификатов я обычно ставлю 600 секунд. На время отладки есть смысл сделать его покороче, 30 секунд, например:
Всё, готово, пока достаточно. Можно проверять… нет, еще рано. Сейчас, естественно, ничего мы не получим. Просто потому, что скрипт еще не написали. Исправляем это упущение. Скрипт будет выдавать просто цифру, количество дней, оставшихся до окончания срока действия сертификата. Самую минимальную из всех имеющихся. Пример скрипта:
Получается так:
723 дня, до истечения срока действия сертификата еще почти два года. Логично, потому что сертификаты на Exchange тестового стенда я перевыписывал совсем недавно.
Это был простой вариант. Наверное, кого-то и такой устроит, но нам хотелось большего. Задачу мы себе поставили получить список всех сертификатов на сервере, поименно, и у каждого видеть количество дней, оставшихся до окончания срока действия сертификата.
Второй вариант, несколько посложнее.
Опять редактируем конфиг агента и там вместо строчки с ExternalParameter пишем две другие:
В ExternalList мы получаем просто список строк. В нашем случае, список строк с именами сертификатов. Список этих строк мы получим скриптом. Имя списка — HTTPS.CertificateNames.
Скрипт NetXMS_CertNames.ps1:
А уже в ExternalParameter мы на вход подаем строки из списка ExternalList, а на выходе получим всё то же количество дней для каждой. Идентификатором служит Thumbprint сертификата. Обратите внимание, что HTTPS.CertificateExpireDate в этом варианте содержит звездочку (*). Это необходимо для того, чтобы он принимал внешние переменные, как раз наш CertificateId.
Скрипт NetXMS_CertExpireDate.ps1:
В Data Collection Configuration сервера создаем новый параметр. В Parameter выбираем наш HTTPS.CertificateExpireDate(*) из списка, и, (внимание!) меняем звездочку на {instance}. Этот важный момент позволит создать отдельный счетчик для каждого инстанса (сертификата). Остальное заполняется, как в предыдущем варианте:
Для того, чтобы счетчики было из чего создавать, на вкладке Instance Discovery нужно выбрать Agent List из списка и в поле List Name вписать имя нашего ExternalList из скрипта — HTTPS.CertificateNames.
Почти готово, немного подождать или принудительно сделать Poll > Configuration и Poll > Instance Discovery, если совсем невозможно ждать. В результате получаем все наши сертификаты со сроками действия:
То, что надо? Ну да, только червячок перфекционизма смотрит на этот ненужный Thumbprint в имени счетчика печальными глазами и никак не дает закончить статью. Чтобы его накормить, снова открываем свойства счетчика и на вкладке Instance Discovery в поле «Instance discovery filter script» добавляем написанный на NXSL (внутреннем языке NetXMS) скрипт:
который будет отфильтровывать Thumbprint:
А чтобы его отобразить отфильтрованным, на вкладке General в поле Description меняем CertificateExpireDate: {instance} на CertificateExpireDate: {instance-name}:
Всё, наконец-то финиш из КДПВ:
Красота же?
Осталось настроить оповещения, чтобы они приходили на почту, когда срок сертификата подходит к своему логическому концу.
1. Сначала нужно создать шаблон события (Event Template) для активации его при уменьшении значения счетчика до какого-то заданного нами порога. В Event Configuration создаем два новых шаблона с именами, скажем CertificateExpireDate_Threshold_Activate со статусом Warning:
и аналогичный CertificateExpireDate_Threshold_Deactivate со статусом Normal.
2. Дальше идем в свойства счетчика и на вкладке Tresholds настраиваем порог:
где выбираем наши созданные эвенты CertificateExpireDate_Threshold_Activate и CertificateExpireDate_Threshold_Deactivate, ставим количество замеров (Samples) 1 (конкретно для этого счетчика больше ставить смысла нет), значение в 30 (дней), к примеру, и, что важно, настраиваем время повторения эвента. Для сертификатов в продакшене я ставлю раз в сутки (86400 секунд), иначе можно утонуть в оповещениях (что, кстати и случилось один раз, причем так, что переполнился почтовый ящик за выходные). На время отладки есть смысл поставить поменьше, 60 секунд, к примеру.
3. В Action Configuration создаем шаблон письма оповещения, типа такого:
Все эти %m, %S и т.п. — макросы, в которые будут подставлены значения из нашего параметра. Подробнее они описаны в мануале NetXMS.
4. И, наконец, объединяя предыдущие пункты, в Event Processing Policy создаем правило, по которому будет создаваться Alarm и отправляться письмо:
Сохраняем политику, всё, можно тестировать. Поставим порог повыше, для проверки. У меня ближайший сертификат истекает через 723 дня, для проверки поставил 724. В результате получим такой аларм:
и такое оповещение по почте:
Вот теперь точно всё. Можно было бы, конечно, настроить дашборд, построить графики, но для сертификатов это будут несколько бессмысленные и скучные прямые линии, в отличие от графиков загрузки процессора или памяти, например. Но, об этом как-нибудь в другой раз.
Результат в итоге был получен в таком виде:
А сам процесс далее.
Поехали. Встроенного счетчика заканчивающегося срока сертификатов в NetXMS нет, поэтому нужно создавать свой и использовать скрипты для обеспечения его данными. Конечно, на Powershell, это же Windows. Скрипт должен прочитать все сертификаты в операционной системе, взять оттуда срок их истечения в днях и передать это число в NetXMS. Через его агента. Вот с него и начнем.
Вариант первый, самый простой. Просто получить количество дней до окончания срока действия сертификата с ближайшей датой.
Чтобы сервер NetXMS узнал о существовании нашего кастомного параметра, он должен получить его от агента. Иначе этот параметр невозможно будет добавить, по причине его отсутствия. Поэтому, в конфигурационный файл агента nxagentd.conf мы добавляем строку внешнего параметра с именем HTTPS.CertificateExpireDateSimple, в которой прописываем запуск скрипта:
ExternalParameter = HTTPS.CertificateExpireDateSimple: powershell.exe -File "\\server\share\NetXMS_CertExpireDateSimple.ps1"
Учитывая, что скрипт запускается по сети, нужно не забыть про Execution Policy, а так же не забыть прочие "-NoLogo -NoProfile -NonInteractive", которые я опустил для лучшей читаемости кода.
В результате чего конфиг агента выглядит примерно так:
#
# NetXMS agent configuration file
# Created by agent installer at Thu Jun 13 11:24:43 2019
#
MasterServers = netxms.corp.testcompany.ru
ConfigIncludeDir = C:\NetXMS\etc\nxagentd.conf.d
LogFile = {syslog}
FileStore = C:\NetXMS\var
SubAgent = ecs.nsm
SubAgent = filemgr.nsm
SubAgent = ping.nsm
SubAgent = logwatch.nsm
SubAgent = portcheck.nsm
SubAgent = winperf.nsm
SubAgent = wmi.nsm
ExternalParameter = HTTPS.CertificateExpireDateSimple: powershell.exe -File "\\server\share\NetXMS_CertExpireDateSimple.ps1"
После этого нужно сохранить конфиг и перезапустить агента. Можно это сделать из консоли NetXMS: открыть конфиг (Edit agent’s confuguration file), отредактировать, выполнить Save&Apply, в результате чего, собственно, произойдет то же самое. Потом перечитать конфигурацию (Poll > Configuration), если совсем нет сил подождать. После этих действий должна появиться возможность добавить наш кастомный параметр.
В консоли NetXMS идем в Data Collection Configuration подопытного сервера, на котором мы собрались мониторить сертификаты и создаем там новый параметр (в дальнейшем, после настройки, есть смысл перенести его в шаблоны). Выбираем HTTPS.CertificateExpireDateSimple из списка, вписываем Description с понятным именем, тип ставим Integer и настраиваем интервал опроса. Слишком коротким его нет смысла делать, чтобы не забивать базу лишней информацией, слишком длинным неудобно будет ждать при проверке. Для сертификатов я обычно ставлю 600 секунд. На время отладки есть смысл сделать его покороче, 30 секунд, например:
Всё, готово, пока достаточно. Можно проверять… нет, еще рано. Сейчас, естественно, ничего мы не получим. Просто потому, что скрипт еще не написали. Исправляем это упущение. Скрипт будет выдавать просто цифру, количество дней, оставшихся до окончания срока действия сертификата. Самую минимальную из всех имеющихся. Пример скрипта:
try {
# Получаем все сертификаты из хранилища сертификатов
$lmCertificates = @( Get-ChildItem -Recurse -path 'Cert:\LocalMachine\My' -ErrorAction Stop )
# Если сертификатов нет, вернуть "10 лет"
if ($lmCertificates.Count -eq 0) { return 3650 }
# Получаем Expiration Date всех сертификатов
$expirationDates = @( $lmCertificates | ForEach-Object { return $_.NotAfter } )
# Получаем наиболее близкий Expiration Date из всех
$minExpirationDate = ($expirationDates | Measure-Object -Minimum -ErrorAction Stop ).Minimum
# Конвертируем наиболее близкий Expiration Date в количество оставшихся дней с округлением в меньшую сторону
$daysLeft = [Math]::Floor( ($minExpirationDate - [DateTime]::Now).TotalDays )
# Возвращаем значение
return $daysLeft
}
catch {
return -1
}
Получается так:
723 дня, до истечения срока действия сертификата еще почти два года. Логично, потому что сертификаты на Exchange тестового стенда я перевыписывал совсем недавно.
Это был простой вариант. Наверное, кого-то и такой устроит, но нам хотелось большего. Задачу мы себе поставили получить список всех сертификатов на сервере, поименно, и у каждого видеть количество дней, оставшихся до окончания срока действия сертификата.
Второй вариант, несколько посложнее.
Опять редактируем конфиг агента и там вместо строчки с ExternalParameter пишем две другие:
ExternalList = HTTPS.CertificateNames: powershell.exe -File "\\server\share\NetXMS_CertNames.ps1"
ExternalParameter = HTTPS.CertificateExpireDate(*): powershell.exe -File "\\server\share\NetXMS_CertExpireDate.ps1" -CertificateId "$1"
В ExternalList мы получаем просто список строк. В нашем случае, список строк с именами сертификатов. Список этих строк мы получим скриптом. Имя списка — HTTPS.CertificateNames.
Скрипт NetXMS_CertNames.ps1:
#Список возможных имен сертификатов
$nameTypeList = @(
[System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName,
[System.Security.Cryptography.X509Certificates.X509NameType]::DnsName,
[System.Security.Cryptography.X509Certificates.X509NameType]::DnsFromAlternativeName,
[System.Security.Cryptography.X509Certificates.X509NameType]::UrlName,
[System.Security.Cryptography.X509Certificates.X509NameType]::EmailName,
[System.Security.Cryptography.X509Certificates.X509NameType]::UpnName
)
#Ищем все сертификаты, имеющие закрытый ключ
$certList = @( Get-ChildItem -Path 'Cert:\LocalMachine\My' | Where-Object { $_.HasPrivateKey -eq $true } )
#Проходим по списку сертификатов, формируем строку "Имя сертификата - Дата - Thumbprint" и возвращаем её
foreach ($cert in $certList) {
$name = '(unknown name)'
try {
$thumbprint = $cert.Thumbprint
$dateExpire = $cert.NotAfter
foreach ($nameType in $nameTypeList) {
$name_temp = $cert.GetNameInfo( $nameType, $false)
if ($name_temp -ne $null -and $name_temp -ne '') {
$name = $name_temp;
break;
}
}
Write-Output "$($name) - $($dateExpire.ToString('dd.MM.yyyy')) - [T:$($thumbprint)]"
}
catch {
Write-Error -Message "Error processing certificate list: $($_.Exception.Message)"
}
}
А уже в ExternalParameter мы на вход подаем строки из списка ExternalList, а на выходе получим всё то же количество дней для каждой. Идентификатором служит Thumbprint сертификата. Обратите внимание, что HTTPS.CertificateExpireDate в этом варианте содержит звездочку (*). Это необходимо для того, чтобы он принимал внешние переменные, как раз наш CertificateId.
Скрипт NetXMS_CertExpireDate.ps1:
#Определяем входящий параметр $CertificateId
param (
[Parameter(Mandatory=$false)]
[String]$CertificateId
)
#Проверка на существование
if ($CertificateId -eq $null) {
Write-Error -Message "CertificateID parameter is required!"
return
}
#По Thumbprint из строки в $CertificateId ищем сертификат и определяем его Expiration Date
$certId = $CertificateId;
try {
if ($certId -match '^.*\[T:(?<Thumbprint>[A-Z0-9]+)\]$') {
$thumbprint = $Matches['Thumbprint']
$certificatePath = "Cert:\LocalMachine\My\$($thumbprint)"
if (Test-Path -PathType Leaf -Path $certificatePath ) {
$certificate = Get-Item -Path $certificatePath;
$certificateExpirationDate = $certificate.NotAfter
$certificateDayToLive = [Math]::Floor( ($certificateExpirationDate - [DateTime]::Now).TotalDays )
Write-Output "$($certificateDayToLive)";
}
else {
Write-Error -Message "No certificate matching this thumbprint found on this server $($certId)"
}
}
else {
Write-Error -Message "CertificateID provided in wrong format. Must be FriendlyName [T:<thumbprint>]"
}
}
catch {
Write-Error -Message "Error while executing script: $($_.Exception.Message)"
}
В Data Collection Configuration сервера создаем новый параметр. В Parameter выбираем наш HTTPS.CertificateExpireDate(*) из списка, и, (внимание!) меняем звездочку на {instance}. Этот важный момент позволит создать отдельный счетчик для каждого инстанса (сертификата). Остальное заполняется, как в предыдущем варианте:
Для того, чтобы счетчики было из чего создавать, на вкладке Instance Discovery нужно выбрать Agent List из списка и в поле List Name вписать имя нашего ExternalList из скрипта — HTTPS.CertificateNames.
Почти готово, немного подождать или принудительно сделать Poll > Configuration и Poll > Instance Discovery, если совсем невозможно ждать. В результате получаем все наши сертификаты со сроками действия:
То, что надо? Ну да, только червячок перфекционизма смотрит на этот ненужный Thumbprint в имени счетчика печальными глазами и никак не дает закончить статью. Чтобы его накормить, снова открываем свойства счетчика и на вкладке Instance Discovery в поле «Instance discovery filter script» добавляем написанный на NXSL (внутреннем языке NetXMS) скрипт:
instance = $1;
if (instance ~= "^(.*)\s\-\s\[T\:[a-zA-Z0-9]+\]$")
{
return %(true, instance, $1);
}
return true;
который будет отфильтровывать Thumbprint:
А чтобы его отобразить отфильтрованным, на вкладке General в поле Description меняем CertificateExpireDate: {instance} на CertificateExpireDate: {instance-name}:
Всё, наконец-то финиш из КДПВ:
Красота же?
Осталось настроить оповещения, чтобы они приходили на почту, когда срок сертификата подходит к своему логическому концу.
1. Сначала нужно создать шаблон события (Event Template) для активации его при уменьшении значения счетчика до какого-то заданного нами порога. В Event Configuration создаем два новых шаблона с именами, скажем CertificateExpireDate_Threshold_Activate со статусом Warning:
и аналогичный CertificateExpireDate_Threshold_Deactivate со статусом Normal.
2. Дальше идем в свойства счетчика и на вкладке Tresholds настраиваем порог:
где выбираем наши созданные эвенты CertificateExpireDate_Threshold_Activate и CertificateExpireDate_Threshold_Deactivate, ставим количество замеров (Samples) 1 (конкретно для этого счетчика больше ставить смысла нет), значение в 30 (дней), к примеру, и, что важно, настраиваем время повторения эвента. Для сертификатов в продакшене я ставлю раз в сутки (86400 секунд), иначе можно утонуть в оповещениях (что, кстати и случилось один раз, причем так, что переполнился почтовый ящик за выходные). На время отладки есть смысл поставить поменьше, 60 секунд, к примеру.
3. В Action Configuration создаем шаблон письма оповещения, типа такого:
Все эти %m, %S и т.п. — макросы, в которые будут подставлены значения из нашего параметра. Подробнее они описаны в мануале NetXMS.
4. И, наконец, объединяя предыдущие пункты, в Event Processing Policy создаем правило, по которому будет создаваться Alarm и отправляться письмо:
Сохраняем политику, всё, можно тестировать. Поставим порог повыше, для проверки. У меня ближайший сертификат истекает через 723 дня, для проверки поставил 724. В результате получим такой аларм:
и такое оповещение по почте:
Вот теперь точно всё. Можно было бы, конечно, настроить дашборд, построить графики, но для сертификатов это будут несколько бессмысленные и скучные прямые линии, в отличие от графиков загрузки процессора или памяти, например. Но, об этом как-нибудь в другой раз.