UMN-Qualys.psm1
#region Intro ### # Copyright 2017 University of Minnesota, Office of Information Technology # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Foobar. If not, see <http://www.gnu.org/licenses/>. # Based off # https://community.qualys.com/community/developer # https://www.qualys.com/docs/qualys-api-quick-reference.pdf # https://www.qualys.com/docs/qualys-api-v2-user-guide.pdf # https://www.qualys.com/docs/qualys-asset-management-tagging-api-v2-user-guide.pdf #endregion #region Connect-Qualys function Connect-Qualys{ <# .Synopsis Connect to Qualys API and get back session $cookie for all other functions .DESCRIPTION Connect to Qualys API and get back session $cookie for all other functions. .PARAMETER qualysCred use Get-Credential to create a PSCredential with the username and password of an account that has access to Qualys .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER assetTagging There are two different api endpoints, the new one is Asset Management and Tagging. Use this switch to get a cookie to make calls to Asset Management and Tagging .EXAMPLE $cookie = Connect-Qualys -qualysCred $qualysCred -qualysServer $qualysServer .EXAMPLE $cookie = Connect-Qualys -qualysCred $qualysCred -qualysServer $qualysServer -assetTagging .Notes Author: Travis Sobeck, Kyle Weeks #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [System.Management.Automation.PSCredential]$qualysCred, [Parameter(Mandatory)] [string]$qualysServer, [switch]$assetTagging ) Begin{} Process { $qualysuser = $qualysCred.UserName $qualysPswd = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($qualysCred.Password)) if ($assetTagging) { $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($qualysuser+':'+$qualysPswd)) $header += @{"Authorization" = "Basic $auth"} $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/portal/version" -Method GET -SessionVariable cookie -Headers $header return $cookie } else { ############# Log in ############# ## URL for Logging In/OUT ## Login/out $logInBody = @{ action = "login" username = $qualysuser password = $qualysPswd } ## Log in SessionVariable captures the cookie $uri = "https://$qualysServer/api/2.0/fo/session/" $response = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri $uri -Method Post -Body $logInBody -SessionVariable cookie return $cookie } } End{} } #endregion #region Disconnect-Qualys function Disconnect-Qualys{ <# .Synopsis Disconnect Qaulys API Session, this only works on the old API .DESCRIPTION Disconnect Qaulys API Session, this only works on the old API .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE disconnect-Qualys -uri 'https://qualysapi.qualys.com:443/api/2.0/fo/session/' -header (Get-QualysHeader) .Notes Author: Travis Sobeck #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Login/out $logInBody = @{action = "logout"} $return = (Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/session/" -Method Post -Body $logInBody -WebSession $cookie).SIMPLE_RETURN.RESPONSE.TEXT if ($return -eq 'Logged out'){return $true} else{Write-Warning "Qualys logout issue" + $return} } End{} } #endregion #region Get-QualysAssetGrp function Get-QualysAssetGrp{ <# .Synopsis Get a list of AssetGroup IDs or the ID for a specific AssetGroup .DESCRIPTION Get a list of AssetGroup IDs or the ID for a specific AssetGroup .PARAMETER id Asset Group ID, use this to get a single Asset Group .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [string]$id, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $actionBody = @{action = "list"} if($id){$actionBody['ids'] = $id} [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/asset/group" -Method Get -Body $actionBody -WebSession $cookie $data = $returnedXML.ASSET_GROUP_LIST_OUTPUT.RESPONSE.ASSET_GROUP_LIST.ASSET_GROUP if($id){$data;$data.TITLE.'#cdata-section';$data.IP_SET} else{foreach ($n in 0..($data.Length -1)){"--------------";"Title: " +$data.Get($n).TITLE.'#cdata-section';$data.Get($n);$data.IP_SET}} } End{} } #endregion #region Get-QualysHostAsset function Get-QualysHostAsset{ <# .Synopsis Get Host Asset .DESCRIPTION Get Host Asset .PARAMETER hostID ID of a host .PARAMETER searchTerm part of the name of Host Asset that will be used in a "Contains" search .PARAMETER operator operator to apply to searchTerm, options are 'CONTAINS','EQUALS','NOT EQUALS'. NOTE 'EQUALS' IS case sensative! .PARAMETER IP Get Host Asset by IP address .PARAMETER filter The search section can take a lot of params, see the Qualys Documentation for details. us the filter PARAMETER to create your own custom search .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE .EXAMPLE #> [CmdletBinding()] Param ( [Parameter(Mandatory,ParameterSetName='ID')] [string]$hostID, [Parameter(Mandatory,ParameterSetName='Search')] [string]$searchTerm, [Parameter(ParameterSetName='Search')] [ValidateSet('CONTAINS','EQUALS','NOT EQUALS')] [string]$operator = 'CONTAINS', [Parameter(Mandatory,ParameterSetName='ip')] [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")] [string]$ip, [Parameter(Mandatory,ParameterSetName='filter')] [System.Collections.Hashtable]$filter, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { if ($hostID) { $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/get/am/hostasset/$hostID" -Method GET -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie } elseif ($filter) { $body = $filter | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/search/am/hostasset" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body } elseif ($ip) { $body = @{ServiceRequest = @{filters = @{Criteria = @(@{"field" = "address";"operator" = "EQUALS";"value" = $ip})}}} | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/search/am/hostasset" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body } else { $body = @{ServiceRequest = @{filters = @{Criteria = @(@{"field" = "name";"operator" = $operator;"value" = $searchTerm})}}} | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/search/am/hostasset" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body } return $response.ServiceResponse.data.HostAsset } End{} } #endregion #region Get-QualysReport function Get-QualysReport{ <# .Synopsis Download Qualys Report .DESCRIPTION Download Qualys Report .PARAMETER id Report ID, use Get-QualysReportList to find the ID .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE .EXAMPLE #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$id, [Parameter(Mandatory)] [string]$outFilePath, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ### get the format type $format = (Get-QualysReportList -qualysServer $qualysServer -cookie $cookie -id $id).OUTPUT_FORMAT $outfile = "$outFilePath\qualysReport$ID.$format" ## Create URL, see API docs for path $null = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/report/" -Method get -Body @{action = "fetch";id = "$id"} -WebSession $cookie -OutFile $outfile } End{return $outfile} } #endregion #region Get-QualysReportList function Get-QualysReportList{ <# .Synopsis Get list of Qualys Reports .DESCRIPTION Get list of Qualys Reports .PARAMETER id (Optional) Qualys Report ID, use this to get details on a specific ID .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE .EXAMPLE #> [CmdletBinding()] Param ( [string]$id, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Create URL, see API docs for path ######################### $actionBody = @{action = "list"} if($id){$actionBody['id'] = $id} [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/report/" -Method Get -Body $actionBody -WebSession $cookie $data = $returnedXML.REPORT_LIST_OUTPUT.RESPONSE.REPORT_LIST.REPORT if($id){$data;$data.TITLE.'#cdata-section'} else{foreach ($n in 0..($data.Length -1)){"--------------";"Title: " +$data.Get($n).TITLE.'#cdata-section';$data.Get($n)}} } End{} } #endregion #region Get-QualysScanList function Get-QualysScanList{ <# .Synopsis Get list of Qualys Scans .DESCRIPTION Get list of Qualys Scans .PARAMETER scanRef (Optional) Qualys Scan Reference, use this to get details on a specific Scan .PARAMETER additionalOptions See documentation for full list of additional options and pass in as hashtable .PARAMETER brief Use this switch to get just the title and Ref for faster searching .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE .EXAMPLE #> [CmdletBinding()] Param ( [string]$scanRef, [System.Collections.Hashtable]$additionalOptions, [switch]$brief, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Create URL, see API docs for path ######################### $actionBody = @{action = "list"} if($scanRef){$actionBody['scan_ref'] = $scanRef} if($additionalOptions){$actionBody += $additionalOptions} [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/scan/" -Method Get -Body $actionBody -WebSession $cookie $data = $returnedXML.SCAN_LIST_OUTPUT.RESPONSE.SCAN_LIST.SCAN if ($brief) { if($scanRef){$data.TITLE.'#cdata-section';$data.REF} else { foreach ($n in 0..($data.Length -1)){"--------------";$data.Get($n).TITLE.'#cdata-section';$data[$n].REF} } } else { if($scanRef){"`n--------------`n";"Title: " +$data.TITLE.'#cdata-section';($data | Select-Object REF,TYPE,USER_LOGIN,LAUNCH_DATETIME,DURATION,PROCESSING_PRIORITY,PROCESSED);"State: " + $data.STATUS.STATE;"Target: " + $data.TARGET.'#cdata-section'} else { foreach ($n in 0..($data.Length -1)){"`n--------------`n";"Title: " +$data.Get($n).TITLE.'#cdata-section';($data.Get($n) | Select-Object REF,TYPE,USER_LOGIN,LAUNCH_DATETIME,DURATION,PROCESSING_PRIORITY,PROCESSED);"State: " + $data[$n].STATUS.STATE;"Target: " + $data[$n].TARGET.'#cdata-section'} } } } End{} } #endregion #region Get-QualysScanResults function Get-QualysScanResults{ <# .Synopsis Get results of Qualys Scan .DESCRIPTION Get reults of Qualys Scan .PARAMETER scanRef Qualys Scan Reference, use Get-QualysScanList to find the reference .PARAMETER additionalOptions See documentation for full list of additional options and pass in as hashtable .PARAMETER summary Use this switch to get just the title and Ref for faster searching .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie .EXAMPLE .EXAMPLE #> [CmdletBinding()] Param ( [string]$scanRef, [System.Collections.Hashtable]$additionalOptions, [switch]$brief, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Create URL, see API docs for path ######################### $actionBody = @{action = "fetch";scan_ref = $scanRef;output_format='json'} if($additionalOptions){$actionBody += $additionalOptions} if($brief){$actionBody += @{mode='brief'}} Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/scan/" -Method Get -Body $actionBody -WebSession $cookie } End{} } #endregion #region Get-QualysSchedReportList function Get-QualysSchedReportList{ <# .Synopsis Get a list of Reports Scheduled .DESCRIPTION Get a list of Reports Scheduled .PARAMETER id (Optional) Report Schedule ID .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [string]$id, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Create URL, see API docs for path ######################### $actionBody = @{action = "list"} if($id){$actionBody['id'] = $id} [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/schedule/report" -Method Get -Body $actionBody -WebSession $cookie $data = $returnedXML.SCHEDULE_REPORT_LIST_OUTPUT.RESPONSE.SCHEDULE_REPORT_LIST.REPORT if($id){$data;$data.TITLE.'#cdata-section';$data.TEMPLATE_TITLE.'#cdata-section';$data.SCHEDULE} else{foreach ($n in 0..($data.Length -1)){"--------------";"Title: " +$data.Get($n).TITLE.'#cdata-section';$data.Get($n);$data.Get($n).TEMPLATE_TITLE.'#cdata-section';$data.Get($n).SCHEDULE}} } End{} } #endregion #region Get-QualysTagCount function Get-QualysTagCount{ <# .Synopsis Get-QualysTagCount .DESCRIPTION Get-QualysTagCount .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/count/am/tag" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie if ($response.ServiceResponse.count){return $response.ServiceResponse.count} else{throw "Error $($response.ServiceResponse)"} } End{} } #endregion #region Get-QualysTag function Get-QualysTag{ <# .Synopsis Get Qualys Tag(s) .DESCRIPTION Get Qualys Tag(s) .PARAMETER tagID ID of a tag .PARAMETER searchTerm part of the name of tag that will be used in a "Contains" search .PARAMETER operator operator to apply to searchTerm, options are 'CONTAINS','EQUALS','NOT EQUALS'. NOTE 'EQUALS' IS case sensative! .PARAMETER filter The search section can take a lot of params, see the Qualys Documentation for details. us the filter PARAMETER to create your own custom search .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory,ParameterSetName='ID')] [string]$tagID, [Parameter(Mandatory,ParameterSetName='Search')] [string]$searchTerm, [Parameter(ParameterSetName='Search')] [ValidateSet('CONTAINS','EQUALS','NOT EQUALS')] [string]$operator = 'CONTAINS', [Parameter(Mandatory,ParameterSetName='filter')] [System.Collections.Hashtable]$filter, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { if ($tagID) { $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/get/am/tag/$tagID" -Method GET -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie } elseif ($filter) { $body = $filter | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/search/am/tag" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body } else { $body = @{ServiceRequest = @{filters = @{Criteria = @(@{"field" = "name";"operator" = $operator;"value" = $searchTerm})}}} | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/search/am/tag" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body } return $response.ServiceResponse.data.Tag } End{} } #endregion #region New-QualysHostAsset function New-QualysHostAsset{ <# .Synopsis Create New Qualys Asset .DESCRIPTION Create New Qualys Host Asset .PARAMETER assetName Host Asset's FQDN to be added .PARAMETER tagID ID of tag to add at build time .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$assetName, [Parameter(Mandatory)] [string]$ip, [string]$tagID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $body = @{ServiceRequest = @{data = @{HostAsset = @{name = $assetName;address=$ip;trackingMethod='IP'}}}} if($tagID){$body['ServiceRequest']['data']['HostAsset']['tags'] = @{set=@{TagSimple = @{id = $tagID}}}} $body = $body | ConvertTo-Json -Depth 7 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/create/am/hostasset" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body if ($response.ServiceResponse.responseCode -eq "SUCCESS"){return $response.ServiceResponse.data.HostAsset} else{Throw $($response.ServiceResponse.responseErrorDetails.errorMessage)} } End{} } #endregion #region Invoke-QualysBase function Invoke-QualysBase{ <# .Synopsis Not currently useable .DESCRIPTION .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [System.Collections.Hashtable]$body, [Parameter(Mandatory)] [string]$method, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { return (Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/" -Method $method -Body $body -WebSession $cookie) } End{} } #endregion #region New-QualysIP function New-QualysIP{ <# .Synopsis Add an IP to a specific Group .DESCRIPTION Add an IP to a specific Group .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")] [string]$ip, [Parameter(Mandatory)] [string]$groupID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $actionBody = @{ action = "list" ids = $groupID } ## Run your action, WebSession contains the cookie from login [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"}-Uri "https://$qualysServer/api/2.0/fo/asset/group/" -Method Post -Body $actionBody -WebSession $cookie # Single IPs [System.Collections.ArrayList]$ips = $returnedXML.ASSET_GROUP_LIST_OUTPUT.RESPONSE.ASSET_GROUP_LIST.ASSET_GROUP.IP_SET.IP # IP Ranges, these will take more work to extrapolate [System.Collections.ArrayList]$ipRanges = $returnedXML.ASSET_GROUP_LIST_OUTPUT.RESPONSE.ASSET_GROUP_LIST.ASSET_GROUP.IP_SET.IP_RANGE ## break up the ip range strings, extract all the ips .. blah blah foreach ($range in $ipRanges) { $a,$b = $range -split "-" $a1,$a2,$a3,[int]$a4 = $a -split "\." $b1,$b2,$b3,[int]$b4 = $b -split "\." foreach ($i in $a4 .. $b4) { $newIP = $a1 + "." + $a2 + "." + $a3 + "." + [string]$i # add to the array of IPs, check for doubles if ($ips -notcontains $newIP){$null = $ips.Add($newIP)} } } ########################### now we have a full list of IPs to check against ### check if IP to be added is is in the list if ($ips -notcontains $ip) { $actionBody = @{ action = "edit" id = $groupID add_ips = $ip } [xml]$response = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"}-Uri "https://$qualysServer/api/2.0/fo/asset/group/" -Method Post -Body $actionBody -WebSession $cookie ## check that it worked if (-not ($response.SIMPLE_RETURN.RESPONSE.TEXT -eq 'Asset Group Updated Successfully')){throw "Failed to add IP $ip -- $qualysError"} else{return $true} } else{return $true} } End { $returnedXML = $null } } #endregion #region New-QualysTag function New-QualysTag{ <# .Synopsis Create New Qualys Tag .DESCRIPTION Create New Qualys Tag .PARAMETER tagName Name of a tag to create .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory,ParameterSetName='ID')] [string]$tagName, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Validate tage does not already exist $body = @{ServiceRequest = @{data = @{Tag = @{name = $tagName}}}} | ConvertTo-Json -Depth 5 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/create/am/tag" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body if ($response.ServiceResponse.responseCode -eq "SUCCESS"){return $true} else{throw ($response | Select-Object *)} } End{} } #endregion #region Remove-QualysHostAsset function Remove-QualysHostAsset{ <# .Synopsis Remove Qualys Host Asset .DESCRIPTION Remove New Qualys Host Asset .PARAMETER assetID Host Asset's ID .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$assetID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/delete/am/hostasset/$assetID" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie if ($response.ServiceResponse.responseCode -eq "SUCCESS"){return $true} else{throw ($response | Select-Object *)} } End{} } #endregion #region Remove-QualysHostAssetTag function Remove-QualysHostAssetTag{ <# .Synopsis Remove tag from a Host Asset .DESCRIPTION Remove tag from a Host Asset .PARAMETER hostID ID of a host .PARAMETER tagID ID of tag to apply to Host Asset .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$hostID, [Parameter(Mandatory)] [string]$tagID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $body = @{ServiceRequest = @{data = @{HostAsset = @{tags = @{remove = @{TagSimple = @{id = $tagID}}}}}}} | ConvertTo-Json -Depth 7 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/update/am/hostasset/$hostID" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body ## the quayls api response is junk, to a get to test it actually got added if ($response.ServiceResponse.responseCode -eq 'SUCCESS'){return $true} else{Write-Warning $response.ServiceResponse.responseErrorDetails;return $false} } End{} } #endregion #region Remove-QualysIP function Remove-QualysIP{ <# .Synopsis Remove IP from a group by groupId .DESCRIPTION Remove IP from a group by groupId .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")] [string]$ip, [Parameter(Mandatory)] [string]$groupID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { ## Look at passinging in Asset Group (High or regular) and set IP ######################### $actionBody = @{ action = "edit" id = $groupID remove_ips = $ip } [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri "https://$qualysServer/api/2.0/fo/asset/group/" -Method Post -Body $actionBody -WebSession $cookie if ($returnedXML.SIMPLE_RETURN.RESPONSE.TEXT -ne "Asset Group Updated Successfully"){throw "Error - $ip - " + $returnedXML.SIMPLE_RETURN.RESPONSE.TEXT} else{return $true} } End{} } #endregion #region Set-QualysHostAssetTag function Set-QualysHostAssetTag{ <# .Synopsis Set tag on a Host Asset .DESCRIPTION Set tag on a Host Asset .PARAMETER hostID ID of a host .PARAMETER tagID ID of tag to apply to Host Asset .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .PARAMETER cookie Use Connect-Qualys to get session cookie #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$hostID, [Parameter(Mandatory)] [string]$tagID, [Parameter(Mandatory)] [string]$qualysServer, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie ) Begin{} Process { $body = @{ServiceRequest = @{data = @{HostAsset = @{tags = @{add = @{TagSimple = @{id = $tagID}}}}}}} | ConvertTo-Json -Depth 7 $response = Invoke-RestMethod -Uri "https://$qualysServer/qps/rest/2.0/update/am/hostasset/$hostID" -Method Post -Headers @{'Content-Type' = 'application/json'} -WebSession $cookie -Body $body ## the quayls api response is junk, to a get to test it actually got added if ($response.ServiceResponse.responseCode -eq 'SUCCESS'){return $true} else{return $($response.ServiceResponse.responseErrorDetails.errorMessage)} } End{} } #endregion #region Update-QualysIP function Update-QualysIP{ <# .Synopsis Update IP asset in Qualys. .DESCRIPTION Update the FQDN, NetBIOS, and IP tracking info of the asset. .PARAMETER cookie Use Connect-Qualys to get session cookie .PARAMETER fqdn Domain validated and tested FQDN of host. something.ad.umn.edu .PARAMETER ip Valid IP address asset to be updated .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. .EXAMPLE Update-QualysIP -cookie $cookie -ip $ip -fqdn $fqdn -qualysServer $qualysServer .NOTES Authors: Kyle Weeks #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie, [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")] [string]$ip, [Parameter(Mandatory)] [string]$fqdn, [Parameter(Mandatory)] [string]$qualysServer ) Begin{} Process { $computer = $fqdn.Split('.')[0] $body = "action=update&echo_request=1&ips=$ip&tracking_method=IP&host_dns=$fqdn&host_netbios=$computer" [xml]$response = Invoke-RestMethod -Headers @{'X-Requested-With'="powershell"} -Uri "https://$qualysServer/api/2.0/fo/asset/ip/" -Method Post -WebSession $cookie -Body $body ## check that it worked if (-not ($response.SIMPLE_RETURN.RESPONSE.TEXT -eq 'IP successfully updated')){$return = $response.SIMPLE_RETURN.RESPONSE.TEXT; throw "Failed to update IP $ip - $return"} else{return $true} } End { $response = $null } } #endregion #region Update-QualysAssetGroup function Update-QualysAssetGroup{ <# .Synopsis Update Asset Group .DESCRIPTION Add or remove specific IP address from Asset Group. .PARAMETER action Action to be performed. Add or delete. .PARAMETER cookie Use Connect-Qualys to get session cookie .PARAMETER ip IP address of entry to add/remove .PARAMETER groupID Asset group ID number to modify .PARAMETER qualysServer FQDN of qualys server, see Qualys documentation, based on wich Qualys Platform you're in. #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [ValidateSet('Add','Remove')] [string]$action, [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$cookie, [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")] [string]$ip, [Parameter(Mandatory)] [int]$groupID, [Parameter(Mandatory)] [string]$qualysServer ) Begin{} Process { ## Run your action, WebSession contains the cookie from login $uri = "https://$qualysServer/api/2.0/fo/asset/group/?action=list&ids=$groupID" [xml]$returnedXML = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"} -Uri $uri -Method Post -WebSession $cookie # Single IPs [System.Collections.ArrayList]$ips = $returnedXML.ASSET_GROUP_LIST_OUTPUT.RESPONSE.ASSET_GROUP_LIST.ASSET_GROUP.IP_SET.IP # IP Ranges, these will take more work to extrapolate [System.Collections.ArrayList]$ipRanges = $returnedXML.ASSET_GROUP_LIST_OUTPUT.RESPONSE.ASSET_GROUP_LIST.ASSET_GROUP.IP_SET.IP_RANGE ## break up the ip range strings, extract all the ips part of asset group. foreach ($range in $ipRanges) { $a,$b = $range -split "-" $a1,$a2,$a3,[int]$a4 = $a -split "\." $b1,$b2,$b3,[int]$b4 = $b -split "\." foreach ($i in $a4 .. $b4) { $newIP = $a1 + "." + $a2 + "." + $a3 + "." + [string]$i # add to the array of IPs, check for doubles if ($ips -notcontains $newIP){$null = $ips.Add($newIP)} } } ### check if IP to be added is is in the list If ($action -eq 'Remove') { if ($ips -contains $ip) { $change = 'remove_ips' $uri = "https://$qualysServer/api/2.0/fo/asset/group/?action=edit&id=$groupID&$change=$ip" [xml]$response = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"}-Uri $uri -Method Post -WebSession $cookie ## check that it worked if (-not ($response.SIMPLE_RETURN.RESPONSE.TEXT -eq 'Asset Group Updated Successfully')){$errorResponse = $response.SIMPLE_RETURN.RESPONSE.TEXT;throw "Failed to update Asset Group IP $ip - $errorResponse"} else{return "Asset Group Updated Successfully"} } else{return "IP address $ip was not found in this asset group"} } Else { if ($ips -notcontains $ip) { $change = 'add_ips' $uri = "https://$qualysServer/api/2.0/fo/asset/group/?action=edit&id=$groupID&$change=$ip" [xml]$response = Invoke-RestMethod -Headers @{"X-Requested-With"="powershell"}-Uri $uri -Method Post -WebSession $cookie ## check that it worked if (-not ($response.SIMPLE_RETURN.RESPONSE.TEXT -eq 'Asset Group Updated Successfully')){$errorResponse = $response.SIMPLE_RETURN.RESPONSE.TEXT;throw "Failed to update Asset Group IP $ip - $errorResponse"} else{return "Asset Group Updated Successfully"} } else{return "IP address $ip was already part of this asset group"} } } End { $returnedXML = $null } } #endregion # SIG # Begin signature block # MIIaxgYJKoZIhvcNAQcCoIIatzCCGrMCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUsmzno2MtUPB0EvplNmaQx4IL # HjegghW3MIIEmTCCA4GgAwIBAgIPFojwOSVeY45pFDkH5jMLMA0GCSqGSIb3DQEB # BQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQg # TGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNV # BAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJG # aXJzdC1PYmplY3QwHhcNMTUxMjMxMDAwMDAwWhcNMTkwNzA5MTg0MDM2WjCBhDEL # MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE # BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKjAoBgNVBAMT # IUNPTU9ETyBTSEEtMSBUaW1lIFN0YW1waW5nIFNpZ25lcjCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBAOnpPd/XNwjJHjiyUlNCbSLxscQGBGue/YJ0UEN9 # xqC7H075AnEmse9D2IOMSPznD5d6muuc3qajDjscRBh1jnilF2n+SRik4rtcTv6O # KlR6UPDV9syR55l51955lNeWM/4Og74iv2MWLKPdKBuvPavql9LxvwQQ5z1IRf0f # aGXBf1mZacAiMQxibqdcZQEhsGPEIhgn7ub80gA9Ry6ouIZWXQTcExclbhzfRA8V # zbfbpVd2Qm8AaIKZ0uPB3vCLlFdM7AiQIiHOIiuYDELmQpOUmJPv/QbZP7xbm1Q8 # ILHuatZHesWrgOkwmt7xpD9VTQoJNIp1KdJprZcPUL/4ygkCAwEAAaOB9DCB8TAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUjmstM2v0 # M6eTsxOapeAK9xI1aogwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYD # VR0lAQH/BAwwCgYIKwYBBQUHAwgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny # bC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDA1BggrBgEF # BQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20w # DQYJKoZIhvcNAQEFBQADggEBALozJEBAjHzbWJ+zYJiy9cAx/usfblD2CuDk5oGt # Joei3/2z2vRz8wD7KRuJGxU+22tSkyvErDmB1zxnV5o5NuAoCJrjOU+biQl/e8Vh # f1mJMiUKaq4aPvCiJ6i2w7iH9xYESEE9XNjsn00gMQTZZaHtzWkHUxY93TYCCojr # QOUGMAu4Fkvc77xVCf/GPhIudrPczkLv+XZX4bcKBUCYWJpdcRaTcYxlgepv84n3 # +3OttOe/2Y5vqgtPJfO44dXddZhogfiqwNGAwsTEOYnB9smebNd0+dmX+E/CmgrN # Xo/4GengpZ/E8JIh5i15Jcki+cPwOoRXrToW9GOUEB1d0MYwggV3MIIEX6ADAgEC # AhAT6ihwW/Ts7Qw2YwmAYUM2MA0GCSqGSIb3DQEBDAUAMG8xCzAJBgNVBAYTAlNF # MRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJu # YWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJv # b3QwHhcNMDAwNTMwMTA0ODM4WhcNMjAwNTMwMTA0ODM4WjCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4w # HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVz # dCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzO # iZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwW # IJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YU # VD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1da # t//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+ # UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/z # JSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLa # qUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxb # gtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9A # qURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ # eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwID # AQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1Ud # DgQWBBRTeb9aqitKz1SA4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T # AQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYz # aHR0cDovL2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3Qu # Y3JsMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNl # cnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAQEAk2X2N4OVD17Dghwf1nfnPIrA # qgnw6Qsm8eDCanWhx3nJuVJgyCkSDvCtA9YJxHbf5aaBladG2oJXqZWSxbaPAyJs # M3fBezIXbgfOWhRBOgUkG/YUBjuoJSQOu8wqdd25cEE/fNBjNiEHH0b/YKSR4We8 # 3h9+GRTJY2eR6mcHa7SPi8BuQ33DoYBssh68U4V93JChpLwt70ZyVzUFv7tGu25t # N5m2/yOSkcZuQPiPKVbqX9VfFFOs8E9h6vcizKdWC+K4NB8m2XsZBWg/ujzUOAai # 0+aPDuO0cW1AQsWEtECVK/RloEh59h2BY5adT3Xg+HzkjqnR8q2Ks4zHIc3C7zCC # BawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZIhvcNAQELBQAwfDEL # MAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQ # BgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24xJTAjBgNVBAMTHElu # Q29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0MDAwMDAwWhcNMjAx # MjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTU1NDU1MRIwEAYD # VQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlzMRgwFgYDVQQJDA8x # MDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3Rh # MSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBvcnQxIDAeBgNVBAMM # F1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucCXomNj9NYj8t+JfPn # a3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVaiU5s5hG3vBhfRX8W1 # /2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4qmFGV9RJxtiLZH1q # UldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y/new5+CBUGTAvrw6 # K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSpd75+hvw4Sl3HAaWZ # toWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCCAdQwHwYDVR0jBBgw # FoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4LEhElVUvT8n5txOJS # NAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG # CCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAEXzBdMFsGDCsGAQQB # riMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5pbmNvbW1vbi5vcmcv # Y2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRmMEkGA1UdHwRCMEAw # PqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9JbkNvbW1vblJTQUNv # ZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggrBgEFBQcwAoY4aHR0 # cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNBQ29kZVNpZ25pbmdD # QS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29tbW9uLXJzYS5vcmcw # GQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcNAQELBQADggEBAENR # lesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6iiRJcFVasGLUVFncd # G1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3paDy1sj4CADO2D0gn # xcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/b/yblwA1fKXw4loc # UpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswuavB00T52Y+ZZmz3t # MMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8hy8LgoY5UnGjX5jHs # IvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5QRYXzJiSABeLUMA0G # CSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNl # eTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1Qg # TmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1 # dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5NTlaMHwxCzAJBgNV # BAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQK # EwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYDVQQDExxJbkNvbW1v # biBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB # CgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOYMlYjXAaGlw2A6O2i # gTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsriphHMMzKuHRhxqx+85 # Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj78gw2qblessWBRyN9 # EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4DrkN3dRyJ59UopPgNwm # ucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJmm2j/w2nP35MViPW # Cb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYwHwYDVR0jBBgwFoAU # U3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf//wY9nFDgjCRlMx5w # EIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQM # MAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BB # hj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2VydGlmaWNh # dGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNo # dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5j # cnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZI # hvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4J8HHnQTJgjTbhuET # 98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh26owAmvquMCF1pKi # QWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6c9G1VNwLkl/po9OR # PljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIqUDw3LsrdYWzgg4x0 # 6tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQAsp05HUNCXyhznlV # Lr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5MjvwTzuNorbwBv0DL+ # 7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwfz/8bq3MhTiKOWEb0 # 4FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6Tv4WAVsr/i7LoXU7 # 2gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmxeO4u94196qIqJQl+ # 8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGxCDkbSQf3npXeRFm0 # IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+AEOqnY98j2zRMYIE # eTCCBHUCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQH # EwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21t # b24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0ECEHJNXiAT # 1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC # gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG # CisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFENXOSJIt1ZafA0H+H9dRDmbrr3U # MA0GCSqGSIb3DQEBAQUABIIBAI/FMEsZg4oG2hWR0kqEuaOGYw2fiKHYmtb40AKa # xAkdEB5sQIK30Efaj8TtHcgld65427nlQHqLJXDwoayGmn/NyTOitwrvNEZa49rK # XCeRQUrpS6mxvWT2h2x4zY8Y89zxgg9Jma0LntbtU9PjBzEY3z8L4zkwePvXMF8T # YcrjFT8bn2wiEWdKdF26Bpa66jmPoJyesyTEa+tylcijTLxzj075EzNcYYHNolFH # zSpH84pqFAp+fH3iJQ128dAE4XSE0WZSxgBwU1Cbl+uDiprvPggojiagZesc8I05 # oYRcUF/Z++iFZdM+kpoD2xaRIekUVN7AzQfhlTOgLdpIl1qhggJDMIICPwYJKoZI # hvcNAQkGMYICMDCCAiwCAQEwgakwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJV # VDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJV # U1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0w # GwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdAIPFojwOSVeY45pFDkH5jMLMAkG # BSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ # BTEPFw0xODAzMDExODEwNTJaMCMGCSqGSIb3DQEJBDEWBBR0Kus5GPNdVzDybR5Y # ksfRPedJSTANBgkqhkiG9w0BAQEFAASCAQCY9eG21F6FQkMaH7bvqIwL16y18KOo # HbSLpVD3XBupW27shWxlwrUVxs8KvvBXFQPOsBwVRn3iN+kMD14Y0JXVDipHr1JX # DS/uZUCYRnvP8JYQj0T4ymA1mEOiFzNbuzG/CCSxrHF/yhMcC/Q0/R1HIkOEJkcc # o2FKKgFlIX+ejZ5q2mYaB6LioW5QUx0WxIqOkgA1di+klnAw/DuVZ1CloTXqDj3s # TYVu6iA9gveUEKCNf8bTHhE0CighDOz6lC0wbJUoVicmtoZsQaGNlxxGaExPh/TO # xv8DrM2MDyUshp1lXkefvznKiSM/pUXYsiJvtFXD1q+qPeh5AD+rjc8D # SIG # End signature block |