Мониторинг важных учетных записей Active Directory

monitoring usersДобрый день! Уважаемые читатели и гости IT блога Pyatilistnik.org. В ранней публикции мы изучали методы публикации принтеров в Active Directory. Сегодня мы так же поработаем с активным каталогом и выполним задачу по мониторингу важных учетных записей. Предположим, что в вашей организации есть сотрудники из руководящего состава, и если у их учетных записей появляются какие-то проблемы работоспособности и безопасности, это может быть серьезно. В данной статье я вам покажу пример решения на PowerShell который поможет оперативно получать нужные вам атрибуты и состояния учетной записи с оповещение в чат теоеграм. Думаю, что вы легко его сможете кастомизировать под свои нужны.

Что нужно мониторить у важных учетных записей Active Directory

Давайте я попробую описать требования и алгоритм мониторнга по данной задаче:

  1. Вам необходимо определиться с кругом VIP пользователей и создать для них отдельную группу безопасности, так как ее то мы и будим использовать как точку входа, с кем скрипт будет работать.
  2. Определиться какие атрибуты Active Directory мы будим мониторить, список я приведу ниже
  3. Отправлять результат проверки по критериям на почту и в телеграм группу
  4. Согласовать расписание запуска скрипта

Список атрибутов

  • Enabled - Этот атрибут указывает, активна ли учетная запись пользователя. Если значение равно True, учетная запись включена и пользователь может входить в систему. Если значение False, учетная запись отключена, и пользователь не сможет войти в систему. Будет плохо, если VIP пользователь не сможет работать если его учетная запись стала отключенной.
  •  PasswordNeverExpires - Этот атрибут определяет, истекает ли пароль пользователя. Если значение установлено в True, это означает, что пароль пользователя никогда не истечет, и ему не нужно будет менять его через определенные промежутки времени. Если значение False, пароль будет подлежать истечению в соответствии с политиками безопасности домена. Полезно для понимания, по какой причине стала блокироваться учетная запись.
  •  LockedOut - Этот атрибут указывает, заблокирована ли учетная запись пользователя. Если значение равно True, это означает, что учетная запись заблокирована из-за превышения количества неудачных попыток входа в систему (обычно это связано с политиками безопасности). Если значение False, учетная запись разблокирована и пользователь может войти в систему. Это как раз то событие ID 4740, когда мы искали причину блокировки.
  •  msDS-UserPasswordExpiryTimeComputed - Этот атрибут хранит дату и время, когда истечет срок действия пароля пользователя. Он вычисляется на основе политик паролей, применяемых к учетной записи, и показывает, когда пользователю необходимо будет изменить пароль. Это значение автоматически обновляется при изменении пароля или изменении политик безопасности. (Подробно с атрибутом msDS-UserPasswordExpiryTimeComputed мы уже знакомились)

Скрипт мониторинга VIP пользователей Active Directory с уведомлением

# Определение функции Date, которая возвращает текущую дату и время в формате "yyyy.MM.dd HH:mm:ss"
function Date {Get-Date -Format "yyyy.MM.dd HH:mm:ss"}

# Определение пути к папке для логов, основываясь на имени текущего скрипта
$log_folder = "$PSScriptRoot\Logs\" + $($MyInvocation.MyCommand.Name -replace (".ps1", ""))

# Формирование имени файла лога с текущей датой и временем
$log = "$log_folder\$(Get-Date -Format "yyyy_MM_dd_HH_mm_ss").txt"

# Проверка, существует ли папка для логов; если нет, создаем ее
if (! (Test-Path $log_folder -PathType Container -ErrorAction SilentlyContinue))
{
New-Item $log_folder -ItemType D -Force | Out-Null # Создание папки и подавление вывода
}

# Определение пути к папке для логов о членстве в группах
$group_membership_folder = "$log_folder\Group_Membership"

# Проверка, существует ли папка для логов о членстве в группах; если нет, создаем ее
if (! (Test-Path $group_membership_folder -PathType Container -ErrorAction SilentlyContinue))
{
New-Item $group_membership_folder -ItemType D -Force | Out-Null # Создание папки и подавление вывода
}

# Запись сообщения о начале обработки в лог-файл с текущей датой и временем
"$(Date) Start Processing" | Tee-Object $log -Append

# Определение адреса глобального каталога (GC) для использования в дальнейшем
$GC = "DC01.pyatilistnik.org:3268"

# Определение имени корневого контроллера домена (DC)
$DC_root = "DC1.root.pyatilistnik.org"

# Запись сообщения о том, какой сервер глобального каталога будет использован, в лог-файл
"$(Date) Will use $GC as Global Catalog server" | Tee-Object $log -Append

# Определение массива групп, для которых будет запрашиваться информация о членах
$groups = @("VIP Users")

# Инициализация пустого массива для хранения информации о пользователях
$users_info = @()

# Цикл по каждой группе из массива $groups
foreach ($group in $groups)
{
# Запись в лог о попытке получения членов группы
"$(Date) Trying to get members for group `"$group`"" | Tee-Object $log -Append

# Запись в лог о начале запроса членства группы
"$(Date) Trying to query group membership" | Tee-Object $log -Append # from server $server
$group_members = $null # Инициализация переменной для хранения членов группы

try {
# Запрос членов группы из Active Directory
$group_members = Get-ADGroupMember $group -ErrorAction Stop # -Server $server
}
catch {
# Запись в лог сообщения об ошибке, если запрос не удался
"$(Date) $($_.exception.message)" | Tee-Object $log -Append
}

# Цикл по каждому члену группы
foreach ($group_member in $group_members)
{
# Создание нового объекта для хранения информации о пользователе
$user_info = New-Object PSObject

$user = $null # Инициализация переменной для хранения информации о пользователе
try {
# Запрос информации о пользователе из Active Directory по Distinguished Name
$user = Get-ADUser $group_member.distinguishedName -Properties Enabled, PasswordNeverExpires, LockedOut, "msDS-UserPasswordExpiryTimeComputed" -Server $DC_root -ErrorAction Stop
}
catch {
# Запись в лог сообщения об ошибке, если запрос не удался
"$(Date) $($_.exception.message)" | Tee-Object $log -Append
}

$UserPasswordExpiryTimeComputed = $null

# Преобразование времени истечения срока действия пароля из формата файла в объект DateTime
$UserPasswordExpiryTimeComputed = [datetime]::FromFileTime($user."msDS-UserPasswordExpiryTimeComputed")

# Добавление свойств к объекту user_info с информацией о пользователе
# $user_info | Add-Member -MemberType NoteProperty -Name "Group" -Value $group.DistinguishedName # Закомментировано: добавление Distinguished Name группы (при необходимости)
$user_info | Add-Member -MemberType NoteProperty -Name "Username" -Value $group_member.name # Имя пользователя
$user_info | Add-Member -MemberType NoteProperty -Name "SamAccountName" -Value $group_member.SamAccountName # SamAccountName пользователя
# $user_info | Add-Member -MemberType NoteProperty -Name "SID" -Value $group_member.SID # Закомментировано: добавление SID пользователя (при необходимости)
# $user_info | Add-Member -MemberType NoteProperty -Name "Created" -Value $(Get-Date $user.createTimeStamp -Format "yyyy.MM.dd HH:mm:ss") # Закомментировано: дата создания пользователя (при необходимости)
# $user_info | Add-Member -MemberType NoteProperty -Name "Password Last Set" -Value $(Get-Date $([datetime]::FromFileTime($user.pwdLastSet)) -Format "yyyy.MM.dd HH:mm:ss") # Закомментировано: дата последнего изменения пароля (при необходимости)
$user_info | Add-Member -MemberType NoteProperty -Name "Enabled" -Value $user.Enabled # Статус активности пользователя
$user_info | Add-Member -MemberType NoteProperty -Name "Password Never Expires" -Value $user.PasswordNeverExpires # Установка флага "Пароль никогда не истекает"
$user_info | Add-Member -MemberType NoteProperty -Name "LockedOut" -Value $user.LockedOut # Статус блокировки пользователя
$user_info | Add-Member -MemberType NoteProperty -Name "UserPasswordExpiryTimeComputed" -Value $UserPasswordExpiryTimeComputed # Время истечения срока действия пароля

# Добавление объекта user_info в массив users_info
$users_info += $user_info
}
}

# Определение имени файла для экспорта данных о пользователях, используя текущую дату и время
$users_info_file = "$group_membership_folder\$(Get-Date -Format "yyyy_MM_dd_HH_mm_ss").csv"

# Запись в лог о попытке экспорта данных в указанный файл
"$(Date) Trying to export data to `"$users_info_file`" file" | Tee-Object $log -Append

try {
# Экспорт данных о пользователях в CSV-файл, отключая информацию о типах и устанавливая кодировку Unicode
$users_info | Export-Csv $users_info_file -NoTypeInformation -Encoding Unicode -Force -ErrorAction Stop
}
catch {
# Запись в лог сообщения об ошибке, если экспорт не удался
"$(Date) $($_.exception.message)" | Tee-Object $log -Append
}

# Запись в лог о начале сравнения последних двух файлов из указанной папки
"$(Date) Trying to compare latest 2 files from `"$group_membership_folder`" folder" | Tee-Object $log -Append

# Получение имени последнего измененного файла в указанной папке
$file_last = (Get-ChildItem $group_membership_folder -File | sort LastWriteTime -Descending)[0].FullName

# Получение имени предпоследнего измененного файла в указанной папке
$file_prev = (Get-ChildItem $group_membership_folder -File | sort LastWriteTime -Descending)[1].FullName

# Запись в лог имен последних и предпоследних файлов
"$(Date) Latest file: `"$file_last`"" | Tee-Object $log -Append
"$(Date) Previous file: `"$file_prev`"" | Tee-Object $log -Append

# Импорт содержимого последних и предпоследних файлов CSV в переменные
$file_last_content = Import-Csv $file_last
$file_prev_content = Import-Csv $file_prev

# Инициализация переменных для хранения списков имен пользователей из предыдущего и последнего файлов
$members_prev = $members_last = $null

# Извлечение списка имен пользователей (SamAccountName) из предыдущего файла
$members_prev = $file_prev_content.SamAccountName

# Извлечение списка имен пользователей (SamAccountName) из последнего файла
$members_last = $file_last_content.SamAccountName

# Инициализация пустого массива для хранения изменений в членстве
$membership_changes = @()

# Сравнение списков пользователей из двух файлов и получение списка пользователей, у которых нет изменений
$members_equal = Compare-Object -ReferenceObject $members_prev -DifferenceObject $members_last -IncludeEqual | ? {$_.SideIndicator -eq "=="}

# Перебор всех имен пользователей из последнего файла
foreach ($username in $file_last_content.SamAccountName)
{
# Получение данных о пользователе из последнего файла по имени пользователя
$userdata_last = $file_last_content | ? {$_.SamAccountName -eq $username}

# Получение данных о пользователе из предыдущего файла по имени пользователя
$userdata_prev = $file_prev_content | ? {$_.SamAccountName -eq $username}

# Проверка, изменилось ли состояние свойства "Enabled" (включен/выключен) для пользователя
if ($userdata_prev."Enabled" -ne $userdata_last."Enabled")
{
# Добавление информации об изменении свойства "Enabled" в массив изменений
$membership_changes += "User $($userdata_last.SamAccountName) / `"$($userdata_last.Username)`" property `"Enabled`" value changed from `"$($userdata_prev."Enabled")`" to `"$($userdata_last."Enabled")`""
}

# Проверка, изменилось ли состояние свойства "Password Never Expires" (пароль никогда не истекает) для пользователя
if ($userdata_prev."Password Never Expires" -ne $userdata_last."Password Never Expires")
{
# Добавление информации об изменении свойства "Password Never Expires" в массив изменений
$membership_changes += "User $($userdata_last.SamAccountName) / `"$($userdata_last.Username)`" property `"Password Never Expires`" value changed from `"$($userdata_prev."Password Never Expires")`" to `"$($userdata_last."Password Never Expires")`""
}

# Проверка, изменилось ли состояние свойства "LockedOut" (заблокирован) для пользователя
if ($userdata_prev."LockedOut" -ne $userdata_last."LockedOut")
{
# Добавление информации об изменении состояния "LockedOut" в массив изменений
$membership_changes += "User $($userdata_last.SamAccountName) / `"$($userdata_last.Username)`" property `"LockedOut`" value changed from `"$($userdata_prev."LockedOut")`" to `"$($userdata_last."LockedOut")`""
}

# Проверка, истек ли срок действия пароля пользователя
if ($(Get-Date $userdata_last."UserPasswordExpiryTimeComputed") -le $(Get-Date))
{
# Добавление информации о том, что пароль пользователя истек в массив изменений
$membership_changes += "User $($userdata_last.SamAccountName) / `"$($userdata_last.Username)`" password is expired on `"$($userdata_last."UserPasswordExpiryTimeComputed")`""
}
}

# Записываем изменения в массиве $membership_changes в лог-файл, добавляя новые записи
$membership_changes | Tee-Object $log -Append

# Инициализация переменной для подсчета количества изменений
$diff_count = 0
# Подсчет количества изменений в массиве $membership_changes
$diff_count = ($membership_changes | Measure-Object).Count

# Проверка, есть ли изменения
if ($diff_count -eq 0)
{
# Если изменений нет, записываем сообщение в лог и указываем, что все в порядке
"$(Date) All good. Nothing to do" | Tee-Object $log -Append
}
else
{
# Если изменения найдены, записываем количество изменений в лог
"$(Date) $diff_count new change(s) found." | Tee-Object $log -Append

### Настройка параметров электронной почты для уведомления об изменениях
$from = "pyatilistnik_notify@pyatilistnik.org" # Адрес отправителя
$to = "barboskin.g@pyatilistnik.org" # Адрес получателя
$cc1 = "sem@pyatilistnik.org" # Адрес первого получателя в копии
# $cc2 = "barboskina.r@pyatilistnik.org" # Закомментированный адрес второго получателя в копии

$Subject = "Check VIP Users issues" # Тема письма
$smtpserver = "mail.pyatilistnik.org" # SMTP сервер для отправки почты
$secure = $true # Использовать защищенное соединение
$port = 587 # Порт для TLS (для SSL использовать 465)
$username = "pyatilistnik_notify" # Имя пользователя для SMTP аутентификации
$password = 'Пароль от учетной записи e-mail' # Пароль для SMTP аутентификации (необходимо обеспечить безопасность)

# HTML-стили для форматирования таблицы в теле письма
$Header = @"
<style>
TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@

# Инициализация тела письма
$body = $null
$body += "<p>Please resolve next $diff_count issue(s):<br>"

$body += "<ul>" # Начало ненумерованного списка

# Перебор всех изменений и добавление их в тело письма в виде списка
foreach ($change in $membership_changes)
{
$body += "<li>$change</li>" # Добавление каждого изменения как элемента списка
}
$body += "</ul>" # Закрытие ненумерованного списка

# Добавление информации о компьютере, на котором было сгенерировано сообщение
$body += "<p>Generated on $($env:COMPUTERNAME + "." + $((Get-WmiObject Win32_ComputerSystem).Domain))"

# Создание нового объекта MailMessage для формирования письма
$message = New-Object System.Net.Mail.MailMessage
$message.From = $from # Установка адреса отправителя
$message.To.Add($to) # Добавление адреса получателя
$message.CC.Add($cc1) # Добавление адреса первого получателя в копии
# $message.CC.Add($cc2) # Закомментированное добавление второго адреса получателя в копии
$message.Subject = $Subject # Установка темы письма
$message.Body = $body # Установка тела письма
$message.IsBodyHtml = $true # Указание, что тело письма содержит HTML

# Создание SMTP-клиента для отправки почты
$smtp = New-Object Net.Mail.SmtpClient($smtpserver, $port)
$smtp.EnableSsl = $secure # Включение SSL для защищенного соединения
$smtp.Credentials = New-Object System.Net.NetworkCredential($username, $password) # Установка учетных данных

# Запись сообщения в лог о попытке отправки уведомления по электронной почте
"$(Date) Trying to send e-mail notification" | Tee-Object $log -Append

# Попытка отправить электронное сообщение с помощью SMTP
try {
$smtp.Send($message) # Отправка сообщения через настроенный SMTP-клиент
}
catch {
# Если произошла ошибка при отправке, записываем сообщение об ошибке в лог
"$(Date) $($_.exception.message)" | Tee-Object $log -Append
}

# Запись в лог о попытке отправить уведомление в Telegram
"$(Date) Trying to send Telegram notification" | Tee-Object $log -Append

# Попытка отправить уведомление в Telegram
try {
# Установка токена бота Telegram (необходимо обеспечить безопасность)
$token = "668697434:A0000000000-Hgeq345345345ygug"
# Установка идентификатора чата, куда будет отправлено сообщение
$chat_id = "-45555555415"

# Формирование текста сообщения с количеством изменений
$text = "Found *$diff_count* change(s) in *VIP Users* AD groups:`n"

# Перебор всех изменений и добавление их в текст сообщения
foreach ($change in $membership_changes) {
$text += "$change`n" # Добавление каждого изменения в текст сообщения
}

# Создание полезной нагрузки для API Telegram
$payload = @{
"chat_id" = $chat_id; # Идентификатор чата
"text" = $text; # Текст сообщения
"parse_mode" = "Markdown"; # Указание формата текста (Markdown)
}

# Установка протокола безопасности для HTTPS-запросов
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Отправка POST-запроса к API Telegram для отправки сообщения
Invoke-WebRequest -Uri ("https://api.telegram.org/bot{0}/sendMessage" -f $token) -Method Post -ContentType "application/json;charset=utf-8" -Body (ConvertTo-Json -Compress -InputObject $payload) -ErrorAction Stop # Остановка выполнения при ошибке запроса
}
catch {
# Если произошла ошибка при отправке сообщения в Telegram, записываем сообщение об ошибке в лог
"$(Date) $($_.exception.message)" | Tee-Object $log -Append
}

# Запись в лог о завершении обработки скрипта
"$(Date) End Processing" | Tee-Object $log -Append

Далее вам остается сохранить данный код в виде скрипта PowerShell и запускать его с помощью планировщика Windows по нужному вам расписанию. Думаю, что приемлимо производить запуск скрипта раз в 60 минут.

После того, как один из параметров атрибута будет не в штатном состоянии вы получите приблизительно вот такие сообщения.

Сообщение в телеграм по VIP пользователям

или на почту

Check VIP Users issues

Надеюсь, что вам пригодится это скрипт мониторинга. С вами был Иван Сёмин, автор и создатель IT портала Pyatilistnik.org.

Оцените статью
Настройка серверов windows и linux
Добавить комментарий