PowerShell: Crear grupos de seguridad para cada servidor, remover administradores locales y agregarlos al nuevo grupo.

Hace tiempo me solicitaron buscar un método para realizar ciertas medidas de seguridad y control con los usuarios que son administradores locales en cada uno de los servidores, dichas medidas requerian lo siquiente:

1.- Crear un grupo de seguridad en AD para cada servidor.
2.- Agregar el grupo creado al servidor correspondiente como administrador local.
3.- Buscar todos los administradores locales del servidor y agregarlos al nuevo grupo de AD.
4.- Ya que los usuarios estuvieran agregados entonces elminiarlos del grupo de administradores.
5.- Guardar en un log los usuarios que que son administradores en cada servidor.
6.- Enviar un correo.

La idea de todo esto es poder tener el control y saber que usuarios son administradores locales en los servidores, estamos hablando de más de 500 servidores y del cual hacer la administración uno por uno podría ser una labor bastante complicada.

Análisando la problematica la mejor opción fue realizar un script en PowerShell y que éste se ejectute diariamente con una tarea programada.
En este post mostraré paso a paso como realizar esta tarea, este script es digamos que es “una versión mejorada” del script original.

Este script cuenta con 6 funciones que explicaré en cada uno de los bloques.

Get-ADGroupName, esta función solo me sirve para obtener el nombre del grupo de AD del servidor seleccionado, la estandarización para el grupo es la siguiente: “IT – Local Administrator – SERVERNAME“,
La función esta recibiendo la variable $ServerName que es la que usaré en cada uno de las funciones restantes, al final retornará el nombre correcto del grupo si este existe en AD.

Function Get-ADGroupName() {
    param($ServerName)
    $ADGroup = 'IT - Local Administrator - ' + $ServerName
    Get-ADGroup -Filter { name -eq $ADGroup } | ForEach-Object { $_.Name }
}

Set-ADGroupName, esta función creará el nombre del grupo en AD en caso de que no exista, se agregra una descripción del grupo, el nombre de acuerdo al estándar que manejamos y algo que se debe de tomar muy en cuenta es el scope del grupo, en AD existen tres scopes; Domain Local, Global y Universal, para este grupo debemos de utilizar el scope universal, por qué?… Bueno, digamos que dentro de nuestros servidores tenemos varios grupos de seguridad como adminitradores locales y los grupos tienen un scope universal, si creamos nuestros nuevos grupos con scope global y queremos agregarle grupos con scope universal, simplemente no vamos a poder. El scope universal adminte los otros tipos de scope y es algo que se debe de tomar en cuenta, si queremos evitar problemas creen el scope universal como se muestra en el bloque de abajo y agregarlo al path correspondiente.

Function Set-ADGroupName() {
    param($ServerName)
    $DescriptionGroup = 'Members of this group are Administrators of ' + $ServerName 
    $ADGroupName = 'IT - Local Administrator - ' + $ServerName            
    New-ADGroup -Name $ADGroupName -GroupCategory Security -GroupScope Universal -DisplayName $ADGroupName -Path "OU=IT - Local Administrators - Servers Groups,OU=Restricted,OU=MXLITPRO_Groups,DC=MXLITPRO,DC=TK" -Description $DescriptionGroup      
    Write-Host "Created the AD Group $ADGroupName" -BackgroundColor Black -ForegroundColor Green
}

Add-ADGroupName-To-Server, esta función agregará el grupo de AD correspondiente a cada servidor, existe una condición IF, y es que al ejecutar el script éste intentará conectarse remotamente a cada uno de los servidores, en caso de que intente conectarse remotamente al servidor donde se está ejecutando creará un error, por lo que si se cumple esta condición if ($ServerName -eq $env:computername) entonces solo agregará el grupo de AD al grupo de administradores locales, como notan la función solo recibe la variable del nombre del servidor y dentro del bloque estoy mandando llamar la función para obtener el nombre del grupo de AD, en caso de invocar el comando en un servidor remoto, le estoy enviando directamente el nombre del grupo de AD.

Function Add-ADGroupName-To-Server() {
    param($ServerName)
    if ($ServerName -eq $env:computername) {
        Add-LocalGroupMember -Group Administrators -Member (Get-ADGroupName -ServerName $ServerName) -ErrorAction SilentlyContinue
    }
    else {
        Invoke-Command -ComputerName $ServerName -ScriptBlock { 
            param($ADGroup)
            Add-LocalGroupMember -Group Administrators -Member $ADGroup 
        } -ArgumentList (Get-ADGroupName -ServerName $ServerName) -ErrorAction SilentlyContinue
    }
}

Get-LocalUsers-From-Server , esta función obtendrá todos los usuarios o grupos que son administradores dentro del servidor, dado a que a mi solo me interesa obtener los usuarios que son pertenecientes a AD y no locales, agregué la condición Where-Object { $_.PrincipalSource -eq ‘ActiveDirectory’ }, en este bloque básicamente agrego la misma condición IF en caso de querer ejecutar este bloque en el mismo servidor de donde está corriento, además de eso cree un cumstom object que me servirá para formar mi archivo CSV donde podré agrupar el nombre del servidor junto al usuario o grupo que es administrador en cada uno de los servidores.

Function Get-LocalUsers-From-Server() {
    param($ServerName)
    if ($ServerName -eq $env:computername) {
           Get-LocalGroupMember administrators | Where-Object { $_.PrincipalSource -eq 'ActiveDirectory' } | ForEach-Object {
            New-Object psobject -Property @{
                Server     = $ServerName
                LocalAdmin = $_.Name
            } 
        }
    } 
    else {
        Invoke-Command -ComputerName $ServerName -ScriptBlock {   
            Get-LocalGroupMember administrators | Where-Object { $_.PrincipalSource -eq 'ActiveDirectory' } |  ForEach-Object {
                New-Object psobject -Property @{
                    Server     = $env:computername
                    LocalAdmin = $_.Name
                }
            }       
        } 
    }    
} 

Remove-LocalUsers-From-Server, esta función recibe el nombre del servidor y un usuario o grupo, básicamente removerá estos últimos del groupo de administradores locales, notese que igualmente incluyo la confición IF y que en el servidor remoto solamente le estoy pasando la variable del usuario.

Function Remove-LocalUsers-From-Server() {
    param($ServerName, $LocalAdmin)
    if ($ServerName -eq $env:computername) {
        Remove-LocalGroupMember -Group Administrators -Member $LocalAdmin
    } 
    else {
        Invoke-Command -ComputerName $ServerName -ScriptBlock {  
            param($LocalAdmin) 
            Remove-LocalGroupMember -Group Administrators -Member $LocalAdmin
        } -ArgumentList $LocalAdmin 
    }
}

Send-Mail, esta función se ejecutará al final del script y solamente mandará un correo indicando donde se encuentran los logs.

Function Send-Mail() {
    $smtpServer = "outlook.mxlitpro.tk"
    $smtpFrom = "[email protected]"
    $smtpTo = "[email protected]"
    $messageSubject = "Users in local admin group" 
    $message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
    $message.Subject = $messageSubject
    $message.IsBodyHTML = $true
    $message.Body =
    "<font face='Arial', size=3>
    <br>
    <br>Users in local admin group
    <br>
    <br>Logs are located in the following path:
    <br>\\HERE YOUR PATH
    <br>
    </font>"  
    $smtp = New-Object Net.Mail.SmtpClient($smtpServer)
    $smtp.Send($message)
    $smtp.Dispose();
}


Ahora, teniendo las funciones de nuestro escript, lo primero que debemos hacer es obtener todos los servidores, para ello buscaremos todos los servidores que el OS sea Windows dentro de nuestro OU seleccionado, además necesitaremos crear dos arrays vacios, uno nos servirá para obtener todos los usuarios o grupos que son administradores locales y otro para saber que servidores no estaban en línea.

Clear-Host
$UsersInLocalAdminGroup = @()
$UnavailableServers = @() 
$Servers = Get-ADComputer -Properties OperatingSystem -Filter * -SearchBase "OU=MXLITPRO_Servers,DC=MXLITPRO,DC=TK" | Where-Object { $_.OperatingSystem -like "*Windows*" }#Get all Servers that belong to the Server OU

Ahora trataré de explicar todo este bloque paso por paso, incluiré una imagen como guía.

1.- Aquí solo recorro todos los servidores que obtuve y guarde en mi variable.

2.- Test-WSMan, con esto checaré si el servidor es accesible, se puede sustituir por un ping y si el servidor responde entonces entrar en la condición del if, solo como dato, Windows Server tiene por defecto bloqueado el protocol ICMPv4, y puede que no sea la mejor opción de comprobar de que el servidor está activo salvo que sepan de que el firewall en todos los servidores está desactivado.

3.- Si se cumple la condición de arriba entrará a la parte del bloque, en caso de que no se agregará el nombre del servidor al array de servidores no disponibles.

4.- Obtendremos el nombre del grupo de acuerdo al nombre del servidor.

5.- En caso de que no obtengamos el nombre porque es un servidor nuevo y no existe el grupo, crearemos un nuevo grupo y despues obtendremos el nombre del grupo creado.

6.- Agregaremos el grupo como administrador local dentro del servidor.

7.- Obtendremos todos los administradores locales y los guardaremos en la variable $LocalAdmins.

8.- Agregaremos todo lo que contenga la variable $LocalAdmins al array de administradores locales.

9.- Recorremos cada uno de los administradores locales dentro del ciclo foreach.

10.- Aquí básicamente hago dos cosas, una de ellas es remover el nombre de NetBios y el siguiente es una condición IF, si embargo este paso es sumamente importante dado a que nuestra variable $LocalAdmins contiene usuarios y grupos de AD, será un hecho de que se incluirá el grupo de Domain Admins, en este punto dentro de la variable IF debemos indicar que solo será verdadera si el usuario o grupo es diferente a Domain Admins ya que sino indicamos eso, cuando el bloque de abajo se ejecute dejaremos nuestro servidor sin ningún usuario o grupo de AD como administrador.
Nota: En esta revisión me pasó esto, sin embargo cuento con un GPO para siempre mantener el grupo de Domain Admins dentro del grupo de administradores locales.
Dejo en enlace aquí

11.- En este paso se agregará el usuario o grupo de AD encontrado en la variable $LocalAdmin al nuevo grupo de AD.

12.- Aquí se removerá el usuario o grupo de AD en la variable $LocalAdmin del grupo de administradores locales dentro del servidor.

13.- Aquí solo se guardarán los logs en los paths indicados.
14.- Se ejecuta la función para mandar correo.

foreach ($Server in $Servers) {
    $Active = Test-WSMan -ComputerName $Server.Name -ErrorAction SilentlyContinue
    if ($Active) { #This will check if the server is accessible, a ping can work, but if it is blocked by the firewall then will skip this script block.
        $ADGroupName = (Get-ADGroupName -ServerName $Server.Name) #Get AD Group Name.
        Write-Host "The Server $Server is active." -ForegroundColor Green -BackgroundColor Black
        if (!$ADGroupName) {
            Set-ADGroupName -ServerName $Server.Name #Set AD Group Name.
            $ADGroupName = (Get-ADGroupName -ServerName $Server.Name) #Get AD Group Name.
        }        
        Add-ADGroupName-To-Server -ServerName $Server.Name #This will add the AD Group to the local administrator group in the target Server.
        $LocalAdmins = ( Get-LocalUsers-From-Server -ServerName $Server.Name) #This will get all users in the local administrator groups of the target server.                     
        $UsersInLocalAdminGroup += $LocalAdmins
        foreach ($LocalAdmin in $LocalAdmins) {        
            $LocalAdmin = ([String]$LocalAdmin.LocalAdmin).Replace("MXLITPRO\", "") #Remove NetBios Name.
            if ($LocalAdmin -ne 'Domain Admins' -and  #Exclude users or groups that you don't want remove.             
                $LocalAdmin -ne $ADGroupName) {                             
                Add-ADGroupMember -Identity $ADGroupName -Members $LocalAdmin #Add local Admin user to the AD Group.
                Remove-LocalUsers-From-Server -ServerName $Server.Name -LocalAdmin $LocalAdmin #Remove  local Admin user from the target server.
            }
        }
    }
    else {
        $UnavailableServers += $Server.Name
        Write-Host "Unable to connect to $Server `n$($_.Exception.Message)" -ForegroundColor Red -BackgroundColor Black
    }
}
$output = '\\YOUR PATH HERE\Log ' + (Get-Date).tostring("MM-dd-yyyy") + '.csv'   #Set the temp CSV file name that will be sent when the script finish
$UsersInLocalAdminGroup | Export-Csv $Output -NoTypeInformation
$output02 = '\\YOUR PATH HERE\Log_unavailable_servers ' + (Get-Date).tostring("MM-dd-yyyy") + '.csv'   #Set the temp CSV file name that will be sent when the script finish
$UnavailableServers | Export-Csv $Output02 -NoTypeInformation 
Send-Mail

Bueno, eso sería todo, aquí hemos aprendido como realizar multiples cosas con un solo script, esto como lo dije arriba, nos ayudará a mantener el control de quien es administrador, puede ser de mucha utilidad cuando se manejan cientos de servidores y se quiere tener un mejor control.

Leave a Reply

Your email address will not be published. Required fields are marked *