Classes/Main/PaloAltoDevice.Class.ps1
class PaloAltoDevice { [string]$Name [string]$Model [string]$Serial [string]$Hostname [string]$ApiKey # Verion info [string]$OsVersion [string]$GpAgent [string]$AppVersion [string]$ThreatVersion [string]$WildFireVersion [string]$UrlVersion # Settings [bool]$VsysEnabled [xml]$Config [ValidateRange(1, 65535)] [int]$Port = 443 [ValidateSet('http', 'https')] [string]$Protocol = "https" # Context Data [string]$TargetVsys = 'shared' [string]$TargetDeviceGroup # Track usage hidden [bool]$Connected hidden [string]$ConfigNode [array]$UrlHistory [array]$RawQueryResultHistory [array]$QueryHistory [array]$QueryParamHistory $LastError $LastResult # Create XPath [string] createXPath ([string]$ConfigNode, [string]$Name) { $XPath = '/config' $this.ConfigNode = $ConfigNode $ObjectsInSharedOnNonVsysSystems = @() $ObjectsInSharedOnNonVsysSystems += 'reports' # choose correct vsys/device-group $PanoramaNodesinDevices = @( 'device-group' 'deviceconfig' 'log-collector' 'log-colletor-group' 'platform' 'plugins' 'template' 'template-stack' 'wildfire-appliance' 'wildfire-appliance-cluster' ) if ($this.Model -eq "Panorama") { if ($this.TargetDeviceGroup) { $XPath += "/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='$($this.TargetDeviceGroup)']" } else { $ConfigNodeFound = $false :PanoramaNodesinDevices foreach ($node in $PanoramaNodesinDevices) { if ($ConfigNode -match $node) { $XPath += "/devices/entry[@name='localhost.localdomain']" $ConfigNodeFound = $true break PanoramaNodesinDevices } } if (-not $ConfigNodeFound) { $XPath += '/shared' } } } elseif ($ConfigNode -match 'deviceconfig') { $XPath += "/devices/entry[@name='localhost.localdomain']" } elseif ($ConfigNode -match 'network/') { $XPath += "/devices/entry[@name='localhost.localdomain']" } else { if ($this.VsysEnabled) { if ($this.TargetVsys -eq 'shared') { $XPath += '/shared' } else { $XPath += "/devices/entry/vsys/entry[@name='$($this.TargetVsys)']" } } else { if ($ObjectsInSharedOnNonVsysSystems -contains $ConfigNode) { $XPath += '/shared' } else { $XPath += "/devices/entry/vsys/entry[@name='vsys1']" } } } # Add ConfigNode $XPath += "/$ConfigNode" if ($Name) { $XPath += "/entry[@name='$Name']" } return $XPath } # Create query string static [string] createQueryString ([hashtable]$hashTable) { $i = 0 $queryString = "?" foreach ($hash in $hashTable.GetEnumerator()) { $i++ $queryString += $hash.Name + "=" + $hash.Value if ($i -lt $HashTable.Count) { $queryString += "&" } } return $queryString } # Generate Api URL [String] getApiUrl([string]$formattedQueryString) { if ($this.Hostname) { $url = "https://" + $this.Hostname + "/api/" + $formattedQueryString return $url } else { return $null } } ##################################### Main Api Query Function ##################################### # invokeApiQuery [xml] invokeApiQuery([hashtable]$queryString,[string]$method, $body) { # If the query is not a keygen query we need to append the apikey to the query string if ($queryString.type -ne "keygen") { $queryString.key = $this.ApiKey } # format the query string and general the full url $formattedQueryString = [HelperWeb]::createQueryString($queryString) $url = $this.getApiUrl($formattedQueryString) # Populate Query/Url History # Redact password if it's a keygen query if ($queryString.type -ne "keygen") { $this.UrlHistory += $url } else { $this.UrlHistory += $url.Replace($queryString.password, "PASSWORDREDACTED") $queryString.password = $queryString.password, "PASSWORDREDACTED" } # add query object to QueryHistory $this.QueryHistory += $queryString # try query try { $QueryParams = @{} $QueryParams.Uri = $url $QueryParams.UseBasicParsing = $true $QueryParams.Method = $method switch ($method) { <# 'PUT' { $QueryParams.Uri += $this.createQueryString($queryString) if ('' -ne $body) { $QueryParams.Body = $body } } #> 'POST' { if ('' -ne $body) { $QueryParams.Body = $body if ($queryString.type -eq 'import' -and $queryString.category -eq 'keypair') { $Boundary = [System.Guid]::NewGuid().ToString() $QueryParams.ContentType = "multipart/form-data; boundary=`"$Boundary`"" $QueryParams.TimeoutSec = 60 } } #$QueryParams.ContentType = 'application/json' } <# 'PATCH' { $QueryParams.Body = $body $QueryParams.ContentType = 'application/json' } #> <# 'GET' { $QueryParams.Uri += $this.createQueryString($queryString) } #> <# 'DELETE' { $QueryParams.Uri += $this.createQueryString($queryString) } #> } <# if ($queryString.type -eq "keygen") { $QueryParams.Method = 'POST' } else { $QueryParams.Method = $method } #> switch ($global:PSVersionTable.PSEdition) { 'Core' { $QueryParams.SkipCertificateCheck = $true continue } default { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 try { add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ } catch { } [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy continue } } $this.QueryParamHistory += $QueryParams $rawResult = Invoke-WebRequest @QueryParams } catch { Throw $_ } $result = [xml]($rawResult.Content) $this.RawQueryResultHistory += $rawResult $this.LastResult = $result $proccessedResult = $this.processQueryResult($result) return $proccessedResult } # processQueryResult [xml] processQueryResult ([xml]$unprocessedResult) { $result = $null switch ($unprocessedResult.response.status) { 'success' { $result = $unprocessedResult } 'error' { if ($unprocessedResult.response.msg.line) { if ($unprocessedResult.response.msg.line.'#cdata-section') { $Message = $unprocessedResult.response.msg.line.'#cdata-section' -join "`r`n" Write-Verbose "line and #cdata-section detected: $Message" } else { $Message = $unprocessedResult.response.msg.line -join "`r`n" Write-Verbose "line detected: $Message" } } else { $Message = $unprocessedResult.response.msg Write-Verbose "line not detected: $Message" } Throw $Message } 'unauth' { $Message = $unprocessedResult.response.msg.line Throw $Message } } return $result } # Keygen API Query [xml] invokeKeygenQuery([PSCredential]$credential) { $queryString = @{} $queryString.type = "keygen" $queryString.user = $credential.UserName $queryString.password = $Credential.getnetworkcredential().password $result = $this.invokeApiQuery($queryString,'POST','') $this.ApiKey = $result.response.result.key return $result } # Commit API Query [xml] invokeCommitQuery([string]$cmd) { $queryString = @{} $queryString.type = "commit" $queryString.cmd = $cmd $result = $this.invokeApiQuery($queryString) return $result } # Operational API Query [xml] invokeOperationalQuery([string]$cmd) { $queryString = @{} $queryString.type = "op" $queryString.cmd = $cmd $result = $this.invokeApiQuery($queryString) return $result } # invokeConfigQuery without element [Xml] invokeConfigQuery([string]$Action, [string]$XPath) { $queryString = @{} $queryString.type = "config" $queryString.action = $Action $queryString.xpath = $xPath $result = $this.invokeApiQuery($queryString) return $result } # invokeConfigQuery with element/location [Xml] invokeConfigQuery([string]$Action, [string]$XPath, [string]$Element) { $queryString = @{} $queryString.type = "config" $queryString.action = $Action $queryString.xpath = $XPath switch ($Action) { 'move' { $queryString.where = $Element continue } 'set' { $queryString.element = $Element continue } } $result = $this.invokeApiQuery($queryString) return $result } # invokeReportQuery [Xml] invokeReportQuery([string]$ReportType, [string]$ReportName, [string]$Cmd) { $queryString = @{} $queryString.type = "report" $queryString.reporttype = $ReportType $queryString.reportname = $ReportName $queryString.cmd = $Cmd $result = $this.invokeApiQuery($queryString) return $result } # invokeReportGetQuery [Xml] invokeReportGetQuery([int]$JobId) { $queryString = @{} $queryString.type = "report" $queryString.action = "get" $queryString.'job-id' = $JobId $result = $this.invokeApiQuery($queryString) return $result } # with just a querystring [psobject] invokeApiQuery([hashtable]$queryString) { return $this.invokeApiQuery($queryString, 'GET', '') } # with just a method [psobject] invokeApiQuery([string]$method) { return $this.invokeApiQuery(@{}, $method, '') } # with no method or querystring specified [psobject] invokeApiQuery() { return $this.invokeApiQuery(@{}, 'GET', '') } # https://<firewall>/api/?type=report&action=get&job-id=jobid # Test Connection [bool] testConnection() { $result = $this.invokeOperationalQuery('<show><system><info></info></system></show>') $this.Connected = $true $this.Name = $result.response.result.system.devicename $this.Hostname = $result.response.result.system.'ip-address' $this.Model = $result.response.result.system.model $this.Serial = $result.response.result.system.serial $this.OsVersion = $result.response.result.system.'sw-version' $this.GpAgent = $result.response.result.system.'global-protect-client-package-version' $this.AppVersion = $result.response.result.system.'app-version' $this.ThreatVersion = $result.response.result.system.'threat-version' $this.WildFireVersion = $result.response.result.system.'wildfire-version' $this.UrlVersion = $result.response.result.system.'url-filtering-version' if ($result.response.result.system.'multi-vsys' -eq 'on') { $this.VsysEnabled = $true } else { $this.VsysEnabled = $false } return $true } ##################################### Initiators ##################################### # Initiator with apikey PaloAltoDevice([string]$Hostname, [string]$ApiKey) { $this.Hostname = $Hostname $this.ApiKey = $ApiKey } # Initiator with Credential PaloAltoDevice([string]$Hostname, [PSCredential]$Credential) { $this.Hostname = $Hostname $this.invokeKeygenQuery($Credential) } # Initiator with configfile PaloAltoDevice([string]$ConfigFilePath) { $this.Config = [xml](Get-Content -Path $ConfigFilePath -Raw) $this.OsVersion = $this.Config.config.version $this.Name = $this.Config.config.devices.entry.deviceconfig.system.hostname $this.HostName = $this.Config.config.devices.entry.deviceconfig.system.'ip-address' } } |