PSSymantecCloud.psm1
#Region '.\Private\ConvertTo-FlatObject.ps1' 0 # From https://github.com/EvotecIT/PSSharedGoods/tree/master/Public/Converts Function ConvertTo-FlatObject { <# .SYNOPSIS Flattends a nested object into a single level object. .DESCRIPTION Flattends a nested object into a single level object. .PARAMETER Objects The object (or objects) to be flatten. .PARAMETER Separator The separator used between the recursive property names .PARAMETER Base The first index name of an embedded array: - 1, arrays will be 1 based: <Parent>.1, <Parent>.2, <Parent>.3, … - 0, arrays will be 0 based: <Parent>.0, <Parent>.1, <Parent>.2, … - "", the first item in an array will be unnamed and than followed with 1: <Parent>, <Parent>.1, <Parent>.2, … .PARAMETER Depth The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER Uncut The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER ExcludeProperty The propertys to be excluded from the output. .EXAMPLE $Object3 = [PSCustomObject] @{ "Name" = "Przemyslaw Klys" "Age" = "30" "Address" = @{ "Street" = "Kwiatowa" "City" = "Warszawa" "Country" = [ordered] @{ "Name" = "Poland" } List = @( [PSCustomObject] @{ "Name" = "Adam Klys" "Age" = "32" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = "33" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = 30 } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = $null } ) } ListTest = @( [PSCustomObject] @{ "Name" = "Sława Klys" "Age" = "33" } ) } $Object3 | ConvertTo-FlatObject .NOTES Based on https://powersnippets.com/convertto-flatobject/ #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeLine)][Object[]]$Objects, [String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [int]$Depth = 5, [string[]] $ExcludeProperty, [Parameter(DontShow)][String[]]$Path, [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject ) Begin { $InputObjects = [System.Collections.Generic.List[Object]]::new() } Process { foreach ($O in $Objects) { if ($null -ne $O) { $InputObjects.Add($O) } } } End { If ($PSBoundParameters.ContainsKey("OutputObject")) { $Object = $InputObjects[0] $Iterate = [ordered] @{} if ($null -eq $Object) { #Write-Verbose -Message "ConvertTo-FlatObject - Object is null" } elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') { $Object = $Object.ToString() } elseif ($Depth) { $Depth-- If ($Object -is [System.Collections.IDictionary]) { $Iterate = $Object } elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) { $i = $Base foreach ($Item in $Object.GetEnumerator()) { $NewObject = [ordered] @{} If ($Item -is [System.Collections.IDictionary]) { foreach ($Key in $Item.Keys) { if ($Key -notin $ExcludeProperty) { $NewObject[$Key] = $Item[$Key] } } } elseif ($Item -isnot [Array] -and $Item -isnot [System.Collections.IEnumerable]) { foreach ($Prop in $Item.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $NewObject["$($Prop.Name)"] = $Item.$($Prop.Name) } } } else { $NewObject = $Item } $Iterate["$i"] = $NewObject $i += 1 } } else { foreach ($Prop in $Object.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name) } } } } If ($Iterate.Keys.Count) { foreach ($Key in $Iterate.Keys) { if ($Key -notin $ExcludeProperty) { ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty } } } else { $Property = $Path -Join $Separator if ($Property) { # We only care if property is not empty if ($Object -is [System.Collections.IDictionary] -and $Object.Keys.Count -eq 0) { $OutputObject[$Property] = $null } else { $OutputObject[$Property] = $Object } } } } elseif ($InputObjects.Count -gt 0) { foreach ($ItemObject in $InputObjects) { $OutputObject = [ordered]@{} ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty [PSCustomObject] $OutputObject } } } } #EndRegion '.\Private\ConvertTo-FlatObject.ps1' 162 #Region '.\Private\Get-ConfigurationPath.ps1' 0 function Get-ConfigurationPath { <# .SYNOPSIS returns hashtable object with the BaseURL, SepCloudCreds, SepCloudToken full path .DESCRIPTION .INPUTS None .OUTPUTS Hashtable #> @{ BaseUrl = "api.sep.securitycloud.symantec.com" SepCloudCreds = "$env:TEMP\SepCloudOAuthCredentials.xml" SepCloudToken = "$env:TEMP\SepCloudToken.xml" } } #EndRegion '.\Private\Get-ConfigurationPath.ps1' 19 #Region '.\Private\Get-ExcelAllowListObject.ps1' 0 function Get-ExcelAllowListObject { <# TODO fill description .SYNOPSIS Imports excel allow list report from its file path as a PSObject .DESCRIPTION Imports excel allow list report as a PSObject. Same structure that Get-SepCloudPolicyDetails uses to compare Excel allow list and SEP Cloud allow list policy .EXAMPLE Get-ExcelAllowListObject -Excel "WorkstationsAllowListPolicy.xlsx" Imports the excel file and returns a structured PSObject .INPUTS Excel path of allow list policy previously generated from Export-SepCloudPolicyToExcel CmdLet .OUTPUTS Custom PSObject #> param ( # excel path [Parameter( ValueFromPipeline )] [string] [Alias("Excel")] [Alias("Path")] $excel_path ) # List all excel tabs $AllSheets = Get-ExcelSheetInfo $excel_path $SheetsInfo = @{} # Import all Excel info in $SheetsInfo hashtable $AllSheets | ForEach-Object { $SheetsInfo[$_.Name] = Import-Excel $_.Path -WorksheetName $_.Name } # Get Object from ExceptionStructure Class $obj_policy_excel = [ExceptionStructure]::new() # Populates $obj_policy_excel # Add Applications foreach ($line in $SheetsInfo['Applications']) { $obj_policy_excel.AddProcessFile( $line.sha2, $line.Name ) } # Add Certificates foreach ($line in $SheetsInfo['Certificates']) { $obj_policy_excel.AddCertificates( $line.signature_issuer, $line.signature_company_name, $line."signature_fingerprint.algorithm", $line."signature_fingerprint.value" ) } # Add WebDomains foreach ($line in $SheetsInfo['Webdomains']) { $obj_policy_excel.AddWebDomains( $line.domain ) } # Add IPS Hosts foreach ($line in $SheetsInfo['Ips_Hosts']) { $obj_policy_excel.AddIpsHostsIpv4Address( $line.ip ) } # Add IPS Subnet foreach ($line in $SheetsInfo['Ips_Hosts_subnet']) { $obj_policy_excel.AddIpsHostsIpv4Subnet( $line.ip, $line.mask ) } # Add Extensions # no loop required, whole array needed $obj_policy_excel.AddExtensions(@{ names = $sheetsInfo['Extensions'].extensions scheduled = $true features = 'AUTO_PROTECT' } ) # Add Files foreach ($line in $SheetsInfo['Files']) { # Parse "features.X" properties to gather the feature_names in an array [array]$feature_names = @() [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature $i = 0 foreach ($feat in $nb_features) { if ($null -ne $line.($nb_features[$i])) { $feature_names += $line.($nb_features[$i]) } $i++ } # Use AddWindowsFiles $obj_policy_excel.AddWindowsFiles( $line.pathvariable, $line.path, $line.scheduled, $feature_names ) } # Add Directories foreach ($line in $SheetsInfo['Directories']) { # Parse "features.X" properties to gather the feature names in an array [array]$feature_names = @() [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature $i = 0 foreach ($feat in $nb_features) { if ($null -ne $line.($nb_features[$i])) { $feature_names += $line.($nb_features[$i]) } $i++ } # Use AddWindowsDirectories $obj_policy_excel.AddWindowsDirectories( $line.pathvariable, $line.directory, $line.recursive, $line.scheduled, $feature_names ) } return $obj_policy_excel } #EndRegion '.\Private\Get-ExcelAllowListObject.ps1' 132 #Region '.\Private\Get-SEPCloudToken.ps1' 0 function Get-SEPCloudToken { <# .SYNOPSIS Generates an authenticated Token from the SEP Cloud API .DESCRIPTION Gathers Bearer Token from the SEP Cloud console to interact with the authenticated API Securely stores credentials or valid token locally (By default on TEMP location) Connection information available here : https://sep.securitycloud.symantec.com/v2/integration/client-applications .PARAMETER ClientID ClientID parameter required to generate a token .PARAMETER Secret Secret parameter required in combinaison to ClientID to generate a token .EXAMPLE Get-SEPCloudToken .EXAMPLE Get-SEPCloudToken(ClientID,Secret) .NOTES Function logic 1. Test locally stored encrypted token 2. Test locally stored encrypted Client/Secret to generate a token 3. Requests Client/Secret to generate token #> [CmdletBinding()] param ( # ClientID from SEP Cloud Connection App [Parameter()] [string] $ClientID, # Secret from SEP Cloud Connection App [Parameter()] [string] $Secret ) # init $BaseURL = (Get-ConfigurationPath).BaseUrl $SepCloudCreds = (Get-ConfigurationPath).SepCloudCreds $SepCloudToken = (Get-ConfigurationPath).SepCloudToken $URI_Tokens = 'https://' + $BaseURL + '/v1/oauth2/tokens' $URI_Features = 'https://' + $BaseURL + '/v1/devices/enums' # Test if we have a token locally stored if (Test-Path -Path $SepCloudToken) { <# If true, test it against the API #> $Token = Import-Clixml -Path $SepCloudToken $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token } try { $response = Invoke-RestMethod -Method POST -Uri $URI_Features -Headers $Headers # Valid token, returning it Write-Verbose "Local token - valid" return $Token } catch { $StatusCode = $_.Exception.Response.StatusCode Write-Verbose "Authentication error - From locally stored token - Expected HTTP 200, got $([int]$StatusCode) - Continue ..." # Invalid token, deleting local token file Remove-Item $SepCloudToken } } # Test if OAuth cred present on the disk if (Test-Path -Path "$SepCloudCreds") { <# If true, Attempt to get a token #> $OAuth = Import-Clixml -Path $SepCloudCreds $OAuth_Basic = "Basic " + $OAuth $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $OAuth_Basic } try { $response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers # Get the auth token from the response & store it locally Write-Verbose "Valid credentials - returning valid token" $null = $Bearer_Token = "Bearer " + $response.access_token $Bearer_Token | Export-Clixml -Path $SepCloudToken return $Bearer_Token } catch { $StatusCode = $_.Exception.Response.StatusCode Write-Verbose "Authentication error - From locally stored credentials - Expected HTTP 200, got $([int]$StatusCode) - Continue..." # Invalid Credentials, deleting local credentials file Remove-Item $SepCloudCreds } } <# If no token nor OAuth creds available locally # Encode ClientID and Secret to create Basic Auth string # Authentication requires the following "Basic + encoded CliendID:ClientSecret" #> if ($clientID -eq "" -or $Secret -eq "") { Write-Host "No local credentials found" $ClientID = Read-Host -Prompt "Enter ClientID" $Secret = Read-Host -Prompt "Enter Secret" } $Encoded_Creds = [convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($ClientID + ':' + $Secret))) $Encoded_Creds | Export-Clixml -Path $SepCloudCreds # Create Basic Auth string $BasicAuth = "Basic " + $Encoded_Creds $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $BasicAuth } $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -UseBasicParsing # Get the auth token from the response and store it $Bearer_Token = "Bearer " + $Response.access_token $Bearer_Token | Export-Clixml -Path $SepCloudToken return $Bearer_Token } #EndRegion '.\Private\Get-SEPCloudToken.ps1' 125 #Region '.\Private\InternalClass.ps1' 0 # https://stackoverflow.com/a/74901407/2552996 class Extensions { [Collections.Generic.List[string]] $names = [Collections.Generic.List[string]]::new() [bool] $scheduled [Collections.Generic.List[string]] $features = [Collections.Generic.List[string]]::new() } class UpdateAllowlist { [object] $add [object] $remove UpdateAllowlist() { $ExceptionStructureAdd = [ExceptionStructure]::new() $ExceptionStructureRemove = [ExceptionStructure]::new() $this.add = $ExceptionStructureAdd $this.remove = $ExceptionStructureRemove } } class ExceptionStructure { [object] $Applications [object] $Certificates [object] $webdomains [object] $ips_hosts [Extensions] $Extensions [object] $windows # Setting up the PSCustomObject structure from the JSON example : https://pastebin.com/FaKYpgw3 # TODO finish obj structure ExceptionStructure() { $this.applications = [System.Collections.Generic.List[object]]::new() $this.Certificates = [System.Collections.Generic.List[object]]::new() $this.webdomains = [System.Collections.Generic.List[object]]::new() $this.ips_hosts = [System.Collections.Generic.List[object]]::new() $this.extensions = [Extensions]::new() # TODO Extensions obj be hashtable. Converting to JSON will not be incorrect format (list instead of k/v pair) $this.windows = [PSCustomObject]@{ files = [System.Collections.Generic.List[object]]::new() directories = [System.Collections.Generic.List[object]]::new() } } # method to add APPLICATIONS tab to the main obj [void] AddProcessFile( [string] $sha2, [string] $name ) { $this.applications.Add([pscustomobject]@{ processfile = [pscustomobject]@{ sha2 = $sha2 name = $name } }) } # Method to add CERTIFICATES tab to the main obj [void] AddCertificates( [string] $signature_issuer, [string] $signature_company_name, # [string] $signature_fingerprint, [string] $algorithm, [string] $value ) { # $this.certificates.Add() $this.certificates.Add([pscustomobject]@{ signature_issuer = $signature_issuer signature_company_name = $signature_company_name signature_fingerprint = [pscustomobject]@{ algorithm = $algorithm value = $value } }) } # Method to add WEBDOMAINS to the main obj [void] AddWebDomains( [string] $domain ) { $this.webdomains.add([PSCustomObject]@{ domain = $domain }) } # Method to add IPv4 addresses IPS_HOSTS to the main obj [void] AddIpsHostsIpv4Address( [string] $ip ) { $this.ips_hosts.add([PSCustomObject]@{ ip = $ip }) } # Method to add IPv4 subnet IPS_HOSTS to the main obj [void] AddIpsHostsIpv4Subnet( [string] $ip, [string] $mask ) { $this.ips_hosts.add([pscustomobject]@{ ipv4_subnet = [pscustomobject]@{ ip = $ip mask = $mask } }) } # Method to add EXTENSIONS tab to the main obj [void] AddExtensions([Extensions] $Extension) { $this.Extensions = $Extension } # Method to add FILES excel tab to obj [void] AddWindowsFiles( [string] $pathvariable, [string] $path, [bool] $scheduled, [array] $features ) { $this.windows.files.add([pscustomobject]@{ pathvariable = $pathvariable path = $path scheduled = $scheduled features = $features }) } # Method to add DIRECTORIES excel tab to obj [void] AddWindowsDirectories( [string] $pathvariable, [string] $directory, [bool] $recursive, [bool] $scheduled, [array] $features ) { $this.windows.directories.add([pscustomobject]@{ pathvariable = $pathvariable directory = $directory recursive = $recursive scheduled = $scheduled features = $features }) } } #EndRegion '.\Private\InternalClass.ps1' 142 #Region '.\Private\Merge-SepCloudAllowList.ps1' 0 function Merge-SepCloudAllowList { <# TODO add description .SYNOPSIS Merges 2 SEP Cloud allow list policy to a single PSObject .DESCRIPTION Returns a custom PSObject ready to be converted in json as HTTP Body for Update-SepCloudAllowlistPolicy CmdLet Excel file takes precedence in case of conflicts. It is the main "source of truth". Logic goes as below - If SEP exception present in both excel & policy : no changes - If SEP exception present only in Excel : add exception - If SEP exception present only in policy (so not in Excel) : remove exception .NOTES Excel file takes precedence in case of conflicts .INPUTS - SEP cloud allow list policy PSObject - Excel report file path (generated from Export-SepCloudPolicyToExcel CmdLet) .OUTPUTS - Custom PSObject .EXAMPLE Get-SepCloudPolicyDetails -policy_name "My Policy" -policy_version "5" | Merge-SepCloudAllowList -Excel ".\Data\AllowlistReportForWorkstations.xlsx" Gathers SEP Cloud policy, compare it with an excel based allow list policy and returns differences .EXAMPLE Get-SepCloudPolicyDetails -policy_name "My Policy" | Merge-SepCloudAllowList -Excel ".\Data\Excel.xlsx" | Update-SepCloudAllowlistPolicy TODO : verify this example works #> param ( # Policy version [Parameter( )] [string] [Alias("Version")] $Policy_Version, # Exact policy name [Parameter( Mandatory )] [string] [Alias("PolicyName")] $Policy_Name, # excel path [Parameter( Mandatory )] [string] [Alias("Excel")] [Alias("Path")] $excel_path ) # Get policy details to compare with Excel file $obj_policy = Get-SepCloudPolicyDetails -Policy_Name $Policy_Name -Policy_Version $Policy_Version # Import excel report as a structured object with $obj_policy_excel = Get-ExcelAllowListObject -Path $excel_path # Initialize structured obj that will be later converted # to HTTP JSON Body with "add" and "remove" hive $obj_body = [UpdateAllowlist]::new() # Comparison starts here <# As there are no built-in ways to compare 2 deeply nested PSObject Parse through every allow list type (Applications/certificates/etc...) and compare as followed : - If exception type (file/hash/etc) found in both excel / baseline policy : no changes - If found in Excel but not in baseline : set it in "add" hive - If found in baseline but not Excel : set it in "remove" hive UPDATE - instead of adding the logic in this function (Update-SepCloudAllowlistPolicy) - Create the Merge-SepCloudAllowList function to compare 2 "ExceptionStructure" classes #> # Comparison with "Applications" tab $policy_sha2 = $obj_policy.features.configuration.applications.processfile $excel_sha2 = $obj_policy_excel.Applications.processfile # Parsing first excel object foreach ($line in $excel_sha2) { # if sha2 appears in both lists if ($policy_sha2.sha2.contains($line.sha2)) { # No changes needed continue } else { # if sha2 only in excel list, set the sha to the "add" hive $obj_body.add.AddProcessFile( $line.sha2, $line.name ) } } # Parsing then policy object foreach ($line in $policy_sha2) { # if sha2 appears only in policy (so not in Excel) if (-not $excel_sha2.sha2.contains($line.sha2)) { # set the sha to the "remove" hive $obj_body.remove.AddProcessFile( $line.sha2, $line.name ) } } # Comparison with "Certificates" tab $policy_certs = $obj_policy.features.configuration.certificates $excel_certs = $obj_policy_excel.certificates foreach ($line in $excel_certs) { # If certs appears in both lists if ($policy_certs.signature_fingerprint.value.contains($line.signature_fingerprint.value)) { # No changes needed continue } else { # if cert only in excel list, set the cert to the "add" hive $obj_body.add.AddCertificates( $line.signature_issuer, $line.signature_company_name, $line.signature_fingerprint.algorithm, $line.signature_fingerprint.value ) } } # Parsing then policy object foreach ($line in $policy_certs) { # if cert appears only in policy (so not in Excel) if (-not $excel_certs.signature_fingerprint.value.contains($line.signature_fingerprint.value)) { # set the cert to the "remove" hive $obj_body.remove.AddCertificates( $line.signature_issuer, $line.signature_company_name, $line.signature_fingerprint.algorithm, $line.signature_fingerprint.value ) } } # Comparison with "Webdomains" tab $policy_webdomains = $obj_policy.features.configuration.webdomains $excel_webdomains = $obj_policy_excel.webdomains foreach ($line in $excel_webdomains) { # If webdomain appears in both lists if ($policy_webdomains.domain.contains($line.domain)) { # No changes needed continue } else { # if webdomain only in excel list, set the webdomain to the "add" hive $obj_body.add.AddWebDomains( $line.domain ) } } # Parsing then policy object foreach ($line in $policy_webdomains) { # if webdomain appears only in policy (so not in Excel) if (-not $excel_webdomains.domain.contains($line.domain)) { # set the webdomain to the "remove" hive $obj_body.remove.AddWebDomains( $line.domain ) } } # Comparison with "Ips_hosts" tab $policy_ips_hosts = $obj_policy.features.configuration.ips_hosts $excel_ips_hosts = $obj_policy_excel.ips_hosts foreach ($line in $excel_ips_hosts) { # If Ips_hosts appears in both lists if ($policy_ips_hosts.ip.contains($line.ip)) { # No changes needed continue } else { # if Ips_hosts only in excel list, set the Ips_hosts to the "add" hive $obj_body.add.AddIpsHostsIpv4Address( $line.ip ) } } # Parsing then policy object foreach ($line in $policy_ips_hosts) { # if Ips_hosts appears only in policy (so not in Excel) if (-not $excel_ips_hosts.ip.contains($line.ip)) { # set the Ips_hosts to the "remove" hive $obj_body.remove.AddIpsHostsIpv4Address( $line.ip ) } } # Comparison with "Ips_Hosts_subnet" tab $policy_ips_hosts_subnet = $obj_policy.features.configuration.ips_hosts.ipv4_subnet $excel_ips_hosts_subnet = $obj_policy_excel.ips_hosts.ipv4_subnet foreach ($line in $excel_ips_hosts_subnet) { # Getting rid of null arrays in IPS subnets if ($null -ne $line) { # If same IP + mask appears in both lists if ($policy_ips_hosts_subnet.ip.contains($line.ip) -and $policy_ips_hosts_subnet.mask.contains($line.mask)) { # No changes needed continue } else { # if Ips_Hosts_subnet only in excel list # set the Ips_Hosts_subnet to the "add" hive $obj_body.add.AddIpsHostsIpv4Subnet( $line.ip, $line.mask ) } } } # Parsing then policy object foreach ($line in $policy_ips_hosts_subnet) { # if Ips_Hosts_subnet appears only in policy (so not in Excel) if (-not $excel_ips_hosts_subnet.ip.contains($line.ip) -and $excel_ips_hosts_subnet.mask.contains($line.mask)) { # set the Ips_Hosts_subnet to the "remove" hive $obj_body.remove.AddIpsHostsIpv4Subnet( $line.ip, $line.mask ) } } # Comparison with "Extensions" tab $policy_extensions = $obj_policy.features.configuration.extensions $excel_extensions = $obj_policy_excel.extensions foreach ($line in $excel_extensions.names) { # If extension appears in both lists if ($policy_extensions.names.contains($line.names)) { # No changes needed continue } else { # if extension only in excel list, set the extension to the "add" hive [PSCustomObject]$ext = @{ Names = $line scheduled = $true features = 'AUTO_PROTECT' } $obj_body.add.AddExtensions( $ext ) } } # Parsing then policy object $extensions_list_to_remove = @() foreach ($line in $policy_extensions.names) { # if extension appears only in policy (so not in Excel) # Adding it to the $extensions_list_to_remove if (-not $excel_extensions.names.contains($line)) { $extensions_list_to_remove += $line } } # If extensions to remove not empty if ($null -ne $extensions_list_to_remove) { # set the extension to the "remove" hive [PSCustomObject]$ext = @{ Names = $extensions_list_to_remove scheduled = $true features = 'AUTO_PROTECT' } $obj_body.remove.AddExtensions( $ext )} # Comparison with "Files" tab # Comparison ends here # ... return $obj_body } #EndRegion '.\Private\Merge-SepCloudAllowList.ps1' 275 #Region '.\Public\Export-SepCloudPolicyToExcel.ps1' 0 function Export-SepCloudPolicyToExcel { <# .SYNOPSIS Export an Allow List policy object to a human readable excel report .INPUTS Policy object from Get-SepCloudPolicyDetails function. Pipeline support .OUTPUTS Excel file .DESCRIPTION Takes an allow list policy object as input and exports it to an Excel file, with one tab per allow type (filename/file hash/directory etc...) .EXAMPLE Get-SepCloudPolicyDetails -Name "My Allow list Policy" | Export-SepCloudPolicyToExcel -Path "allow_list.xlsx" Gathers policy in an object, pipes the output to Export-SepCloudPolicyToExcel to export in excel format #> param ( # Path of Export [Parameter()] [Alias("Path")] [Alias("Excel")] [string] $excel_path, # Policy Obj to work with [Parameter( ValueFromPipeline, Mandatory )] [pscustomobject] $obj_policy ) <# Using as a template the following command Get-SepCloudPolicyDetails -Name "MyAllowListPolicy" -Policy_version 1 | Export-SepCloudPolicyToExcel -Path "C:\Test\test5.xlsx" Parsing the custom object to get the list of $obj_policy.features.configuration.applications $obj_policy.features.configuration.applications.processfile $obj_policy.features.configuration.applications.processfile.name $obj_policy.features.configuration.applications.processfile.sha2 $obj_policy.features.configuration.certificates $obj_policy.features.configuration.webdomains $obj_policy.features.configuration.ips_hosts $obj_policy.features.configuration.extensions $obj_policy.features.configuration.extensions.names $obj_policy.features.configuration.windows.files $obj_policy.features.configuration.windows.directories #> # Init $Applications = $obj_policy.features.configuration.applications.processfile $Certificates = $obj_policy.features.configuration.certificates $Webdomains = $obj_policy.features.configuration.webdomains $Extensions = $obj_policy.features.configuration.extensions.names $Files = $obj_policy.features.configuration.windows.files $Directories = $obj_policy.features.configuration.windows.directories # Split IPS ipv4 addresses & subnet in 2 different arrays to export in 2 different excel sheets $Ips_Hosts = @() $Ips_Hosts_subnet = @() $Ips_Hosts_temp = $obj_policy.features.configuration.ips_hosts $Ips_Hosts_subnet_temp = $obj_policy.features.configuration.ips_hosts.ipv4_subnet # IPS subnets are a part of IPS_host but is showing empty strings # adding non empty values to correct arrays foreach ($line in $Ips_Hosts_subnet_temp) { if ($null -ne $line) { $Ips_Hosts_subnet += $line } } foreach ($line in $Ips_Hosts_temp) { if ($null -ne $line.ip) { $Ips_Hosts += $line } } # Exporting data to Excel Import-Module -Name ImportExcel $Applications | Export-Excel $excel_path -WorksheetName "Applications" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter $Certificates | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Certificates" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter $Webdomains | Export-Excel $excel_path -WorksheetName "Webdomains" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter $Ips_Hosts | Export-Excel $excel_path -WorksheetName "Ips_Hosts" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter $Ips_Hosts_subnet | Export-Excel $excel_path -WorksheetName "Ips_Hosts_subnet" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter # Extension list comes as an array without name # dumping array from row 2 and manually setting colum name in row 1 $Extensions | Export-Excel $excel_path -WorksheetName "Extensions" -ClearSheet -StartRow 2 $Excel_imported = Open-ExcelPackage -Path $excel_path $Excel_imported.'Extensions'.cells["a1"].Value = 'Extensions' Close-ExcelPackage -ExcelPackage $Excel_imported $Files | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Files" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter $Directories | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Directories" -ClearSheet -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter } #EndRegion '.\Public\Export-SepCloudPolicyToExcel.ps1' 94 #Region '.\Public\Get-SepCloudDeviceInfo.ps1' 0 function Get-SepCloudDeviceInfo { param ( # Mandatory device_ID parameter [Parameter(mandatory)] [string] $Device_ID ) # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/devices/$Device_ID" # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { # HTTP body content containing all the queries $Body = @{} $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token Body = $Body } $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing return $Response } } #EndRegion '.\Public\Get-SepCloudDeviceInfo.ps1' 28 #Region '.\Public\Get-SepCloudDeviceList.ps1' 0 function Get-SepCloudDeviceList { <# TODO fill up description .SYNOPSIS Gathers list of devices from the SEP Cloud console .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .PARAMETER Computername Specify one or many computer names. Accepts pipeline (up to 10 devices per query) Supports partial match .PARAMETER is_online Switch to lookup only online machines .PARAMETER Device_status Lookup devices per security status. Accepts only "SECURE", "AT_RISK", "COMPROMISED", "NOT_COMPUTED" .EXAMPLE Get-SepCloudDeviceList .EXAMPLE Get-SepCloudDeviceList -Computername MyComputer .EXAMPLE Get-SepCloudDeviceList -is_online -Device_status AT_RISK #> [CmdletBinding()] param ( <# Optional ComputerName parameter TODO work to allow multiple values from Computername More info https://apidocs.securitycloud.symantec.com/# name query name of the device. [NOTE] Provide comma seperated values in case of multiple name search Note : seems to be limited to 10 values max #> [Parameter( ValueFromPipeline = $true )] [string] $Computername, # Optional Is_Online parameter [Parameter()] [Alias("Online")] [switch] $is_online, # Optional include_details parameter [Parameter()] [Alias("Details")] [switch] $include_details, # Device Group [Parameter()] [Alias("Group")] [string] $Device_group, # Optional Device_Status parameter [Parameter()] [Alias("DeviceStatus")] [ValidateSet("SECURE", "AT_RISK", "COMPROMISED", "NOT_COMPUTED")] $Device_status ) begin { # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/devices" $ArrayResponse = @() } process { # Get token $Token = Get-SEPCloudToken if ($null -ne $Token ) { # HTTP body content containing all the queries $Body = @{} # Iterating through all parameter and add them to the HTTP body if ($Computername -ne "") { $Body.Add("name", "$Computername") } if ($is_online -eq $true ) { $body.add("is_online", "true") } if ($include_details -eq $true) { $Body.Add("include_details", "true") } if ($Device_status -ne "") { $Body.Add("device_status", "$Device_status") } if ($Device_group -ne "") { $Body.Add("device_group", "$Device_group") } $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token } try { $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing $ArrayResponse += $Response.devices $Devices_count_gathered = (($ArrayResponse | Measure-Object).count) <# If pagination #> if ($Response.total -gt $Devices_count_gathered) { <# Loop through via Offset parameter as there is no "next" parameter for /devices/ API call #> do { # change the "offset" parameter for next query $Body.Remove("offset") $Body.Add("offset", $Devices_count_gathered) # Run query, add it to the array, increment counter $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing $ArrayResponse += $Response.devices $Devices_count_gathered = (($ArrayResponse | Measure-Object).count) } until ( $Devices_count_gathered -ge $Response.total ) } } catch { $StatusCode = $_ $StatusCode } } } end { return $ArrayResponse } } #EndRegion '.\Public\Get-SepCloudDeviceList.ps1' 135 #Region '.\Public\Get-SepCloudEvents.ps1' 0 function Get-SepCloudEvents { <# TODO fill description for Get-SepCloudEvents .SYNOPSIS Get list of SEP Cloud Events. By default, every events for the past 30 days .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .PARAMETER FileDetection Runs the following query under the hood "feature_name:MALWARE_PROTECTION AND ( type_id:8031 OR type_id:8032 OR type_id:8033 OR type_id:8027 OR type_id:8028 ) AND ( id:12 OR id:11 AND type_id:8031 )" .LINK https://github.com/Douda/PSSymantecCloud .EXAMPLE Get-SepCloudEvents -FileDetection .EXAMPLE Get-SepCloudEvents - Query "type_id:8031 OR type_id:8032 OR type_id:8033" #> param ( # file Detection [Parameter()] [switch] $FileDetection, # Custom query to run [Parameter()] [string] $Query ) begin { # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/event-search" $ArrayResponse = @() } process { # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { # HTTP body content containing all mandatory info to start a query $Body = @{ "product" = "SAEP" "feature_name" = "ALL" } <# Setting dates for the query Date Format required : -UFormat "%Y-%m-%dT%T.000+00:00" Example : "start_date": "2022-10-16T00:00:00.000+00:00", "end_date": "2022-11-16T00:00:00.000+00:00" #> $end_date = Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK" $start_date = ((Get-Date).addDays(-29) | Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK") $Body.Add("start_date", $start_date) $Body.Add("end_date", $end_date) # Iterating through all parameter and adding them to the HTTP body switch ($PSBoundParameters.Keys) { 'FileDetection' { $Body.Add("query", '( feature_name:MALWARE_PROTECTION AND ( type_id:8031 OR type_id:8032 OR type_id:8033 OR type_id:8027 OR type_id:8028 ) AND ( id:12 OR id:11 AND type_id:8031 ) )') } 'Query' { $Body.Add("query", "$Query") } Default { } } # Convert body to Json after adding potential query with parameters $Body_Json = ConvertTo-Json $Body $Headers = @{ Host = $BaseURL Accept = "application/json" "Content-Type" = "application/json" Authorization = $Token } try { $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -Body $Body_Json -UseBasicParsing $ArrayResponse += $Response if ($null -ne $Response.next) { <# If pagination #> do { # change the "next" offset for next query $Body.Remove("next") $Body.Add("next", $Response.next) $Body_Json = ConvertTo-Json $Body # Run query & add it to the array $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -Body $Body_Json -UseBasicParsing $ArrayResponse += $Response } until ( ($null -eq $Response.next) ) } } catch { $StatusCode = $_ $StatusCode } } } end { return $ArrayResponse.events } } #EndRegion '.\Public\Get-SepCloudEvents.ps1' 106 #Region '.\Public\Get-SepCloudFeatureList.ps1' 0 function Get-SepCloudFeatureList { <# TODO : fill in description Get-SepCloudFeatureList .SYNOPSIS A short one-line action-based description, e.g. 'Tests if a function is valid' .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not supported in Linux' .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE Test-MyTestFunction -Verbose Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> param ( ) # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/devices/enums" # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { # HTTP body content containing all the queries $Body = @{} $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token Body = $Body } $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing return $Response } } #EndRegion '.\Public\Get-SepCloudFeatureList.ps1' 38 #Region '.\Public\Get-SepCloudIncidents.ps1' 0 function Get-SepCloudIncidents { <# TODO fill description for Get-SepCloudIncidents .SYNOPSIS Get list of SEP Cloud incidents. By default, shows opened incidents .DESCRIPTION Get list of SEP Cloud incidents. Using the LUCENE query syntax, you can customize which incidents to gather. More information : https://techdocs.broadcom.com/us/en/symantec-security-software/endpoint-security-and-management/endpoint-security/sescloud/Endpoint-Detection-and-Response/investigation-page-overview-v134374740-d38e87486/Cloud-Database-Search/query-and-filter-operators-by-data-type-v134689952-d38e88796.html .PARAMETER Open filters only opened incidents. Simulates a query "state_id: [0 TO 3]" which represents incidents with the following states <0 Unknown | 1 New | 2 In Progress | 3 On Hold> .PARAMETER Include_events Includes every events that both are part of the context & triggered incident events .PARAMETER Query Type your customer Lucene query to pass to the API .OUTPUTS PSObject containing all SEP incidents .EXAMPLE Get-SepCloudIncidents -Open -Include_Events .EXAMPLE Get-SepCloudIncidents -Query "state_id: [0 TO 5]" This query a list of every possible incidents (opened, closed and with "Unknown" status) .LINK https://github.com/Douda/PSSymantecCloud #> [CmdletBinding(DefaultParameterSetName = 'QueryOpen')] param ( # Opened incidents [Parameter( ParameterSetName = "QueryOpen" )] [switch] $Open, # Include events [Parameter()] [switch] $Include_events, # Custom query to run [Parameter( ParameterSetName = "QueryCustom" )] [string] $Query ) begin { # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/incidents" $ArrayResponse = @() } process { # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { # HTTP body content containing all the queries $Body = @{} # Settings dates $end_date = Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK" $start_date = ((Get-Date).addDays(-29) | Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK") $Body.Add("start_date", $start_date) $Body.Add("end_date", $end_date) $Body_Json = ConvertTo-Json $Body # Iterating through all parameter and adding them to the HTTP body switch ($PSBoundParameters.Keys) { 'Query' { $Body.Add("query", "$Query") } 'Open' { $Body.Add("query", "state_id: [0 TO 3]") } 'Include_events' { $Body.Add("include_events", "true") } Default { } } $Headers = @{ Host = $BaseURL Accept = "application/json" "Content-Type" = "application/json" Authorization = $Token } try { $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -Body $Body_Json -UseBasicParsing $ArrayResponse += $Response if ($null -ne $Response.next) { <# If pagination #> do { # change the "next" offset for next query $Body.Remove("next") $Body.Add("next", $Response.next) $Body_Json = ConvertTo-Json $Body # Run query & add it to the array $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -Body $Body_Json -UseBasicParsing $ArrayResponse += $Response } until ( ($null -eq $Response.next) ) } } catch { $StatusCode = $_ $StatusCode } } } end { return $ArrayResponse.incidents } } #EndRegion '.\Public\Get-SepCloudIncidents.ps1' 117 #Region '.\Public\Get-SepCloudPolices.ps1' 0 function Get-SepCloudPolices { # TODO to finish; test cmd-let param ( # Policy UUID [Parameter()] [string] $Policy_UUID ) # Init $BaseURL = (Get-ConfigurationPath).BaseUrl $URI_Tokens = 'https://' + $BaseURL + "/v1/policies" # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { # HTTP body content containing all the queries $Body = @{} $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token Body = $Body } $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing return $Response } } #EndRegion '.\Public\Get-SepCloudPolices.ps1' 31 #Region '.\Public\Get-SepCloudPolicyDetails.ps1' 0 function Get-SepCloudPolicyDetails { <# TODO finish Get-SepCloudPolicyDetails description .SYNOPSIS A short one-line action-based description, e.g. 'Tests if a function is valid' .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not supported in Linux' .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE Test-MyTestFunction -Verbose Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> param ( # Policy UUID [Parameter()] [string] $Policy_UUID, # Policy version [Parameter()] [string] [Alias("Version")] $Policy_Version, # Exact policy name [Parameter( ValueFromPipeline, Mandatory )] [string[]] [Alias("Name")] $Policy_Name ) begin { # Init $array_resp = @() } process { # iterating through policy_name list if more than one obj foreach ($p in $Policy_Name) { # Get list of all SEP Cloud policies $obj_policies = (Get-SepCloudPolices).policies $obj_policy = ($obj_policies | Where-Object { $_.name -eq "$p" }) # Use specific version or by default latest if ($Policy_version -ne "") { $obj_policy = $obj_policy | Where-Object { $_.name -eq "$p" -and $_.policy_version -eq $Policy_Version } } else { $obj_policy = ($obj_policy | Sort-Object -Property policy_version -Descending | Select-Object -First 1) } $Policy_UUID = ($obj_policy).policy_uid $Policy_Version = ($obj_policy).policy_version $BaseURL = (Get-ConfigurationPath).BaseUrl $URI = 'https://' + $BaseURL + "/v1/policies/$Policy_UUID/versions/$Policy_Version" # Get token $Token = Get-SEPCloudToken if ($null -ne $Token) { $Body = @{} $Headers = @{ Host = $BaseURL Accept = "application/json" Authorization = $Token Body = $Body } $Resp = Invoke-RestMethod -Method GET -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing $array_resp += $Resp } } } end { return $array_resp } } #EndRegion '.\Public\Get-SepCloudPolicyDetails.ps1' 86 #Region '.\Public\Test-SepCloudConnectivity.ps1' 0 function Test-SepCloudConnectivity { param ( ) if (Get-SEPCloudToken) { Write-Host "Authentication OK" return $true else { Write-Host "Authentication failed" return false } } } #EndRegion '.\Public\Test-SepCloudConnectivity.ps1' 14 #Region '.\Public\Update-SepCloudAllowlistPolicy.ps1' 0 function Update-SepCloudAllowlistPolicy { <# .SYNOPSIS Updates Symantec Allow List policy using an excel file .DESCRIPTION Gathers Allow List policy information from an Excel file generated from Export-SepCloudPolicyToExcel function You can manually add lines to the Excel file, and the updated Excel will be used to add new exceptions to the Allow list policy of your choice .INPUTS - Excel file generated from Export-SepCloudPolicyToExcel function - Policy name to update - OPTIONAL : policy version (default latest version) .PARAMETER Policy_UUID Optional parameter - GUID of the policy. Optional. The function can gathers the UUID from the policy name .PARAMETER Policy_Version Optional parameter - Version of the policy to update. By default, latest version selected .PARAMETER Policy_Name Exact name of the policy to update .PARAMETER ExcelFile Path fo the Excel file that contains updated information on Allow list to update Takes Excel template from Export-SepCloudPolicyToExcel function .NOTES Currently supports only filehash/filename TODO update NOTES when more options will be supported .EXAMPLE TODO review & add more examples Get-SepCloudPolicyDetails Update-SepCloudAllowlistPolicy -policy "My Policy" -ExcelFile .\WorkstationsAllowList.xlsx the file MyAllowList.xlsx can be generated from : get-sepcloudpolicyDetails -name "Workstations Allow List Policy" | Export-SepCloudPolicyToExcel -Path .\Data\WorkstationsAllowList.xlsx #> # TODO to finish; test ParameterSetName Policy param ( # Policy UUID [Parameter( ParameterSetName = 'Policy' )] [string] $Policy_UUID, # Policy version [Parameter( ParameterSetName = 'Policy' )] [string] [Alias("Version")] $Policy_Version, # Exact policy name [Parameter( ValueFromPipeline # Mandatory )] [string] [Alias("PolicyName")] # TODO remove hardcoded info $Policy_Name = "AB - Testing - Allowlist", # Excel file to import data from [Parameter( # Mandatory # TODO add this parameter as mandatory once development is done )] [string] [Alias("Excel")] # TODO remove hardcoded excel path for dev $excel_path = ".\Data\Workstations_allowlist.xlsx" ) # Get PSObject with Merge function # Merge-SepCloudAllowList -obj_policy ***** -Excel **** # To perform once merge function is ready # At this stage $obj_body contains the full import of excel allow list files/directories etc.. # Now we need to compare this obj_body to the policy we'll update to remove duplicates # Converting PSObj to json $Body = $obj_body | ConvertTo-Json -Depth 10 # Get token for API query $Token = Get-SEPCloudToken # API query if ($null -ne $Token) { $Headers = @{ Host = $BaseURL "Content-Type" = "application/json" Accept = "application/json" Authorization = $Token } #$Response = Invoke-RestMethod -Method PATCH -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing # TODO uncomment API query } else { Write-Error "Invalid or empty token - exit" break } # TODO See if we need to remove return once finished return $Response } #EndRegion '.\Public\Update-SepCloudAllowlistPolicy.ps1' 100 |