Active Directoryでの自動パスワード管理

このすべてにうんざりしたら...

おそらく、ほとんどの場合、システム管理者の仕事が始まるのはこのフレーズです。 その結果、1つの大きなシステムで厳密かつ厳密に定義されたタスクを実行する多くの小さなプログラムの外観がわかります(より正確には気づきません)。



私と同じような話が起こりました(そして定期的に起こります)。 私は、新しくて傑出した何かを発明したとは言いません。 まったく逆です-私は、インターネットやHabrの知恵の宝物に見られる同僚の仕事を利用しました。 しかし、それらを組み合わせて、非常に具体的で非常に興味深いタスクを解決することができました。 次に、Active Directoryでユーザーパスワードを管理するための特定のタスクに対する特定のソリューションについて説明します。 より正確には、これらのパスワードの有効期間をチェックし、新しいパスワードを生成する自動化。 同僚に感謝し、誰かに役立つか、新しいアイデアの源として役立つことを期待して、ここにこの決定を公開する必要があると考えました。



したがって、強力で広範なブランチネットワークを持つ特定の組織があります。 広大な祖国には多くの支部があり、それらはすべて多様です。 それらのほとんどはドメイン構造を持つ企業ネットワークに含まれていますが、多くはホームオフィスの原則に従って接続されています。 さらに、多くの従業員は、ドメインネットワークや一般的なインターネットに接続することができず、常に長期の出張を行っています。



その結果、パスワードの有効期限が切れるという問題がしばしば発生します。 会社のポリシーでは、永続的なパスワードの禁止が定義されており、パスワードの重大度の要件は非常に厳しいため、ユーザーがパスワードを発明および置換することは困難です。 したがって、彼らは喜んでITサポートに移行し、すでに非アクティブなパスワードを変更するよう要求し、要求するだけで頭痛の種をなくしています。 定期的に。 疲れた。



それで、私は何をしたかったのですか? 次のツールが必要です。

•ユーザーパスワードの有効期限を自身で確認しました。

•以前に、パスワードが電子メールで変更された日付について彼に警告しました。

•ユーザーに新しいパスワードオプションを提供しました。

•ユーザーがパスワードを変更することができなかった場合、自動的に新しいものに置き換えます。

•SMSを介してユーザーに新しいパスワードを通知しました。



関心は、サードパーティのサービスを使用せずに、最も即興的な手段でこの問題を解決することでした。 まあ、関税とパッケージを選択する欲求はありませんでした。 しかし、無料のGSMモデムがありました。 そして全能のPowerShell。



結果はスクリプト、またはむしろ2つのスクリプトです。 なぜそう説明されるのか-それは歴史的に起こりました。 実際のところ、パスワードの検証は、ある支店にある仮想マシン上のスクリプトによって実行され、国の反対側にある別のマシンがSMS通知の送信に従事しているということです。 携帯電話会社の状況により、それ以外のことを行うことは採算が取れませんでした。



次に、両方のスクリプトを完全に持ち込み、できる限りコメントしました。 彼らは少し巻き毛に見えます。 うまく機能し、この形式で機能するため、それらをとかす必要は特にありませんでした。



#    ,     , #      email, #   ,     . # #   . $dt=Get-Date -Format "dd-MM-yyyy" $setupFolder = "c:\Active_Directory\Log" New-Item -ItemType directory -Path $setupFolder -Force | out-null #    $global:logfilename="C:\Active_Directory\Log\"+$dt+"_LOG.log" [int]$global:errorcount=0 #   [int]$global:warningcount=0 #   function global:Write-log #     -    . {param($message,[string]$type="info",[string]$logfile=$global:logfilename,[switch]$silent) $dt=Get-Date -Format "dd.MM.yyyy HH:mm:ss" $msg=$dt + "`t" + $type + "`t" + $message #: 01.01.2001 01:01:01 [tab] error [tab]  Out-File -FilePath $logfile -InputObject $msg -Append -encoding unicode if (-not $silent.IsPresent) { switch ( $type.toLower() ) { "error" { $global:errorcount++ write-host $msg -ForegroundColor red } "warning" { $global:warningcount++ write-host $msg -ForegroundColor yellow } "completed" { write-host $msg -ForegroundColor green } "info" { write-host $msg } default { write-host $msg } } } } #    function global:Get-RandomPassword { <#    PasswordLength -   #> [CmdletBinding()] param( [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)] [ValidateRange(4,15)] [Int] $PasswordLength ) Begin{} Process{ $numberchars=0..9 | % {$_.ToString()} $lochars = [char]'a' .. [char]'z' | % {[char]$_} $hichars = [char]'A' .. [char]'Z' | % {[char]$_} $punctchars = [char[]](33..47) $PasswordArray = Get-Random -InputObject @($hichars + $lochars + $numberchars + $punctchars) -Count $PasswordLength $char1 = Get-Random -InputObject $hichars $char2 = Get-Random -InputObject $lochars $char3 = Get-Random -InputObject $numberchars $char4 = Get-Random -InputObject $punctchars $RndIndexArray = Get-Random (0..($PasswordLength-1)) -Count 4 $PasswordArray[$RndIndexArray[0]] = $char1 $PasswordArray[$RndIndexArray[1]] = $char2 $PasswordArray[$RndIndexArray[2]] = $char3 $PasswordArray[$RndIndexArray[3]] = $char4 return [system.string]::Join('', $PasswordArray) } End{} } #SMTP    $smtpServer = "mail.domain.local" #   $msg = new-object Net.Mail.MailMessage $msgr = new-object Net.Mail.MailMessage #    $smtp = new-object Net.Mail.SmtpClient($smtpServer) #     Function EmailStructure($to,$expiryDate,$upn) { $msg.IsBodyHtml = $true $msg.From = "ITHelpDesk@domain.local" $msg.To.Clear() $msg.To.Add($to) $msg.Subject = "Password expiration notice" $msg.Body = "<html><body><font face='Arial'>This is an automatically generated message from Company IT Service.<br><br> <b>Please note that the password for your account <i><u>domain\$upn</u></i> will expire on $expiryDate.</b><br><br> System automatically generated a new password for you. <br> You can use password - <b>$generated_password</b><br> Please change your password immediately or at least before this date as you will be unable to access the service without contacting your administrator.<br> If you will not change your password, System set it automatically.<br> </font></body></html>"} #     Function EmailStructureReport($to) { $msgr.IsBodyHtml = $true $msgr.From = "PasswordChecker@domain.local" $msgr.To.Add($to) $msgr.Subject = "Script running report" $msgr.Body = "<html><body><font face='Arial'><b>This is a daily report.<br> <br>Script for check expiried passwords has successfully completed its work. <br>$NotificationCounter users have recieved notifications:<br> <br>$ListOfAccounts<br><br></b></font></body></html>"} #      Active Directory Import-Module activedirectory #      ,       $NotificationCounter = 0 $OU = "OU=Russia,DC=local,DC=domain" $ADAccounts = Get-ADUser -LDAPFilter "(objectClass=user)" -searchbase $OU -properties PasswordExpired, employeeNumber, PasswordNeverExpires, PasswordLastSet, Mail, mobile, Enabled | Where-object {$_.Enabled -eq $true -and $_.PasswordNeverExpires -eq $false} #    foreach ($ADAccount in $ADAccounts) #    { $accountFGPP = Get-ADUserResultantPasswordPolicy $ADAccount if ($accountFGPP -ne $null) { $maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge } else { $maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge } #    $samAccountName = $ADAccount.samAccountName $userEmailAddress = $ADAccount.mail $userPrincipalName = $ADAccount.UserPrincipalName $userStorePassword = $ADAccount.employeeNumber $usermobile = $ADAccount.mobile #    ,     if ($ADAccount.PasswordExpired) { #      AD #     ,     - Pa$$w0rd if ($userStorePassword -eq $NULL -or $useStorePassword -eq " ") { $userStorePassword = "Pa$$w0rd" } #     $newpwd = ConvertTo-SecureString -String $userStorePassword -AsPlainTextForce Set-ADAccountPassword -Identity $samAccountName -NewPassword $newpwd –Reset #         TXT  if ($usermobile -ne $NULL) { $SMSfile="C:\ActiveDirectory\SMS_notice.txt" $SMSMessage=$usermobile + "," + $userStorePassword Out-File -FilePath $SMSfile -InputObject $SMSMessage -Append -encoding unicode } #     write-log "for $samAccountName will set a stored password - $userStorePassword. Message send to mobile - $usermobile" write-log "---------------------------------------------------------------------------------------------------------" #    AD Set-ADUser $samAccountName -employeeNumber $null } else #   ,     ,   $DaysToExpireDD  2 { $ExpiryDate = $ADAccount.PasswordLastSet + $maxPasswordAgeTimeSpan $TodaysDate = Get-Date $DaysToExpire = $ExpiryDate - $TodaysDate #     DaysToExpireDD    $DaysToExpireDD = $DaysToExpire.ToString() -Split ("\S{17}$") if (($DaysToExpire.Days -le 2)) { Write-log "The password for account $samAccountName expires on: $ExpiryDate. Days left: $DaysToExpireDD #      $generated_password $generated_password = Get-RandomPassword 10 write-log "Generated password: $samAccountName - $generated_password" write-log "-----------------------------------------------------------------------------------------" #      e AD.    employeeNumber Set-ADUser $samAccountName -employeeNumber $generated_password #      if ($userEmailAddress) #      . { EmailStructure $userEmailAddress $expiryDate $samAccountName $smtp.Send($msg) write-log "NOTIFICATION - $samAccountName :: e-mail was sent to $userEmailAddress" $NotificationCounter = $NotificationCounter + 1 $ListOfAccounts = $ListOfAccounts + $samAccountName + " - $DaysToExpireDD days left. Sent to $userEmailAddress<br>" } } } } #     ,    SMS #    If (Test-Path $SMSfile) { Copy-Item -Path $SMSfile -Destination \\SMS-Send-Server.domain.local\C$\ActiveDirectory\SMS_notice.txt #       Remove-Item $SMSfile } #     Write-log "SENDING REPORT TO IT DEPARTMENT" EmailStructureReport("ITHelpdesk@domain.local") $smtp.Send($msgr)
      
      





このスクリプトをWindowsタスクスケジューラに追加し、適切なタイミングで実行されるように設定します。 たとえば、夜。



残念ながら、スクリプトは実行時に期限切れのパスワードをチェックします。 そのため、午後にパスワードの有効期限が切れた場合、それは考慮されません。 ただし、勤務時間中に従業員が自分でパスワードを変更できるため、必要ありません。



その結果、新しいパスワードを持つユーザーの携帯電話番号のリストを取得します。 このリストを、GSMモデムが接続されているサーバーに送信します。 そして、次のスクリプトがこのリストを処理します。



 # #            # # ,      $sms_text_filename = "SMS_notice.txt" $PathToSmsPrepareToSend = "C:\ActiveDirectory" + "\" + $sms_text_filename $dt=Get-Date -Format "dd.MM.yyyy" # ,       $of="C:\ActiveDirectory\Log\"+$dt+"_LOG.log" #     If (Test-Path $PathToSmsPrepareToSend) { $SMS = Import-Csv $PathToSmsPrepareToSend -Header mobile, newpassword #       foreach ($SM in $SMS) { # $mobileForSMS = $SM.mobile # $passwordFroSMS = $SM.newpassword # echo $mobileForSMS #    SerialPort $serialPort = new-Object System.IO.Ports.SerialPort #    ,     <# !!!!!! USB-   COM .   ,        .   GSM-   USB ,   COM  . #> $serialPort.PortName = "COM3" $serialPort.BaudRate = 115200 $serialPort.WriteTimeout = 500 $serialPort.ReadTimeout = 3000 $serialPort.DtrEnable = "true" #   # $serialPort.Open() #        #       $phoneNumber = [Regex]::replace($SM.mobile,'\s','') $textMessage = "Your new password - " + $SM.newpassword try { $serialPort.Open() } catch { #  5     Sleep -Milliseconds 500 $serialPort.Open() } If ($serialPort.IsOpen -eq $true) { #  ,     AT- $serialPort.Write("AT+CMGF=1`r`n") Sleep -Milliseconds 500 #     #       #   <CL>   $serialPort.Write("AT+CMGS=`"$phoneNumber`"`r`n") #      Sleep -Milliseconds 500 #      $serialPort.Write("$textMessage`r`n") Sleep -Milliseconds 500 #    Ctrl+Z    . $serialPort.Write($([char] 26)) # ,     Sleep -Milliseconds 500 } #   $serialPort.Close() if ($serialPort.IsOpen -eq $false) { #     $dts=Get-Date -Format "dd.MM.yyyy HH:mm:ss" $msg=$dts+" :Message "+$textMessage+" send to "+ $phoneNumber Out-File -FilePath $of -InputObject $msg -Append -encoding unicode } Sleep -Milliseconds 1000 } #      #         $newname =$dt+"_"+$sms_text_filename rename-item -path $PathToSmsPrepareToSend -newname $newname } #    Else #      { #    ,       $dts=Get-Date -Format "dd.MM.yyyy HH:mm:ss" $msg=$dts + " :No data to send SMS" Out-File -FilePath $of -InputObject $msg -Append -encoding unicode }
      
      





スクリプトは戦闘条件でテストされ、最良の側面を示しました。



タスクが非常に具体的だったので、なぜ私はちょうどそれをしたのか説明しません。 そして、解決策は非常に具体的でした。



しかし、スクリプトの改善または最適化に関するアドバイスは喜んで受けます。



UPD:

興味深いエラーが出ました。

テキストファイルからのパスワードのインポートはImport-Csv関数に基づいています。この関数は、コンマをフィールドセパレーターとして正しく認識します。

また、パスワードジェネレーターは、特殊文字の1つとしてコンマを非常に積極的に使用します。

その結果、パスワードは予想どおり通常の長さに設定されましたが、SMSではユーザーは「切り捨てられた」パスワードを受け取りました。

解決策は簡単です。コンマを使用できないため、アスタリスクを使用します(句読点よりも愛されています)

パスワードを生成した直後に行を追加します。

 $generated_password = $generated_password_comma -replace ",","*"
      
      







事前に考え抜かれていない不幸な些細なこと( mea culpa )、およびユーザーの信頼は非常に有害でした。



All Articles