LiquidObject

Microsoft Network Access Protection Reporting

One of the weak components of Microsoft’s NAP implementation is at it’s core the base functionality is there but reporting capabilities were lacking. Even more so in the event you had to deal with multiple NAP servers running. Beginning with the 2008R2 implementation and going forward there is the ability to optionally use SQL server as the back-end for logging instead of flat text files. However, that’s as far as Microsoft went with it.

With running multiple NAP servers you can aggregate the logging into a single SQL database which gives you the ability to some of your own custom reporting as the data is there but just needs to be queried. Below are some sample PowerShell scripts for getting at least some minimal reporting out of your installs.

Find-User.ps1 locate user based upon a partial username match.

$SqlServer = "SQL-1.liquidobject.com"
$SqlCatalog = "NPS"

function SQLSelect
{
    param($SqlQuery)
    $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $SqlConnection.ConnectionString = "Server = $SqlServer; Database = $SqlCatalog; Integrated Security = True"
    $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $SqlCmd.CommandText = $SqlQuery
    $SqlCmd.Connection = $SqlConnection
    $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $SqlAdapter.SelectCommand = $SqlCmd
    $DataSet = New-Object System.Data.DataSet
    $SqlAdapter.Fill($DataSet)
    $SqlConnection.Close()
    return $DataSet.Tables[0]
}


if($args[0] -ne $NULL)
{
    $myuser = $args[0]
    SQLSelect("select id,timestamp,computer_name as Server,packet_type,User_name,Client_IP_Address as Server_IP,NP_Policy_Name,Called_Station_ID,Quarantine_Update_Non_Compliant as Compliant from accounting_data where user_name like '$myuser%' order by id desc") | FT -AutoSize
}
else
{
    Write-host The script needs to be called in the format: .\Find-User.ps1 username
    $error = 1
}

Get-GeneralStatistics.ps1 shows per NPS server how the load has been balanced across a pair of NPS servers and total usage by authentication method.

$SqlServer = "SQL-1.liquidobject.com"
$SqlCatalog = "NPS"


function SQLSelect
{
    param($SqlQuery)
    $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $SqlConnection.ConnectionString = "Server = $SqlServer; Database = $SqlCatalog; Integrated Security = True"
    $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $SqlCmd.CommandText = $SqlQuery
    $SqlCmd.Connection = $SqlConnection
    $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $SqlAdapter.SelectCommand = $SqlCmd
    $DataSet = New-Object System.Data.DataSet
    $SqlAdapter.Fill($DataSet)
    $SqlConnection.Close()
    return $DataSet.Tables[0]
}
Clear

$NPS1_Count = (SQLSelect("select count(*) from dbo.accounting_data where computer_name = 'NPS-1'")).column1
$NPS2_Count = (SQLSelect("select count(*) from dbo.accounting_data where computer_name = 'NPS-2'")).column1
if($NPS1_Count -gt $NPS2_Count)
{
    $NPS1_Load = [Math]::Round((1-($NPS2_Count / $NPS1_Count))*100,2)
    $NPS2_Load = [Math]::Round(100-$NPS1_Load,2)
}
else
{
    $NPS2_Load = [Math]::Round((1-($NPS1_Count / $NPS2_Count))*100,2)
    $NPS1_Load = [Math]::Round(100-$NPS2_Load,2)

}
Write-Host "`n`n                    Radius Statistics`n"
Write-Host(" ------------------------------------------------------------------------------`n")
Write-Host " NPS-1 has $NPS1_Count status updates`n NPS-1 has $NPS1_Load% of the historical load.`n`n"
Write-Host " NPS-2 has $NPS2_Count status updates`n NPS-2 has $NPS2_Load% of the historical load.`n`n"


$Secure_Count = (SQLSelect("select count(*) from accounting_data where NP_Policy_Name = 'Secure Wireless Connections'")).column1
$EDURoam_Count = (SQLSelect("select count(*) from accounting_data where NP_Policy_Name = 'eduroam'")).column1

Write-Host(" ------------------------------------------------------------------------------`n")
Write-Host(" We have had $Secure_Count Secure Wireless authentication status updates.`n")
Write-Host(" We have had $EDURoam_Count eduroam authentication status updates.`n`n")

Write-Host(" ------------------------------------------------------------------------------`n")

Get-Last50Rows.ps1 returns the last 50 entries in the database.

$SqlServer = "SQL-1.liquidobject.com"
$SqlCatalog = "NPS"

function SQLSelect
{
    param($SqlQuery)
    $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $SqlConnection.ConnectionString = "Server = $SqlServer; Database = $SqlCatalog; Integrated Security = True"
    $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $SqlCmd.CommandText = $SqlQuery
    $SqlCmd.Connection = $SqlConnection
    $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $SqlAdapter.SelectCommand = $SqlCmd
    $DataSet = New-Object System.Data.DataSet
    $SqlAdapter.Fill($DataSet)
    $SqlConnection.Close()
    return $DataSet.Tables[0]
}
Clear


SQLSelect("select TOP 50 id,timestamp,computer_name as Server,packet_type,User_name,Client_IP_Address as Aruba_IP,NP_Policy_Name,Called_Station_ID,Quarantine_Update_Non_Compliant as Compliant from accounting_data order by id desc") | FT -AutoSize

Get-Table.ps1 in the event you need to do some diagnostics, this shows the raw output from the last 1,000 rows.

$SqlServer = "SQL-1.liquidobject.com"
$SqlCatalog = "NPS"

function SQLSelect
{
    param($SqlQuery)
    $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $SqlConnection.ConnectionString = "Server = $SqlServer; Database = $SqlCatalog; Integrated Security = True"
    $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $SqlCmd.CommandText = $SqlQuery
    $SqlCmd.Connection = $SqlConnection
    $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $SqlAdapter.SelectCommand = $SqlCmd
    $DataSet = New-Object System.Data.DataSet
    $SqlAdapter.Fill($DataSet)
    $SqlConnection.Close()
    return $DataSet.Tables[0]
}
Clear


SQLSelect("select Top 1000 * from accounting_data order by id desc") | FT -AutoSize

Pause
, , ,
February 18, 2015 at 8:20 am Comments (0)

Mass enabling windows features via PowerShell

Recently I needed to add the SNMP service to a few hundred systems. There are few more involved methods for this via unattended installs with SCCM or batch scripted GPO-linked entries but if you want a quick and easy way how, try the below.

Target OS: Server 2008, 2008R2, 2012 and 2012R2

Source system was a 2012R2 box with the AD and ServerManager PowerShell modules installed

Import-Module ActiveDirectory
$myservers = Get-ADComputer -SearchBase "OU=My Servers,DC=liquidobject,DC=com" -Filter "*"
foreach($i in $myservers)
{
 $mysession = New-PSSession -ComputerName $i.name
 Invoke-Command -Session $mysession {Import-Module Servermanager}
 Invoke-Command -Session $mysession {Add-WindowsFeature SNMP-Service}
}

Any server in the OU offline or running an unsupported os (ie Server 2003….we all have them) will throw an error. The above is pretty basic example on how you can install any Windows feature remotely. The remote PSSession and Invoke-Command methods allow you to perform any supported PowerShell command remotely.

In the event your running a legacy version of Windows, there are still options available. The limitation of the below is that it requires you to login to the given system and run the command with an administrative command prompt.

servermanagercmd.exe -install snmp-service
logout
, , , , ,
October 16, 2014 at 1:59 pm Comments (0)

IIS website performance tuning

website-speed

After having some free time, to upgrade the underlying hardware running this site along with a few other things. The drive upgrades in particular helped a fair amount on the processing time, however going back and remembering to configure the output caching for IIS was a bigger help. In any event the site should be significantly faster loading for everyone. As IIS output caching is not new by any means, below are some links going over the feature within IIS.

IIS.Net – Configuring IIS 7 Output Caching

IIS.Net Dynamic Content caching

Technet – Kernel-Mode caching

While this feature has been available for years, many IIS websites still haven’t taken advantage of any of the newer features.

, , ,
July 2, 2013 at 7:18 pm Comments (0)

Runaway process checking

Recently I ran into an issue with PHP exhaustion on a Windows Server running IIS. In this scenario the PHP-CGI.exe process would continue to spawn additional instances as load on the server would increase but over time the application pool would struggle and begin to slow to a crawl. In the past I have seen other applications during various iterations of development run into the same issue where if you run into more than “x” instances of an application it is unhealthy or less than “y” instances it is not running properly.

 

$myprocess = "php-cgi"
$myserver = "WebServer"
$mydomain = "liquidobject.com"
$mail_server = "mail.liquidobject.com"
$mail_recipient = "my_support_team@liquidobject.com"
$toomany = 40
$waytoomany = 80

$mail_sender = "$myserver@$mydomain"
$mailreport_subject = "Script: $myserver $myprocess count"
$body = " "

function SendEmailReport
{
    $body = [string]::join([environment]::NewLine, ($body)) 
    $msg = New-Object System.Net.Mail.MailMessage $mail_sender, $mail_recipient, $mailreport_subject, $body
    $client = New-Object System.Net.Mail.SmtpClient $mail_server
    $client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
    $client.Send($msg)
}


$mycount =  (Get-Process -Name $myprocess).count

if($toomany -lt $mycount)
{
	$body = "We have $mycount $myprocess processes, something is unusual."
	if($waytoomany -lt $mycount)
        {
             IISRESET /STOP
             IISRESET /START
             $body = "We have $mycount $myprocess processes, IIS has been reset."
        }
        SendEmailReport
}

In this case we are sending an email notification to the fictional “support team” when more than 40 instances of the php-cgi process are running and in the event no one responds by the time 80 instances are hit the site is automatically bounced to ensure it’s availability.

The simple method for checking is the use of Task Scheduler and call up the script every 5 minutes, pretty simple yet effective.

, , , ,
May 11, 2013 at 7:30 am Comments (0)

Disabling DNS Recursion on Windows Server 2008 R2 and mitigation of DNS Amplification Attacks

Over the past year DNS Amplification attacks have been worse, even to the point that the US-CERT (http://www.us-cert.gov/ncas/alerts/TA13-088A) issued a warning last month. With Linux and Unix BIND DNS Server installs a simple config change can take care of this issue however, in Microsoft’s implementation of the DNS server there is a lack of this basic functionality.

This problem is two fold on Windows if you are also using the recursive DNS servers internally for your DNS forwarders. If you are then the first step is standing up a couple of basic forwarding DNS servers. For an additional level of security I would also suggest deployment with conditional forwarders for domains which you are the root DNS server for to reduce the possibility spoofing (if not using DNSSEC), enhance internal DNS performance, and allow for internal DNS resolution to work in the event your WAN link goes down.

The second step is the actual removal of recursion on your existing DNS servers which the below step by step instructions will take of.

Load up DNS Management, right click on your DNS server and select properties.
DNS-1

Select the “Advanced” tab and check the box to “Disable recursion (also disables forwarders)”

DNS-3

Select “Apply”, then “OK”, and finally close out of the DNS management utility.

At this point we need to stop the DNS server service temporary via “net stop dns”

Now pull up Windows Explorer and navigate to %systemroot%\system32\dns, in this folder you’ll find something called “cache.dns”. This file contains the list of root-forwarders which are always active even though recursion is disabled…. anyway, rename this file to something like cache.dns.bak
DNS-4

Lastly start the DNS server back up “net start dns”

At this point validate recursion is really disabled via nslookup.

On a client system pull up a command line and perform the following

nslookup
server my_non_recursive_dns_server_ip
www.google.com
www.mydomain.org

The resolution for www.google.com should not return an IP address (unless your Google) and the address for www.mydomain.org should return an IP assuming the DNS entry is located within the zone file on the server.

With the above completed, you will no longer be a potential node in a DNS amplification attack.

, , ,
April 10, 2013 at 12:55 pm Comments (2)