Servidor DHCP de Windows: obtenga una notificación cuando un dispositivo que no esté unido a AD obtenga una dirección IP

15

GUIÓN

Para simplificar esto a su ejemplo más fácil:

Tengo un DC estándar de Windows 2008 R2 con la función de servidor DHCP. Distribuye direcciones IP a través de varios ámbitos IPv4, no hay problema allí.

Como me gustaria

Me gustaría una forma de crear una notificación / entrada de registro de eventos / similar cada vez que un dispositivo obtiene una concesión de dirección DHCP y ese dispositivo NO ES una computadora unida al dominio en Active Directory. No me importa si es Powershell personalizado, etc.

En pocas palabras = Me gustaría saber cuándo los dispositivos que no son de dominio están en la red sin utilizar 802.1X en este momento. Sé que esto no tendrá en cuenta los dispositivos IP estáticos. Tengo un software de monitoreo que escaneará la red y encontrará dispositivos, pero no es tan detallado en detalle.

INVESTIGACIÓN REALIZADA / OPCIONES CONSIDERADAS

No veo ninguna posibilidad con el registro incorporado.

Sí, conozco 802.1X y tengo la capacidad de implementarlo a largo plazo en esta ubicación, pero estamos a cierta distancia de un proyecto como ese, y si bien eso resolvería problemas de autenticación de red, esto todavía me ayuda fuera de objetivos 802.1X.

He buscado algunas secuencias de comandos, etc. que pueden resultar útiles, pero las cosas que encuentro me llevan a creer que mi google-fu me está fallando en este momento.

Creo que la lógica a continuación es sólida ( suponiendo que no exista alguna solución existente ):

  1. El dispositivo recibe la dirección DHCP
  2. Se registra la entrada del registro de eventos (el ID de evento 10 en el registro de auditoría de DHCP debería funcionar (ya que un nuevo contrato de arrendamiento es lo que más me interesaría, no las renovaciones): http://technet.microsoft.com/en-us/library /dd759178.aspx )
  3. En este punto, una secuencia de comandos de algún tipo probablemente tendría que hacerse cargo de los "PASOS" restantes a continuación.
  4. De alguna manera, consulte este registro DHCP para estos ID de evento 10 (me encantaría presionar, pero supongo que el único recurso aquí es tirar)
  5. Analice la consulta del nombre del dispositivo al que se le asignó el nuevo contrato de arrendamiento
  6. Consulta AD para el nombre del dispositivo
  7. SI no se encuentra en AD, envíe un correo electrónico de notificación

Si alguien tiene alguna idea sobre cómo hacer esto correctamente, realmente lo agradecería. No estoy buscando un "dame el codez", pero me encantaría saber si hay alternativas a la lista anterior o si no estoy pensando claramente y existe otro método para recopilar esta información. Si tiene fragmentos de código / comandos PS que le gustaría compartir para ayudar a lograr esto, mucho mejor.

El limpiador
fuente
¿Está buscando bloquearlos o simplemente recibir una notificación si obtienen una IP?
HostBits
@Cheekaleak: solo recibe una notificación.
TheCleaner el
¿Qué pasa con las impresoras de red que usan DHCP?
jftuga
@jftuga: utilizamos IP estáticas para impresoras de red.
TheCleaner el

Respuestas:

6

Con muchas gracias a ErikE y los demás aquí, he seguido un camino ... No diré que es el camino correcto, pero el script Powershell que se me ocurrió hace el truco.

El código está debajo si alguien lo quiere. Simplemente ejecútelo manualmente apuntando a cada servidor DHCP o prográmelo (nuevamente apuntando a cada servidor DHCP en el script).

Lo que hace el guión:

  1. Obtiene información de arrendamiento del servidor DHCP (arrendamientos ipv4)
  2. Envía los arrendamientos a un archivo csv
  3. Lee de nuevo en ese archivo CSV para consultar AD
  4. Consultas AD para la computadora
  5. Si no se encuentran salidas a un nuevo archivo txt
  6. Crea un archivo txt final de lista única a partir del creado en el n. ° 5 anterior (ya que puede haber duplicados si el cliente se registra más de una vez o con más de un adaptador)
  7. envía por correo electrónico el contenido del archivo de salida final a un administrador

Lo que necesitarás:

El script usa el módulo AD ( import-module activedirectory), por lo que se ejecuta mejor en un AD DC que ejecuta DHCP. Si este no es su caso, puede instalar el módulo AD PowerShell: http://blogs.msdn.com/b/rkramesh/archive/2012/01/17/how-to-add-active-directory- módulo-en-powershell-en-windows-7.aspx

También necesitará los cmdlets AD Powershell de Quest que se encuentran aquí: http://www.quest.com/powershell/activeroles-server.aspx . Instale ESTOS ANTES de ejecutar el script o fallará.

El script en sí (desinfectado, deberá configurar algunas de las variables para satisfacer sus necesidades, como los nombres de los archivos de entrada, el dominio para conectarse, el servidor dhcp para conectarse, la configuración del correo electrónico cerca del final, etc.):

# Get-nonADclientsOnDHCP.ps1

# Author : TheCleaner http://serverfault.com/users/7861/thecleaner with a big thanks for a lot of the lease grab code to Assaf Miron on code.google.com

# Description : This Script grabs the current leases on a Windows DHCP server, outputs it to a csv
# then takes that csv file as input and determines if the lease is from a non-AD joined computer.  It then emails
# an administrator notification.  Set it up on a schedule of your choosing in Task Scheduler.
# This helps non-802.1X shops keep track of rogue DHCP clients that aren't part of the domain.

#

# Input : leaselog.csv

# Output: Lease log = leaselog.csv
# Output: Rogue Clients with dupes = RogueClients.txt
# Output: Rogue Clients - unique = RogueClientsFinal.txt

$DHCP_SERVER = "PUT YOUR SERVER NAME OR IP HERE" # The DHCP Server Name

$LOG_FOLDER = "C:\DHCP" # A Folder to save all the Logs

# Create Log File Paths

$LeaseLog = $LOG_FOLDER+"\LeaseLog.csv"

#region Create Scope Object

# Create a New Object

$Scope = New-Object psobject

# Add new members to the Object

$Scope | Add-Member noteproperty "Address" ""

$Scope | Add-Member noteproperty "Mask" ""

$Scope | Add-Member noteproperty "State" ""

$Scope | Add-Member noteproperty "Name" ""

$Scope | Add-Member noteproperty "LeaseDuration" ""

# Create Each Member in the Object as an Array

$Scope.Address = @()

$Scope.Mask = @()

$Scope.State = @()

$Scope.Name = @()

$Scope.LeaseDuration = @()

#endregion


#region Create Lease Object

# Create a New Object

$LeaseClients = New-Object psObject

# Add new members to the Object

$LeaseClients | Add-Member noteproperty "IP" ""

$LeaseClients | Add-Member noteproperty "Name" ""

$LeaseClients | Add-Member noteproperty "Mask" ""

$LeaseClients | Add-Member noteproperty "MAC" ""

$LeaseClients | Add-Member noteproperty "Expires" ""

$LeaseClients | Add-Member noteproperty "Type" ""

# Create Each Member in the Object as an Array

$LeaseClients.IP = @()

$LeaseClients.Name = @()

$LeaseClients.MAC = @()

$LeaseClients.Mask = @()

$LeaseClients.Expires = @()

$LeaseClients.Type = @()

#endregion


#region Create Reserved Object

# Create a New Object

$LeaseReserved = New-Object psObject

# Add new members to the Object

$LeaseReserved | Add-Member noteproperty "IP" ""

$LeaseReserved | Add-Member noteproperty "MAC" ""

# Create Each Member in the Object as an Array

$LeaseReserved.IP = @()

$LeaseReserved.MAC = @()

#endregion


#region Define Commands

#Commad to Connect to DHCP Server

$NetCommand = "netsh dhcp server \\$DHCP_SERVER"

#Command to get all Scope details on the Server

$ShowScopes = "$NetCommand show scope"

#endregion


function Get-LeaseType( $LeaseType )

{

# Input : The Lease type in one Char

# Output : The Lease type description

# Description : This function translates a Lease type Char to it's relevant Description


Switch($LeaseType){

"N" { return "None" }

"D" { return "DHCP" }

"B" { return "BOOTP" }

"U" { return "UNSPECIFIED" }

"R" { return "RESERVATION IP" }

}

}


function Check-Empty( $Object ){

# Input : An Object with values.

# Output : A Trimmed String of the Object or '-' if it's Null.

# Description : Check the object if its null or not and return it's value.

If($Object -eq $null)

{

return "-"

}

else

{

return $Object.ToString().Trim()

}

}


function out-CSV ( $LogFile, $Append = $false) {

# Input : An Object with values, Boolean value if to append the file or not, a File path to a Log File

# Output : Export of the object values to a CSV File

# Description : This Function Exports all the Values and Headers of an object to a CSV File.

#  The Object is recieved with the Input Const (Used with Pipelineing) or the $inputObject

Foreach ($item in $input){

# Get all the Object Properties

$Properties = $item.PsObject.get_properties()

# Create Empty Strings - Start Fresh

$Headers = ""

$Values = ""

# Go over each Property and get it's Name and value

$Properties | %{ 

$Headers += $_.Name + ","

$Values += $_.Value

}

# Output the Object Values and Headers to the Log file

If($Append -and (Test-Path $LogFile)) {

$Values | Out-File -Append -FilePath $LogFile -Encoding Unicode

}

else {

# Used to mark it as an Powershell Custum object - you can Import it later and use it

# "#TYPE System.Management.Automation.PSCustomObject" | Out-File -FilePath $LogFile

$Headers | Out-File -FilePath $LogFile -Encoding Unicode

$Values | Out-File -Append -FilePath $LogFile -Encoding Unicode

}

}

}


#region Get all Scopes in the Server 

# Run the Command in the Show Scopes var

$AllScopes = Invoke-Expression $ShowScopes

# Go over all the Results, start from index 5 and finish in last index -3

for($i=5;$i -lt $AllScopes.Length-3;$i++)

{

# Split the line and get the strings

$line = $AllScopes[$i].Split("-")

$Scope.Address += Check-Empty $line[0]

$Scope.Mask += Check-Empty $line[1]

$Scope.State += Check-Empty $line[2]

# Line 3 and 4 represent the Name and Comment of the Scope

# If the name is empty, try taking the comment

If (Check-Empty $line[3] -eq "-") {

$Scope.Name += Check-Empty $line[4]

}

else { $Scope.Name += Check-Empty $line[3] }

}

# Get all the Active Scopes IP Address

$ScopesIP = $Scope | Where { $_.State -eq "Active" } | Select Address

# Go over all the Adresses to collect Scope Client Lease Details

Foreach($ScopeAddress in $ScopesIP.Address){

# Define some Commands to run later - these commands need to be here because we use the ScopeAddress var that changes every loop

#Command to get all Lease Details from a specific Scope - when 1 is amitted the output includes the computer name

$ShowLeases = "$NetCommand scope "+$ScopeAddress+" show clients 1"

#Command to get all Reserved IP Details from a specific Scope

$ShowReserved = "$NetCommand scope "+$ScopeAddress+" show reservedip"

#Command to get all the Scopes Options (Including the Scope Lease Duration)

$ShowScopeDuration = "$NetCommand scope "+$ScopeAddress+" show option"

# Run the Commands and save the output in the accourding var

$AllLeases = Invoke-Expression $ShowLeases 

$AllReserved = Invoke-Expression $ShowReserved 

$AllOptions = Invoke-Expression $ShowScopeDuration

# Get the Lease Duration from Each Scope

for($i=0; $i -lt $AllOptions.count;$i++) 

{ 

# Find a Scope Option ID number 51 - this Option ID Represents  the Scope Lease Duration

if($AllOptions[$i] -match "OptionId : 51")

{ 

# Get the Lease Duration from the Specified line

$tmpLease = $AllOptions[$i+4].Split("=")[1].Trim()

# The Lease Duration is recieved in Ticks / 10000000

$tmpLease = [int]$tmpLease * 10000000; # Need to Convert to Int and Multiply by 10000000 to get Ticks

# Create a TimeSpan Object

$TimeSpan = New-Object -TypeName TimeSpan -ArgumentList $tmpLease

# Calculate the $tmpLease Ticks to Days and put it in the Scope Lease Duration

$Scope.LeaseDuration += $TimeSpan.TotalDays

# After you found one Exit the For

break;

} 

}

# Get all Client Leases from Each Scope

for($i=8;$i -lt $AllLeases.Length-4;$i++)

{

# Split the line and get the strings

$line = [regex]::split($AllLeases[$i],"\s{2,}")

# Check if you recieve all the lines that you need

$LeaseClients.IP += Check-Empty $line[0]

$LeaseClients.Mask += Check-Empty $line[1].ToString().replace("-","").Trim()

$LeaseClients.MAC += $line[2].ToString().substring($line[2].ToString().indexOf("-")+1,$line[2].toString().Length-1).Trim()

$LeaseClients.Expires += $(Check-Empty $line[3]).replace("-","").Trim()

$LeaseClients.Type += Get-LeaseType $(Check-Empty $line[4]).replace("-","").Trim()

$LeaseClients.Name += Check-Empty $line[5]

}

# Get all Client Lease Reservations from Each Scope

for($i=7;$i -lt $AllReserved.Length-5;$i++)

{

# Split the line and get the strings

$line = [regex]::split($AllReserved[$i],"\s{2,}")

$LeaseReserved.IP += Check-Empty $line[0]

$LeaseReserved.MAC += Check-Empty $line[2]

}

}

#endregion 


#region Create a Temp Scope Object

# Create a New Object

$tmpScope = New-Object psobject

# Add new members to the Object

$tmpScope | Add-Member noteproperty "Address" ""

$tmpScope | Add-Member noteproperty "Mask" ""

$tmpScope | Add-Member noteproperty "State" ""

$tmpScope | Add-Member noteproperty "Name" ""

$tmpScope | Add-Member noteproperty "LeaseDuration" ""

#endregion

#region Create a Temp Lease Object

# Create a New Object

$tmpLeaseClients = New-Object psObject

# Add new members to the Object

$tmpLeaseClients | Add-Member noteproperty "IP" ""

$tmpLeaseClients | Add-Member noteproperty "Name" ""

$tmpLeaseClients | Add-Member noteproperty "Mask" ""

$tmpLeaseClients | Add-Member noteproperty "MAC" ""

$tmpLeaseClients | Add-Member noteproperty "Expires" ""

$tmpLeaseClients | Add-Member noteproperty "Type" ""

#endregion

#region Create a Temp Reserved Object

# Create a New Object

$tmpLeaseReserved = New-Object psObject

# Add new members to the Object

$tmpLeaseReserved | Add-Member noteproperty "IP" ""

$tmpLeaseReserved | Add-Member noteproperty "MAC" ""

#endregion

# Go over all the Client Lease addresses and export each detail to a temporary var and out to the log file

For($l=0; $l -lt $LeaseClients.IP.Length;$l++)

{

# Get all Scope details to a temp var

$tmpLeaseClients.IP = $LeaseClients.IP[$l] + ","

$tmpLeaseClients.Name = $LeaseClients.Name[$l] + ","

$tmpLeaseClients.Mask =  $LeaseClients.Mask[$l] + ","

$tmpLeaseClients.MAC = $LeaseClients.MAC[$l] + ","

$tmpLeaseClients.Expires = $LeaseClients.Expires[$l] + ","

$tmpLeaseClients.Type = $LeaseClients.Type[$l]

# Export with the Out-CSV Function to the Log File

$tmpLeaseClients | out-csv $LeaseLog -append $true

}



#Continue on figuring out if the DHCP lease clients are in AD or not

#Import the Active Directory module
import-module activedirectory

#import Quest AD module
Add-PSSnapin Quest.ActiveRoles.ADManagement

#connect to AD
Connect-QADService PUTTHEFQDNOFYOURDOMAINHERE_LIKE_DOMAIN.LOCAL | Out-Null

# get input CSV
$leaselogpath = "c:\DHCP\LeaseLog.csv"
Import-csv -path $leaselogpath | 
#query AD for computer name based on csv log
foreach-object `
{ 
   $NameResult = Get-QADComputer -DnsName $_.Name
   If ($NameResult -eq $null) {$RogueSystem = $_.Name}
   $RogueSystem | Out-File C:\DHCP\RogueClients.txt -Append
   $RogueSystem = $null

}
Get-Content C:\DHCP\RogueClients.txt | Select-Object -Unique | Out-File C:\DHCP\RogueClientsFinal.txt
Remove-Item C:\DHCP\RogueClients.txt

#send email to netadmin
$smtpserver = "SMTP SERVER IP"
$from="[email protected]"
$to="[email protected]"
$subject="Non-AD joined DHCP clients"
$body= (Get-Content C:\DHCP\RogueClientsFinal.txt) -join '<BR>&nbsp;<BR>'
$mailer = new-object Net.Mail.SMTPclient($smtpserver)
$msg = new-object Net.Mail.MailMessage($from,$to,$subject,$body)
$msg.IsBodyHTML = $true
$mailer.send($msg)

Espero que ayude a alguien más!

El limpiador
fuente
3

OK, no estoy seguro de seguir la etiqueta aquí, pero estoy publicando una segunda respuesta en lugar de editar la anterior, ya que contenía información que puede ser útil para alguien, incluso si se demuestra que es irrelevante para este caso. Si eso me convierte en un idiota en este foro, siéntase libre de informarme de mis maneras erróneas.

El problema se divide en varias partes, aquí hay sugerencias para las que encuentro más interesantes. Sin ejemplos del registro, esto es lo mejor que puedo hacer, por lo que son solo sugerencias, no soluciones.

Para analizar el registro, use get-contentel -waitparámetro. Para mi caso de uso, es suficiente encontrar un error en un registro de errores.

Esto es lo que funcionó para mi propio caso de uso, perdone el formato:

get-content E:\temp13\log.txt -tail(1) -wait | where {$_ -match "ERROR"} |
    foreach {
        send-mailmessage `
        -port 25 `
        -smtpserver my.mail.server `
        -from [email protected] `
        -to [email protected] `
        -subject "test logmonitor" `
        -body "ERROR found: $_" `
        }

En lugar de $_ -match "ERROR", necesitaría separar el campo ID de registro y el nombre de la computadora de alguna manera. No estoy seguro de cómo hacerlo de la mejor manera en este momento, pero dado que where-object -matchbrinda soporte para expresiones regulares, supongo que podría ser una opción. También puede comenzar almacenando la variable $ _ en otra variable nueva, para poder recogerla a su conveniencia más adelante en la tubería, dentro de bucles foreach anidados, etc.

Suponiendo que pueda obtener el nombre de la computadora, supongo que el get-adcomputercmdlet sería su forma más simple de consultar su AD ( import-module activedirectory), y supongo que, por error, envíe un correo.

import-csvPor supuesto, usar el sería mucho más elegante en su caso, pero no conozco ninguna forma de seguirlo (si alguien lee esto y conoce un truco en ese callejón, por favor, comparta).

ErikE
fuente
Gracias ErikE, correré con esto y te lo haré saber. Tendré que encontrar una manera de obtener la información, consultar AD y luego, después de la "alerta", ignorar futuras comprobaciones de esa misma línea de entrada. Por ejemplo, si consulta el archivo cada cinco minutos, no quiero que vuelva a analizar la misma información y envíe una alerta de duplicación cada 5 minutos.
TheCleaner
Dos cosas que encuentro un poco ordenadas: si solo dejas que se ejecute el script, el parámetro de espera lo hará esperar constantemente a que aparezca una nueva línea. No tiene que volver a ejecutar el script. Esto dará un efecto de empuje en lugar de tirar. Además, la cola (1) solo analizará la última línea 1 al inicio. Por lo tanto, si el administrador de tareas encuentra que tiene que reiniciar el script, y encuentra una manera de inyectar una línea de marcador de posición que desplaza la última línea para activar una alerta, habrá desarmado la molestia de las realecciones.
ErikE
1
Erik, he encontrado una manera de exportar los arrendamientos de DHCP (porque el 2012 tiene un cmdlet PS pero el 2008 no) a un archivo csv. De esta manera, no me meto con los registros de auditoría reales y no tengo que preocuparme por romper nada con la entrada. Comenzaré a jugar con el resto del código y lo actualizaré pronto.
TheCleaner
1

Bajo el supuesto de que está seguro de la ID del evento, y de que ningún otro evento se registra en esta ID en el registro de DHCP, pero sí en los que le interesa, la inserción es una opción.

1) Abra el Administrador del servidor, vaya al registro de DHCP en el Visor de eventos.

2) Encuentre una entrada representativa a la que desee adjuntar su acción. Selecciónelo y haga clic derecho.

3) Elija "Adjuntar tarea a este evento".

4) Se abre el Asistente de creación de tareas, quítelo de allí ...

En realidad, hay una opción de correo electrónico explícito, pero si necesita más lógica que eso, por supuesto, puede usar la opción de iniciar un programa para iniciar powershell.exe y adjuntarle un script. Hay muchos excelentes ejemplos de googleable sobre cómo dejar que el Administrador de tareas ejecute scripts de PowerShell si necesita orientación.

La alternativa inmediata que veo es usar pull analizando el registro de eventos usando powershell a intervalos programados. "The Microsoft Scripting Guy", también conocido como Ed Wilson, ha escrito algunas publicaciones de blog increíbles sobre cómo analizar el Registro de eventos utilizando los cmdlets disponibles en las diferentes versiones de powershell, por lo que sería su sugerencia tomar su blog como punto de partida.

En cuanto a los cmdlets reales, no tengo tiempo en este momento para sacar mi alijo de fragmentos útiles, pero buscaré nuevamente en uno o dos días y puede contribuir si nadie más ha colaborado con algunos bien elegidos, o si no No lo resolvió todo usted solo :-)

ErikE
fuente
2
Erik, gracias. El problema aquí es que el DHCPsrvlog- "día" en C: \ windows \ system32 \ DHCP (con la auditoría DHCP habilitada en la GUI del servidor DHCP), no escribe en ningún registro del Visor de eventos, incluido el registro del Visor de eventos del servidor DHCP en Applications and Services Logs(hasta ahora basado en mi investigación / prueba)
TheCleaner
Me había olvidado de esos registros. Pero creo que este es un lugar posible: analizar el registro de texto usando get-content con las directivas -wait y -tail. Esto es similar a la cola en * nix. Para asegurarse de que una instancia siempre esté analizando el registro, el Administrador de tareas puede programar la secuencia de comandos al inicio del sistema, luego iniciar cada (intervalo más corto posible) pero solo permite una instancia en ejecución. Si aparece tu evento, dispara la lógica.
ErikE
Resulta que tengo un problema similar de análisis de registro para resolver en Windows, publicaré mis hallazgos en esa parte en particular cuando esté seguro de que funciona, y probablemente algunos otros bloques de construcción que tengo por ahí que deberían ser útiles para usted. ¿Podría pegar un par de filas representativas pero ofuscadas de su registro de dhcp? Estoy particularmente interesado en el formato del nombre del dispositivo.
ErikE
1

Si bien esto no aborda su solución deseada, una opción que puede lograr su objetivo es utilizar arpwatch( enlace ) para notificarle cuando se ve un nuevo host (previamente no visto) en la red.

Una alternativa a Windows arpwatchparece ser descafeinante, pero nunca la he usado, así que no puedo hablar de eso bueno o malo.

fukawi2
fuente
Gracias. La idea allí es el sonido. Puedo seguir ese camino si es necesario.
TheCleaner el
El beneficio adicional es que esto también detectaría nuevas máquinas que usan IP estáticas, lo cual no estaba en su alcance establecido, pero probablemente debería estarlo.
mfinni