Еще с выходом в свет Windows Vista\2008 администраторы столкнулись с маленькой, но неприятной проблемой: оповещение об истечении срока действия пароля стало сиротливо появляться в самом неприметном углу экрана. И это вместо окна прямо по центру, как было раньше!
Отсюда и смена паролей в последний момент, под аккомпанемент отказов доступа; и негодование, почему вдруг перестал работать VPN, и что с этим делать в командировке. Конечно, не проблема года, но явление назойливое и неприятное. Поэтому разбираемся, как его одолеть.
Отчеты и Excel на страже памяти
В статье «Excel вместо PowerShell: запросы к AD и системные отчеты» я рассказывал, как вытащить информацию из Active Directory при помощи Excel. Благодаря этому механизму всегда можно получить отчет о сроках действия пользовательских паролей. В современных доменах для этого существует атрибут msDS-UserPasswordExpiryTimeComputed, который находится в классе user.
При помощи Power Query мы можем легко получить подобную табличку:
Отчет о сроках действия пароля в MS Excel.
Теперь, если добавить формулу вида
=ЕСЛИ([@[msDS-UserPasswordExpiryTimeComputed]]<ТДАТА();"Пароль просрочен!";ЕСЛИ([@[msDS-UserPasswordExpiryTimeComputed]]-5<ТДАТА();"Пароль скоро закончится!";"ОК"))
в соседний столбец, то мы получим таблицу уже такого вида:
Делаем отчет более наглядным.
Осталось только добавить правила раскрашивания ячеек на основе их содержимого, и будет совсем красиво:
И еще более наглядным.
При настройке автоматического обновления данных можно смотреть в таблицу и готовиться к заявкам от пользователей. Или поручить аккаунт-менеджеру (да, где-то есть такие специальные люди) обзванивать сотрудников.
Попробуем использовать в качестве аккаунт-менеджера PowerShell.
Автоматические уведомления сотрудников
По счастью, получать данные из Active Directory можно не только через Excel, но и при помощи любимых скриптовых языков вроде PowerShell.
Для начала получим список пользователей и их адресов таким запросом:
$users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0} `
-Properties "Name", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" | ` Select-Object -Property "Name", "EmailAddress", `
@{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }}
Как видно, по этому запросу я получаю только тех пользователей, которые соответствуют трем критериям:
- Не заблокированные ― Enabled -eq $True.
- Те, у кого пароль имеет срок жизни ― PasswordNeverExpires -eq $False.
- Те, у кого вообще не установлен пароль ― PasswordLastSet -gt 0.
В результате мы получим таблицу значения с именем пользователя, его адресом и датой истечения пароля. Для удобства задаем три варианта времени предупреждения ― за 7, за 3 и за 1 день:
$SevenDayWarnDate = (get-date).adddays(7).ToLongDateString()
$ThreeDayWarnDate = (get-date).adddays(3).ToLongDateString()
$OneDayWarnDate = (get-date).adddays(1).ToLongDateString()
Теперь, сравнивая эти три переменные со значением $user.PasswordExpiry, мы сможем посылать соответствующие уведомления. Напомню, что отправка e-mail производится при помощи командлета Send-MailMessage.
Но не обязательно уведомлять пользователей исключительно по почте ― можно использовать материал «Еще не бот, но уже что-то ― получаем уведомления от Zabbix в мессенджеры» и отсылать уведомления по любому другому каналу связи.
С полным листингом скрипта, который можно запускать ежедневно при помощи планировщика, можно ознакомиться под спойлером.
Import-Module ActiveDirectory
#Создаем пороги срабатывания уведомлений
$SevenDayWarnDate = (get-date).adddays(7).ToLongDateString()
$ThreeDayWarnDate = (get-date).adddays(3).ToLongDateString()
$OneDayWarnDate = (get-date).adddays(1).ToLongDateString()
#Настройка текста сообщений
$MailSender = " Бот-Напоминалка <bot@domain.com>"
$Subject = 'Внимание! Срок действия Вашего пароля заканчивается'
$EmailStub1 = 'Я бот-напоминалка. Ваш пароль закончится'
$EmailStub2 = 'через'
$EmailStub3 = 'дней'
$EmailStub4 = '. Пожалуйста, заблаговременно измените свой пароль. Обратитесь в службу технической поддержки, если вы испытываете трудности со сменой пароля.'
$SMTPServer = 'smtp.domain.com'
#Получаем пользователей
$users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0 } `
-Properties "Name", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "Name", "EmailAddress", `
@{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }}
#Проверяем сроки действия и отправляем уведомления.
foreach ($user in $users) {
if ($user.PasswordExpiry -eq $SevenDayWarnDate) {
$days = 7
$EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $SevenDayWarnDate, $EmailStub4 -join ' '
Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody
}
elseif ($user.PasswordExpiry -eq $ThreeDayWarnDate) {
$days = 3
$EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $ThreeDayWarnDate, $EmailStub4 -join ' '
Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject `
-Body $EmailBody
}
elseif ($user.PasswordExpiry -eq $oneDayWarnDate) {
$days = 1
$EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $OneDayWarnDate, $EmailStub4 -join ' '
Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody
}
else {}
}
Теперь пользователи предупреждены, и остается надеяться на их ответственность. Рассмотрим еще один вариант уведомления.
Уведомляем пользователей при входе
Можно вернуть на экран сообщение об истекающем сроке пароля через логон-скрипт или программу в автозагрузке. К сожалению, по понятным причинам этот вариант не подойдет для пользователей, работающих с доменными сервисами удаленно.
Приведу пример простого скрипта на PowerShell, подсунутого в автозагрузку групповыми политиками:
$user= Get-ADUser -Identity $env:username -Properties 'msDS-UserPasswordExpiryTimeComputed','PasswordNeverExpires'
if ( -not $user.'PasswordNeverExpires') {
$diff=(new-timespan -start (get-date) -end ([datetime]::FromFileTime($user."msDS-UserPasswordExpiryTimeComputed"))).Days
if ($diff -lt 7) {
$msgBoxInput = [System.Windows.MessageBox]::Show("Ваш пароль истекает через "+ $diff + " дней!`nПерейти к диалогу смены пароля?","Внимание!","YesNo","Warning")
switch ($msgBoxInput) {
'Yes' {
cmd /c "explorer shell:::{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}"
}
'No' { }
}
}
}
Строка запуска explorer shell:::{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0} ― это запуск интерфейса «Безопасность Windows». После выхода Windows 2012 он исчез из меню «Пуск» и стал доступен только при нажатии Ctrl+Alt+Del или Ctrl+Alt+End в случае подключения по RDP.
Окно уведомления.
Для ценителей под спойлером старая версия скрипта.
#Include <AD.au3>
#include <Date.au3>
_AD_Open()
$array=_AD_GetPasswordInfo()
if $array[9] <> "" then
$t=_DateDiff ( "d", _NowCalc(), $array[9] )
if $t < 7 Then
$a=MsgBox ( 4, "Внимание!", "Срок действия вашего пароля истекает через " &$t &" дней."&@crlf&"Перейти к диалогу смены пароля?" )
if $a=6 then
Run("explorer shell:::{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}")
sleep(1000)
endif
endif
endif
_AD_Close()
Простой реализации помогает функция _AD_GetPasswordInfo() из библиотеки AD.au3. Она выдает массив значений с информацией о пароле, в том числе и временем его истечения.
После внедрения этого механизма какие-либо проблемы с просроченными паролями перестали появляться.
Отсутствие домена
Если инфраструктура Active Directory у вас не развернута ― например, в случае отдельного терминального сервера, ― то решение тоже существует, пусть и чуть более сложное.
Для этого нам понадобится чуть-чуть магии WMI и любимый скриптовый язык. Вернемся к примерам на PowerShell:
#Имя компьютера.
$Computer = $env:computername
#Имя пользователя
$UserName = $env:username
#Дельта в днях
$Days = 2
$User = [ADSI]"WinNT://$Computer/$UserName,user"
$Flags = $User.UserFlags.psbase.Value
#Проверка существования срока жизни пароля.
If ($Flags -band 65536)
{
"Пароль никогда не имеет срока действия"
}
Else
{
#Конвертируем время в дни.
$AgeDays = $User.PasswordAge.psbase.Value / 86400
$MaxAge = $User.MaxPasswordAge.psbase.Value / 86400
If ($AgeDays -gt $MaxAge)
{
"Пароль просрочен"
}
Else
{
If (($AgeDays + $Days) -gt $MaxAge)
{
"Пароль будет просрочен через $Days дней"
}
Else
{
"Все в порядке"
}
}
}
При должной доработке такой скрипт может уведомлять пользователей при регулярном запуске или при входе в систему.
Уже давно я задумывался о смысле существования срока жизни пароля. С одной стороны, это хорошо и безопасно, с другой ― постоянное придумывание новых паролей приводит к забыванию этих самых паролей и, как следствие, к стикерам на мониторе и под клавиатурой.
Поэтому все больше мне близка практика, когда пользователи сами придумывают пароли, но при этом регулярно проводится аудит их творчества путем атаки по словарю утилитами вроде L0phtCrack. Обязательным остается только требование к длине паролей (вспоминая известную картинку от xkcd).
Так можно и людям жизнь облегчить, и избежать защитной реакции на «закручивание гаек» в виде чудесных сочетаний вроде Qwerty123. Ну, а что ― система не ругается, а ИБ эта ваша… понапридумывали тут.
Кстати, а у вас есть аккаунт-менеджер или какая-нибудь модно-молодежная практика смены паролей?