Wednesday, 26 August 2015

Palo Alto UID Agent with NPS RADIUS SSO

Don't get me wrong. I love my Palo Alto Firewall. Best decision i have ever made to be purchased, its a brilliant device, but the SSO functionality when it comes to RADIUS is a bit shit because well it isn't there.

The scenario I will give you is if you have NPS(RADIUS) running for your wireless and you would like SSO authenticated internet for you non-domain users, ie BYOD, then you are shit out of luck with Palo Alto, it will prompt the user for a login. Not the end of the world, but still annoying.

Thus this amazing script came which watches the NPS log file, comparing it to the DHCP leases, and will send and xml request through the Palo Alto UID(download UID with your Palo Alto login at link).

It does create a little bit of load on the server and I think it could do with some refining to allow it to run a lot faster. If I ever get around to it I will make a multi threaded version allowing multiple subnets to be scanned at the same time and also do some subnet mapping based off the SSID of the wireless to reduce the load of requesting subnets from the DHCP which appears to be the most CPU intensive and the part that slows the script down the most.

There are two prereqs with 
- Remote DHCP Admin Console, which adds the Powershell Plugins used in the script
- Palo Alto UID Agent with the following turned on

and here is the script
#original script from https://github.com/cesanetwan/uid-radius-script-ps
# modified to remove config file to reduce load as well as fixed a few errors in the code
param([string]$global:strEventUser, [string]$global:strCallingStation)
$ErrorActionPreference = "Stop"
$global:strVersion = "5.8ps"
#create arrays - you can leave them as is
$global:aClientIPs = @()
$global:aExclusions = @()
$global:aDHCPServers = @()
#domain name
$global:strDomain = "DOMAIN"
#location of your NPS Log file
$global:strLogPath = "C:\System32\LogFiles\"
#IP Address of the Palo Alto UID Script
#Runs best if it is installed on the same server as NPS
$global:strAgentServer = "127.0.0.1"
$global:strAgentPort = "5006"
#subnets to exclude from the DHCP Request
$global:aExclusions += ""
#leave this as DHCP, its now the only option that works
$global:strLogFormat = "DHCP"
#DHCP Servers, if you set them up for resiliance you shuld only need one here
$global:aDHCPServers += "PDC"
$global:aDHCPServers += "DC"
#settings for the UID Agent, leave them as is
$global:strVsys = "vsys2"
$global:strAPIKey = "key"
$global:blnAgent = "1"
$global:strTimeout = "120"
#debug to create a log file in the following C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log
$global:strDebug = "0"
#the address of your Palo Alto for the API
$global:strPostAddr = "https://192.168.1.1/api/"
#do you have a proxy server set in the UID Agent?
$global:strProxy = "0"
#do you want it to run multiple passes? if you are running one DHCP you can leave this as 0
$global:blnMultipass = "0"
Function PostToAgent
{
param([string]$strUserAgentData)
If ([int]$global:blnAgent -eq 1)
{
$url = "https://" + $global:strAgentServer + ":" + $global:strAgentPort + "/"
[System.Net.HttpWebRequest]$request = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($url)
$request.Method = "PUT"
If ([int]$global:strDebug -gt 0)
{
$message = "Local agent installed, posting data to " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
If ($global:strProxy -eq "1")
{
       $url = $global:strPostAddr
If ([int]$global:strDebug -gt 0)
{
$message = "Posting to XMLAPIProxy, URL: " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
   $url = $global:strPostAddr + "?key=" + $global:strAPIKey + "&type=user-id&action=set&vsys=" + $global:strVsys + "&client=wget&file-name=UID.xml"
If ([int]$global:strDebug -gt 0)
{
$message = "Posting to XMLAPI on firewall, URL: " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
[System.Net.HttpWebRequest]$request = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($url)
$request.Method = "POST"
}
If ([int]$global:strDebug -gt 0)
{
$message = "Starting post"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$request.ContentType = "text/xml"
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$bytes = [System.Text.Encoding]::UTF8.GetBytes($strUserAgentData)
$request.ContentLength = $bytes.Length
[System.IO.Stream] $outputStream = [System.IO.Stream]$request.GetRequestStream()
        $outputStream.Write($bytes,0,$bytes.Length)
        $outputStream.Close()
If ([int]$global:strDebug -gt 0)
{
$message = "Finished Post"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
try
    {
        [System.Net.HttpWebResponse]$response = [System.Net.HttpWebResponse]$request.GetResponse()  
        $sr = New-Object System.IO.StreamReader($response.GetResponseStream())    
        $txt = $sr.ReadToEnd()
If ([int]$global:strDebug -gt 0)
{
$message = "Response: " + $txt
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
    }
    catch [Net.WebException] {
        [System.Net.HttpWebResponse] $resp = [System.Net.HttpWebResponse] $_.Exception.Response
        add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uidradiuserrors.log" -Value $resp -Force
    }
}
Function CleanMac
{
param([string]$strMac)
$strMac = $strMac -replace "-", ""
        $strMac = $strMac -replace "\.", ""
        $strMac = $strMac -replace ":", ""
        $strMac = $strMac.ToLower()
        return $strMac
}

Function ProcessDHCPClients
{
If ($global:strEventUser.contains("\"))
{
$pos = $global:strEventUser.IndexOf("\")
$global:strEventUser = $global:strEventUser.Substring($pos+1)
} ElseIf ($global:strEventUser.contains("@"))
{
$pos = $global:strEventUser.IndexOf("@")
$global:strEventUser = $global:strEventUser.Substring(0,$pos)
}
If (-Not ($global:strEventUser.contains("$")) -and -Not ($global:strEventUser.contains("host/")) )
{
If ($global:strCallingStation -match "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")
{
$aMatchedIPs = @()
$aMatchedIPs += $global:strCallingStation
If ([int]$global:strDebug -gt 0)
{
$message = "CallingStation is IP, no need for DHCP query"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
If ($global:blnMultipass -eq "0") {
If ([int]$global:strDebug -gt 0)
{
$message = "No MultiPass required, performing single pass"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$aMatchedIPs = @()
foreach ($DHCPServer in $global:aDHCPServers)
{
If ([int]$global:strDebug -gt 0)
{
$message = "Querying DHCP Server: " + [string]$DHCPServer
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$scopes = Get-DhcpServerv4Scope -CN $DHCPServer | select ScopeId
foreach ($scope in $scopes)
                                        {
If ([int]$global:strDebug -gt 0)
{
$message = "Checking Scope: " + [string]$scope
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
                                               $aReservations = Get-DhcpServerv4Lease -CN $DHCPServer -ScopeId $scope.ScopeID -AllLeases | select IPAddress, ClientID
                                               foreach ($reservation in $aReservations)
                                               {
                                                    $MAC = CleanMac($reservation.ClientID)
   If ([int]$global:strDebug -gt 1)
   {
$message = $MAC
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
   }
                                                    $global:strCallingStation = CleanMac($global:strCallingStation)
                                                    If ($global:strCallingStation -eq $MAC)
                                                    {
If ([int]$global:strDebug -gt 0)
       {
   $message = "MAC found, IP is: " + [string]$reservation.IPAddress
   add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
       }
                                                        $aMatchedIPs += $reservation.IPAddress
                                                    }
                                                }
                                        }
}
}
Else
{
If ([int]$global:strDebug -gt 0)
{
$message = "MultiPass required, performing two passes"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$aMatchedIPs = @()
$mp = 0
While ($mp -lt 2) {
If ([int]$global:strDebug -gt 0)
{
$message = "Pass " + [string]$mp + ":"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
foreach ($DHCPServer in $global:aDHCPServers)
{
If ([int]$global:strDebug -gt 0)
{
$message = "Querying DHCP Server: " + [string]$DHCPServer
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$scopes = Get-DhcpServerv4Scope -CN $DHCPServer | select ScopeId
foreach ($scope in $scopes)
                                        {
If ([int]$global:strDebug -gt 0)
{
$message = "Checking Scope: " + [string]$scope
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
                                                $aReservations = Get-DhcpServerv4Lease -ScopeId $scope.ScopeID -AllLeases | select IPAddress, ClientID
                                                foreach ($reservation in $aReservations)
                                                {
                                                    $MAC = CleanMac($reservation.ClientID)
If ([int]$global:strDebug -gt 1)
    {
$message = $MAC
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
    }
                                                    $global:strCallingStation = CleanMac($global:strCallingStation)
                                                    If ($global:strCallingStation -eq $MAC)
                                                    {
If ([int]$global:strDebug -gt 0)
        {
    $message = "MAC found, IP is: " + [string]$reservation.IPAddress
    add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
        }
                                                        $aMatchedIPs += $reservation.IPAddress
                                                    }
                                                }
                                        }
}
$mp = $mp + 1
}
}
}
foreach ($address in $aMatchedIPs)
{
If ($global:strProxy -eq "1")
{
[string]$strXMLLine = "<uid-message><version>1.0</version><scriptv>" + $global:strVersion + "</scriptv><type>update</type><payload><login>"
}
Else
{
[string]$strXMLLine = "<uid-message><version>1.0</version><type>update</type><payload><login>"
}
If ($global:blnAgent -eq "1")
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """/>"
}
Else
{
If ($global:strProxy -eq "1")
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """ timeout=""" + $global:strTimeout + """ vsys=""" + $global:strVsys + """/>"
}
Else
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """ timeout=""" + $global:strTimeout + """/>"
}
}
$strXMLLine = $strXMLLine + "</login></payload></uid-message>"
If ([int]$global:strDebug -gt 0)
{
$message = "Posting mapping: " + $strXMLLine
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
PostToAgent $strXMLLine
}
}
}
Try
{
If ([int]$global:strDebug -gt 0)
{
$ct = Get-Date
$message = "Script launched at " + [string]$ct + " with arguments " + $global:strEventUser + " & " + $global:strCallingStation
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "==========================================================================" -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "Config Loaded Successfully" -Force
}
If ($global:strLogFormat -eq "DTS")
{
#ProcessDTSLog
}
ElseIf ($global:strLogFormat -eq "IAS")
{
#ProcessIASLog
}
ElseIf ($global:strLogFormat -eq "DHCP")
{
If ([int]$global:strDebug -gt 0)
{
$message = "Script in DHCP mode, starting DHCP Process"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
ProcessDHCPClients
If ([int]$global:strDebug -gt 0)
{
$message = "Finished processing DHCP Clients"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
If ([int]$global:strDebug -gt 0)
{
$ct = Get-Date
$message = "Script finished at: " + [string]$ct
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "==========================================================================" -Force
}
}
Catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    $ErrorLog = $FailedItem + " failed with message " + $ErrorMessage
    add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uidradiuserrors.log" -Value $ErrorLog -Force
    Break
}

you will then need to create a task schedule which i have exported for you so you will need to change the script location and the user running

The original script was from https://github.com/cesanetwan/uid-radius-script-ps but it had quite a few errors in it that I have cleaned up and removed the config file from it as it was causing the script to slow down quite a lot. The next stage is to multi thread the sub net scanning to increase the rate at which the script can run and do mapping of subnets to wireless SSIDs coming through the NPS log file. I will most likely also remove all the debugging if statements and have a running/debug version.

anywho i thought it would be a good script to share and credit to the cesanetwan script for the base of this one.

4 comments:

  1. Nice articular, hope you release the multi thread soon

    ReplyDelete
  2. This script stopped working after an update to TLS 1.2 encryption. The error was "failed with message Exception calling "GetRequestStream" with "0" argument(s): "The underlying connection was closed: An unexpected error occurred on a send.""

    To fix this I added this line after "$request.ContentType = "text/xml":
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

    ReplyDelete
  3. Script breaks when multipass is turned on because it doesn't specify the DHCP server

    ReplyDelete