Public/psf-config.ps1
function Export-FalconConfig { <# .SYNOPSIS Create an archive containing Falcon configuration files .DESCRIPTION Uses various PSFalcon commands to gather and export groups, policies and exclusions as a collection of Json files within a zip archive. The exported files can be used with 'Import-FalconConfig' to restore configurations to your existing CID or create them in another CID. .PARAMETER Select Selected items to export from your current CID, or leave unspecified to export all available items .PARAMETER Force Overwrite an existing file when present .LINK https://github.com/crowdstrike/psfalcon/wiki/Export-FalconConfig #> [CmdletBinding(DefaultParameterSetName='ExportItem',SupportsShouldProcess)] param( [Parameter(ParameterSetName='ExportItem',Position=1)] [ValidateSet('HostGroup','IoaGroup','FirewallGroup','DeviceControlPolicy','FirewallPolicy', 'PreventionPolicy','ResponsePolicy','SensorUpdatePolicy','Ioc','IoaExclusion','MlExclusion', 'Script','SvExclusion')] [Alias('Items')] [string[]]$Select, [Parameter(ParameterSetName='ExportItem')] [switch]$Force ) begin { function Get-ItemContent ([string]$String) { # Request content for provided 'Item' Write-Host "[Export-FalconConfig] Exporting '$String'..." $ConfigFile = Join-Path $Location "$String.json" $Param = @{ Detailed = $true; All = $true} $Config = if ($String -match '^(DeviceControl|Firewall|Prevention|Response|SensorUpdate)Policy$') { # Create policy exports in 'platform_name' order to retain precedence @('Windows','Mac','Linux').foreach{ & "Get-Falcon$String" @Param -Filter "platform_name:'$_'" 2>$null } } else { & "Get-Falcon$String" @Param 2>$null } if ($Config -and $String -eq 'FirewallPolicy') { # Export firewall settings Write-Host "[Export-FalconConfig] Exporting 'FirewallSetting'..." $Settings = Get-FalconFirewallSetting -Id $Config.id 2>$null foreach ($Result in $Settings) { ($Config | Where-Object { $_.id -eq $Result.policy_id }).PSObject.Properties.Add( (New-Object PSNoteProperty('settings',$Result))) } } if ($Config) { # Export results to json file and output created file name ConvertTo-Json @($Config) -Depth 32 | Out-File $ConfigFile -Append $ConfigFile } } # Get current location $Location = (Get-Location).Path # Set output archive path $ExportFile = Join-Path $Location "FalconConfig_$(Get-Date -Format FileDateTime).zip" } process { $OutPath = Test-OutFile $ExportFile if ($OutPath.Category -eq 'WriteError' -and !$Force) { Write-Error @OutPath } else { if (!$Select) { # Use items in 'ValidateSet' when not provided [string[]]$Select = @((Get-Command $MyInvocation.MyCommand.Name).ParameterSets.Where({ $_.Name -eq 'ExportItem' }).Parameters.Where({ $_.Name -eq 'Select' }).Attributes.ValidValues).foreach{ $_ } } if ($Select -match '^((Ioa|Ml|Sv)Exclusion|Ioc)$' -and $Select -notcontains 'HostGroup') { # Force 'HostGroup' when exporting Exclusions or IOCs [string[]]$Select = @($Select + 'HostGroup') } # Force 'FirewallRule' when exporting 'FirewallGroup' if ($Select -contains 'FirewallGroup') { [string[]]$Select = @($Select + 'FirewallRule') } [string[]]$JsonFiles = foreach ($String in $Select) { # Retrieve results, export to Json and capture file name ,(Get-ItemContent $String) } if ($JsonFiles -and $PSCmdlet.ShouldProcess($ExportFile,'Compress-Archive')) { # Archive Json exports with content and remove them when complete $Param = @{ Path = (Get-ChildItem | Where-Object { $JsonFiles -contains $_.FullName -and $_.Length -gt 0 }).FullName DestinationPath = $ExportFile Force = $Force } Compress-Archive @Param @($JsonFiles).foreach{ if (Test-Path $_) { Write-Verbose "[Export-FalconConfig] Removing '$_'." Remove-Item $_ -Force } } } # Display created archive if (Test-Path $ExportFile) { Get-ChildItem $ExportFile | Select-Object FullName,Length,LastWriteTime } } } } function Import-FalconConfig { <# .SYNOPSIS Import items from a 'FalconConfig' archive into your Falcon environment .DESCRIPTION Creates groups, policies, exclusions, rules and scripts within a 'FalconConfig' archive within your authenticated Falcon environment. Anything that already exists will be ignored and no existing items will be modified unless the relevant switch parameters are included. .PARAMETER Path FalconConfig archive path .PARAMETER AssignExisting Assign existing host groups with identical names to imported items .PARAMETER ModifyDefault Modify specified 'platform_default' policies to match import .PARAMETER ModifyExisting Modify existing specified items to match import .LINK https://github.com/crowdstrike/psfalcon/wiki/Import-FalconConfig #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory,Position=1)] [ValidatePattern('\.zip$')] [ValidateScript({ if (Test-Path $_ -PathType Leaf) { $true } else { throw "Cannot find path '$_' because it does not exist or is not a file." } })] [string]$Path, [Alias('Force')] [switch]$AssignExisting, [ValidateSet('DeviceControlPolicy','FirewallPolicy','PreventionPolicy','ResponsePolicy', 'SensorUpdatePolicy')] [string[]]$ModifyDefault, [ValidateSet('DeviceControlPolicy','FirewallGroup','FirewallPolicy','HostGroup','IoaExclusion','IoaGroup', 'Ioc','MlExclusion','PreventionPolicy','ResponsePolicy','Script','SensorUpdatePolicy','SvExclusion')] [string[]]$ModifyExisting ) begin { function Add-Result { # Create result object for CSV output param( [ValidateSet('Created','Modified','Ignored')] [string]$Action, [object]$Item, [string]$Type, [string]$Property, [string]$Old, [string]$New, [string]$Comment ) $Obj = [PSCustomObject]@{ time = Get-Date -Format o api_client_id = $Script:Falcon.ClientId type = $Type id = if ($Action -eq 'Ignored') { $null } else { if ($Item.instance_id) { $Item.instance_id } else { $Item.id } } name = if ($Item.value) { if ($Item.type) { $Item.type,$Item.value -join ':' } else { $Item.value } } else { $Item.name } platform = if ($Item.platform) { if ($Item.platform -is [string[]]) { $Item.platform -join ',' } else { $Item.platform } } elseif ($Item.platforms) { $Item.platforms -join ',' } elseif ($Item.platform_name) { $Item.platform_name } else { $null } action = $Action property = $Property old_value = $Old new_value = $New comment = $Comment } $Config.Result.Add($Obj) if ($Action -match '^(Created|Modified)$') { # Notify when items are created or modified [System.Collections.Generic.List[string]]$Notify = @('[Import-FalconConfig]',$Action) if ($Property) { $Notify.Add("'$Property' for") } if ($Obj.platform -and $Obj.platform -notmatch ',') { $Notify.Add($Obj.platform) } $Notify.Add($Type) $Notify.Add("'$($Obj.Name)'.") Write-Host ($Notify -join ' ') } } function Compare-ImportData ([string]$Item) { if ($Config.$Item.Cid) { # Define properties for comparison between imported and existing items [string[]]$Properties = ($Config.$Item.Cid | Get-Member -MemberType NoteProperty).Name [string[]]$Compare = @('name','type','value').foreach{ if ($Properties -contains $_) { $_ }} $FilterScript = [scriptblock]::Create((@($Compare).foreach{ "`$Config.$($Item).Cid.$($_) -notcontains `$_.$($_)" }) -join ' -and ') @($Config.$Item.Import | Where-Object -FilterScript $FilterScript).foreach{ $_ } if ($ModifyExisting -contains $Item) { # Capture (non-policy) items to modify $FilterScript = [scriptblock]::Create((@($Compare).foreach{ "`$Config.$($Item).Cid.$($_) -contains `$_.$($_)" }) -join ' -and ') @($Config.$Item.Import | Where-Object -FilterScript $FilterScript).foreach{ $Config.$Item.Modify.Add($_) } } } elseif ($Config.$Item.Import) { # Output all items @($Config.$Item.Import).foreach{ $_ } } } function Compare-Setting ([object]$New,[object]$Old,[string]$Type,[string]$Property,[switch]$Result) { if ($Type -match 'Policy$') { # Compare modified policy settings $NewArr = if ($New.prevention_settings) { $New.prevention_settings } else { $New.settings } $OldArr = if ($Old.prevention_settings) { $Old.prevention_settings } else { $Old.settings } if ($OldArr -or $Result) { foreach ($Item in $NewArr) { if ($Item.value.PSObject.Properties.Name -eq 'enabled') { if ($OldArr.Where({ $_.id -eq $Item.id }).value.enabled -ne $Item.value.enabled) { if ($Result) { # Capture modified result for boolean settings Add-Result Modified $New $Type $Item.id ($OldArr.Where({ $_.id -eq $Item.id }).value.enabled) $Item.value.enabled } else { # Output setting to be modified $Item | Select-Object id,value } } } else { foreach ($Name in $Item.value.PSObject.Properties.Name) { if ($OldArr.Where({ $_.id -eq $Item.id }).value.$Name -ne $Item.value.$Name) { if ($Result) { # Capture modified result for sub-settings Add-Result Modified $New $Type ($Item.id,$Name -join ':') (($OldArr | Where-Object { $_.id -eq $Item.id }).value.$Name) $Item.value.$Name } else { # Output setting to be modified $Item | Select-Object id,value } } } } } } else { # Output new settings if ($NewArr.id) { $NewArr | Select-Object id,value } else { $NewArr } } } elseif ($Result) { # Compare other modified item properties if ($Property -eq 'field_values') { foreach ($Name in $New.$Property.name) { # Track 'field_values' for IoaRule for each modified value $OldValue = ($Old.$Property | Where-Object { $_.name -eq $Name }).values | ConvertTo-Json -Compress $NewValue = ($New.$Property | Where-Object { $_.name -eq $Name }).values | ConvertTo-Json -Compress if ($NewValue -ne $OldValue) { Add-Result Modified $New $Type $Name $OldValue $NewValue } } } elseif ($Property) { if ($New.$Property -ne $Old.$Property) { Add-Result Modified $New $Type $Property $Old.$Property $New.$Property } } else { @($New.PSObject.Properties.Name).Where({ $_ -notmatch '^(id|comment)$' }).foreach{ if ($New.$_ -ne $Old.$_) { Add-Result Modified $New $Type $_ $Old.$_ $New.$_ } } } } } function Compress-Property ([object]$Object) { # Remove unnecessary properties and values if ($Object.applied_globally -eq $true -and $Object.PSObject.Properties.Name -contains 'groups') { Set-Property $Object groups @('all') } if ($Object.prevention_settings.settings) { [object[]]$Object.prevention_settings = $Object.prevention_settings.settings | Select-Object id,value } if ($Object.settings.settings) { [object[]]$Object.settings = $Object.settings.settings | Select-Object id,value } if ($Object.groups.id) { [string[]]$Object.groups = $Object.groups.id } if ($Object.rule_group.id) { [string[]]$Object.rule_group = $Object.rule_group.id } if ($Object.ioa_rule_groups.id) { [string[]]$Object.ioa_rule_groups = $Object.ioa_rule_groups.id } return $Object } function Import-ConfigData ([string]$FilePath) { # Load 'FalconConfig' archive into memory [hashtable]$Output = @{ Ids = @{}; Result = [System.Collections.Generic.List[object]]@() } $ByteStream = if ($PSVersionTable.PSVersion.Major -ge 6) { Get-Content $FilePath -AsByteStream } else { Get-Content $FilePath -Encoding Byte -Raw } [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null $FileStream = New-Object System.IO.MemoryStream $FileStream.Write($ByteStream,0,$ByteStream.Length) $ConfigArchive = New-Object System.IO.Compression.ZipArchive($FileStream) [string[]]$Msg = foreach ($FullName in $ConfigArchive.Entries.FullName) { # Import Json, exclude unnecessary properties, add to output and notify $Filename = $ConfigArchive.GetEntry($FullName) $Item = ($FullName | Split-Path -Leaf).Split('.')[0] $Import = ConvertFrom-Json (New-Object System.IO.StreamReader($Filename.Open())).ReadToEnd() $Output[$Item] = @{ Import = ($Import | Select-Object -ExcludeProperty created_by,modified_by, created_timestamp,modified_timestamp) Modify = [System.Collections.Generic.List[object]]@() } $Output.Ids[$Item] = [System.Collections.Generic.List[object]]@() $Item } if ($FileStream) { $FileStream.Dispose() } if ($Msg) { Write-Host "[Import-FalconConfig] Imported from $($FilePath): $($Msg -join ', ')." } $Output } function Invoke-CreateIoc ([object]$Object) { foreach ($i in ($Object.Value.Import | & "New-Falcon$($Object.Key)")) { if ($i.id) { # Track created Ioc Update-Id $i $Object.Key Add-Result Created $i $Object.Key } elseif ($i.type -and $i.value -and $i.message) { @($Object.Value.Import).Where({ $_.type -eq $i.type -and $_.value -eq $i.value }).foreach{ # Ignore failed Ioc Add-Result Ignored $_ $Object.Key -Comment $i.message } } # Remove created and failed Ioc from 'Import' using 'id' value [string[]]$Remove = @($Object.Value.Import).Where({ $_.type -eq $i.type -and $_.value -eq $i.value }).id $Object.Value.Import = @($Object.Value.Import).Where({ $Remove -notcontains $_.id }) } # Repeat until 'Import' is empty if ($Object.Value.Import) { Invoke-CreateIoc $Object } } function Invoke-PolicyAction ([string]$Type,[string]$Action,[string]$PolicyId,[string]$GroupId) { try { # Perform an action on a policy and output result if ($GroupId -and $PolicyId) { $PolicyId | & "Invoke-Falcon$($Type)Action" -Name $Action -GroupId $GroupId } elseif ($PolicyId) { $PolicyId | & "Invoke-Falcon$($Type)Action" -Name $Action } } catch { Write-Error $_ } } function Submit-Group ([string]$Type,[string]$Property,[object]$Object,[object]$Cid) { # Assign group(s) to target object and capture result [string]$Invoke = if ($Property -eq 'ioa_rule_groups') { 'add-rule-group' } else { 'add-host-group' } $Req = foreach ($Id in $Object.$Property) { if ($Cid.$Property -notcontains $Id) { @(Invoke-PolicyAction $Type $Invoke $Object.id $Id).foreach{ $_ } } } if ($Req) { Add-Result Modified $Req[-1] $Type $Property ($Cid.$Property -join ',') ( $Req[-1].$Property.id -join ',') } } function Update-Id ([object]$Item,[string]$Type) { if ($Config.Ids.$Type) { # Add 'new_id' to 'Ids' [string[]]$Compare = @('platform_name','platform','type','value','name').foreach{ if ($Item.$_) { $_ } } [string]$Filter = (@($Compare).foreach{"`$_.$($_) -eq '$($Item.$_)'" }) -join ' -and ' $FilterScript = [scriptblock]::Create($Filter) @($Config.Ids.$Type | Where-Object -FilterScript $FilterScript).foreach{ $_.new_id = if ($Item.family) { $Item.family } else { $Item.id } } } } # Convert 'Path' to absolute and set 'OutputFile' [string]$ArchivePath = $Script:Falcon.Api.Path($PSBoundParameters.Path) [string]$OutputFile = Join-Path (Get-Location).Path "FalconConfig_$(Get-Date -Format FileDateTime).csv" [string]$UserAgent = (Show-FalconModule).UserAgent } process { # Import configuration files and capture id values for comparison $Config = Import-ConfigData $ArchivePath foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Import })) { foreach ($Import in $Pair.Value.Import) { $Import = Compress-Property $Import @($Import | Select-Object name,platform,platforms,platform_name,type,value).foreach{ $Id = if ($Import.family) { $Import.family } else { $Import.id } Set-Property $_ old_id $Id Set-Property $_ new_id $null $Config.Ids.($Pair.Key).Add($_) } } } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch '^(Ids|Result)$' })) { # Retrieve existing items from CID and update their id values $Pair.Value['Cid'] = try { Write-Host "[Import-FalconConfig] Retrieving '$($Pair.Key)'..." @(& "Get-Falcon$($Pair.Key)" -Detailed -All).foreach{ Update-Id $_ $Pair.Key Compress-Property $_ } } catch { throw $_ } if ($Pair.Key -match 'Policy$') { $Pair.Value.Import = foreach ($Policy in $Pair.Value.Import) { if (!($Config.($Pair.Key).Cid | Where-Object { $_.platform_name -eq $Policy.platform_name -and $_.name -eq $Policy.name })) { # Keep only missing policy items for each OS under 'Import' $Policy $Pair.Value.Modify.Add($Policy.PSObject.Copy()) } else { # Add to relevant 'Modify' list or add result as 'Ignored' if (($ModifyDefault -contains $Pair.Key -and $Policy.name -eq 'platform_default') -or ($ModifyExisting -contains $Pair.Key -and $Policy.name -ne 'platform_default')) { $Pair.Value.Modify.Add($Policy.PSObject.Copy()) } elseif ($Policy.name -ne 'platform_default') { Add-Result Ignored $Policy $Pair.Key -Comment Exists } } } } elseif ($Pair.Key -ne 'FirewallRule') { foreach ($Item in $Pair.Value.Import) { # Track 'Ignored' items for final output [string]$Comment = if ($Item.deleted -eq $true) { 'Deleted' } elseif ($Item.type -and $Item.value -and ($Pair.Value.Cid | Where-Object { $_.type -eq $Item.type -and $_.value -eq $Item.value })) { 'Exists' } elseif ($Item.value -and ($Pair.Value.Cid | Where-Object { $_.value -eq $Item.value })) { 'Exists' } elseif ($Item.name -and ($Pair.Value.Cid | Where-Object { $_.name -eq $Item.name })) { 'Exists' } if ($Comment -and $ModifyExisting -notcontains $Pair.Key) { Add-Result Ignored $Item $Pair.Key -Comment $Comment } } # Remove items that will not be created from 'Import' $Pair.Value.Import = Compare-ImportData $Pair.Key } if ($Pair.Key -eq 'SensorUpdatePolicy' -and ($Pair.Value.Import -or $Pair.Value.Modify)) { # Retrieve available sensor build versions to update 'tags' [object[]]$Builds = try { Write-Host "[Import-FalconConfig] Retrieving available sensor builds..." Get-FalconBuild } catch { throw "Failed to retrieve available sensor builds for '$( $Pair.Key)' import. Verify 'Sensor Update Policies: Write' permission." } foreach ($Item in @($Pair.Value.Import + $Pair.Value.Modify)) { # Update tagged builds with current tagged build versions if ($Item.settings.build -match '^\d+\|') { $Tag = ($Item.settings.build -split '\|',2)[-1] $Current = ($Builds | Where-Object { $_.build -like "*|$Tag" -and $_.platform -eq $Item.platform_name }).build if ($Item.settings.build -ne $Current) { $Item.settings.build = $Current } } if ($Item.settings.variants) { # Update tagged 'variant' builds with current tagged build versions @($Item.settings.variants | Where-Object { $_.build -match '^\d+\|' }).foreach{ $Tag = ($_.build -split '\|',2)[-1] $Current = ($Builds | Where-Object { $_.build -like "*|$Tag" -and $_.platform -eq $Item.platform_name }).build if ($_.build -ne $Current) { $_.build = $Current } } } } } } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -eq 'HostGroup' -and $_.Value.Import })) { foreach ($HostGroup in ($Pair.Value.Import | & "New-Falcon$($Pair.Key)")) { # Create HostGroup Update-Id $HostGroup $Pair.Key Add-Result Created $HostGroup $Pair.Key } [void]$Pair.Value.Remove('Import') } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Import -or $_.Value.Modify })) { # Update 'Import' and 'Modify' HostGroup ids @('Import','Modify').foreach{ foreach ($Item in $Pair.Value.$_) { @('groups','host_groups').foreach{ foreach ($OldId in $Item.$_) { [string]$NewId = ($Config.Ids.HostGroup | Where-Object { $_.old_id -eq $OldId }).new_id if ($NewId) { [string[]]$Item.$_ = $Item.$_ -replace $OldId,$NewId } } } } } } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Import })) { @($Pair.Value.Import | & "New-Falcon$($Pair.Key)").foreach{ # Create Policy Update-Id $_ $Pair.Key Add-Result Created $_ $Pair.Key } [void]$Pair.Value.Remove('Import') } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -ne 'FirewallRule' -and $_.Value.Import })) { if ($Pair.Key -eq 'Ioc') { # Create Ioc Invoke-CreateIoc $Pair } elseif ($Pair.Key -eq 'FirewallGroup') { foreach ($Item in $Pair.Value.Import) { [object]$FwGroup = $Item | Select-Object name,enabled,description,comment,rule_ids if ($FwGroup) { if ($FwGroup.rule_ids) { # Select FirewallRule from import using 'family' as 'id' value [object[]]$Rules = foreach ($Id in $FwGroup.rule_ids) { $Config.FirewallRule.Import | Where-Object { $_.family -eq $Id -and $_.deleted -eq $false } } @($Rules).foreach{ # Trim rule names to 64 characters and use 'rules' as 'rule_ids' if ($_.name.length -gt 64) { $_.name = ($_.name).SubString(0,63) } } if ($Rules) { Set-Property $FwGroup rules $Rules [void]$FwGroup.PSObject.Properties.Remove('rule_ids') } } @($FwGroup | & "New-Falcon$($Pair.Key)").foreach{ # Create FirewallGroup Set-Property $FwGroup id $_ Update-Id $FwGroup $Pair.Key Add-Result Created $FwGroup $Pair.Key } } } } elseif ($Pair.Key -eq 'IoaGroup') { foreach ($Item in $Pair.Value.Import) { # Create IoaGroup [object]$IoaGroup = $Item | & "New-Falcon$($Pair.Key)" if ($IoaGroup) { Update-Id $IoaGroup $Pair.Key Add-Result Created $IoaGroup $Pair.Key if ($Item.rules) { # Create IoaRule [object[]]$IoaGroup.rules = foreach ($Rule in $Item.rules) { $Rule.rulegroup_id = $IoaGroup.id $Req = try { $Rule | New-FalconIoaRule } catch { Write-Error $_ } if ($Req) { Add-Result Created $Req IoaRule if ($Req.enabled -eq $false -and $Rule.enabled -eq $true) { $Req.enabled = $true } $Req } } if ($IoaGroup.rules.enabled -eq $true) { @($IoaGroup | Edit-FalconIoaRule).foreach{ @($_.rules).Where({ $_.enabled -eq $true }).foreach{ # Enable IoaRule Add-Result Modified $_ IoaRule enabled $false $_.enabled } } } } if ($Item.enabled -eq $true -and $IoaGroup.enabled -ne $true) { @(& "Edit-Falcon$($Pair.Key)" -Id $IoaGroup.id -Enabled $true).foreach{ # Enable IoaGroup Add-Result Modified $Item $Pair.Key enabled $false $_.enabled } } } } } elseif ($Pair.Key -eq 'Script') { foreach ($Item in $Pair.Value.Import) { # Create Script @($Item | & "Send-Falcon$($Pair.Key)").foreach{ Add-Result Created ($Item | Select-Object name,platform) $Pair.Key } } } elseif ($Pair.Key -match '^((Ioa|Ml|Sv)Exclusion)$') { foreach ($Item in $Pair.Value.Import) { # Create Exclusion @($Item | & "New-Falcon$($Pair.Key)").foreach{ Update-Id $_ $Pair.Key Add-Result Created $_ $Pair.Key } } } [void]$Pair.Value.Remove('Import') } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch 'Policy$' -and $_.Value.Modify })) { [string[]]$Select = switch ($Pair.Key) { # Select required properties for comparison 'FirewallGroup' { 'name','enabled','rule_ids' } 'HostGroup' { 'group_type','name','assignment_rule' } 'IoaGroup' { 'enabled','name','platform','rules' } 'IoaExclusion' { 'name','pattern_id','pattern_name','cl_regex','ifn_regex','groups', 'applied_globally' } 'Ioc' { 'applied_globally','action','deleted','expiration','host_groups','mobile_action', 'platforms','severity','tags','type','value' } 'MlExclusion' { 'value','excluded_from','groups','applied_globally' } 'Script' { 'platform','permission_type','name','content' } 'SvExclusion' { 'value','groups','applied_globally' } } if ($Select) { [object[]]$EditList = foreach ($Item in ($Pair.Value.Modify | Select-Object @($Select + 'id'))) { # Compare each 'Modify' item against CID (excluding non-dynamic HostGroup) [string[]]$Compare = @('name','type','value').foreach{ if ($Select -contains $_) { $_ }} [string]$Filter = (@($Compare).foreach{ "`$_.$($_) -eq `$Item.$($_)" }) -join ' -and ' [object]$Cid = $Config.($Pair.Key).Cid | Select-Object $Select | Where-Object -FilterScript ( [scriptblock]::Create($Filter)) if ($Cid) { [System.Collections.Generic.List[string]]$Modify = @('id') @($Select).Where({ $_ -ne 'id' }).foreach{ [object]$Diff = if ($null -ne $Item.$_ -and $null -ne $Cid.$_) { # Compare properties that exist in both 'Modify' and CID if ($Pair.Key -eq 'IoaGroup' -and $_ -eq 'rules') { foreach ($Rule in $Item.$_) { # Evaluate each IoaRule [object]$CidRule = $Cid.$_ | Where-Object { $_.ruletype_id -eq $Rule.ruletype_id -and $_.name -eq $Rule.name -and $Rule.deleted -ne $true } [string[]]$RuleDiff = if ($CidRule) { @('enabled','pattern_severity','action_label').foreach{ if (Compare-Object $Rule.$_ $CidRule.$_) { $_ } } foreach ($FieldValue in $Rule.field_values) { # Evaluate 'field_value' as a Json string for each IoaRule [object]$CidFieldValue = $CidRule.field_values | Where-Object { $_.name -eq $FieldValue.name -and $_.type -eq $FieldValue.type } if ($CidFieldValue) { if (Compare-Object ($FieldValue.values | ConvertTo-Json) ( $CidFieldValue.values | ConvertTo-Json)) { 'field_values' } } } } if ($RuleDiff) { # Copy existing rule and modify properties [object]$RuleEdit = $CidRule.PSObject.Copy() @($RuleDiff).foreach{ $RuleEdit.$_ = $Rule.$_ } @(Edit-FalconIoaRule -RuleUpdate $RuleEdit -RuleGroupId ( $Item.id)).foreach{ @($RuleDiff).foreach{ # Capture result for each updated setting against original Compare-Setting $RuleEdit $CidRule IoaRule $_ -Result } } } } } elseif ($Pair.Key -eq 'FirewallGroup' -and $_ -eq 'rule_ids') { <# if ($Item.rule_ids) { # Select FirewallRule from import using 'family' as 'id' value [object[]]$FwRule = foreach ($Rule in $Item.rule_ids) { $Config.FirewallRule.Import | Where-Object { $_.family -eq $Rule -and $_.deleted -eq $false } } if ($FwRule) { # Evaluate rules for modification } } #> } else { Compare-Object $Item.$_ $Cid.$_ } } if ($Diff -or ($null -ne $Item.$_ -and $null -eq $Cid.$_)) { # Output properties that differ, or are not present in CID $Modify.Add($_) } } # Output items with properties to be modified and remove from 'Modify' list if ($Modify.Count -gt 1) { $Item | Select-Object $Modify } } } if ($EditList) { foreach ($Edit in $EditList) { # Update with current 'id' and 'comment' when appropriate Set-Property $Edit id ($Config.Ids.($Pair.Key) | Where-Object { $_.old_id -eq $Edit.id }).new_id if ($Pair.Key -ne 'HostGroup') { Set-Property $Edit comment ($UserAgent,"Import-FalconConfig" -join ': ') } } if ($Pair.Key -eq 'FirewallGroup') { [hashtable[]]$DiffOp = @($EditList).foreach{ # Create 'DiffOperations' for FirewallGroup changes if ($null -ne $_.enabled) { @{ op = 'replace'; path = "/enabled"; value = $_.enabled } } } if ($DiffOp) { # Modify FirewallGroup $Req = $EditList | Edit-FalconFirewallGroup -DiffOperation $DiffOp if ($Req) { Compare-Setting $Item ($Config.($Pair.Key).Cid | Where-Object { $_.id -eq $Item.id }) $Pair.Key -Result } } } else { foreach ($Item in ($EditList | & "Edit-Falcon$($Pair.Key)")) { foreach ($Result in ($EditList | Where-Object { $_.id -eq $Item.id })) { @($Result.PSObject.Properties.Name).Where({ $_ -ne 'id' -and $_ -ne 'comment' }).foreach{ # Modify item and capture result Compare-Setting $Item ($Config.($Pair.Key).Cid | Where-Object { $_.id -eq $Item.id }) $Pair.Key $_ -Result } } } } } foreach ($Item in $Pair.Value.Modify) { if (($EditList -and $EditList.id -notcontains $Item.id) -or !$EditList) { # Record result for items that don't need modification [string]$Comment = if ($Pair.Key -eq 'HostGroup' -and $Item.group_type -ne 'dynamic') { 'Static' } else { 'Identical' } Add-Result Ignored $Item $Pair.Key -Comment $Comment } } [void]$Pair.Value.Remove('Modify') } } foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Modify })) { foreach ($Policy in $Pair.Value.Modify) { # Update policy with current id value and use CID value for comparison [string]$Policy.id = ($Config.Ids.($Pair.Key) | Where-Object { $_.name -eq $Policy.name -and $_.platform_name -eq $Policy.platform_name }).new_id [object]$Cid = $Config.($Pair.Key).Cid | Where-Object { $_.id -eq $Policy.id } if ($Pair.Key -eq 'FirewallPolicy') { if ($Policy.settings.policy_id) { $Policy.settings.policy_id = $Policy.id } foreach ($Id in $Policy.rule_group_ids) { # Update 'rule_group_ids' with new id values [object]$Group = $Config.Ids.FirewallGroup | Where-Object { $_.old_id -eq $Id } if ($Group -and $Policy.rule_group_ids -contains $Id) { [string[]]$Policy.rule_group_ids = $Policy.rule_group_ids -replace $Id,$Group.new_id } } if ($Policy.settings) { # Apply FirewallSetting @($Policy.settings | Edit-FalconFirewallSetting).foreach{ Set-Property $Policy settings $Policy.settings } } } elseif ($Policy.prevention_settings -or $Policy.settings) { # Compare Policy settings $Setting = Compare-Setting $Policy $Cid $Pair.Key if ($Setting) { try { # Modify Policy @(& "Edit-Falcon$($Pair.Key)" -Id $Policy.id -Setting $Setting).foreach{ Compare-Setting (Compress-Property $_) $Cid $Pair.Key -Result } } catch { Write-Error $_ } } } if ($Policy.ioa_rule_groups) { # Assign IoaGroup Submit-Group $Pair.Key ioa_rule_groups $Policy $Cid } if ($Policy.name -ne 'platform_default' -and $Policy.groups) { # Assign HostGroup Submit-Group $Pair.Key groups $Policy $Cid } if ($Policy.name -ne 'platform_default' -and $Cid.enabled -ne $Policy.enabled) { # Enable/disable non-default policies [string]$Action = if ($Policy.enabled -eq $true) { 'enable' } else { 'disable' } $Req = Invoke-PolicyAction $Pair.Key $Action $Policy.id if ($Req) { Add-Result Modified $Req $Pair.Key enabled $Cid.enabled $Policy.enabled } } } [void]$Pair.Value.Remove('Modify') } } end { if ($Config.Result | Where-Object { $_.action -ne 'Ignored' }) { # Output warning for existing policy precedence foreach ($Item in ($Config.Result | Where-Object { $_.action -eq 'Created' -and $_.type -match 'Policy$' } | Select-Object type,platform -Unique)) { if ($Config.($Item.type).Cid | Where-Object { $_.platform_name -eq $Item.platform -and $_.name -ne 'platform_default' }) { $PSCmdlet.WriteWarning("[Import-FalconConfig] Existing $($Item.platform) $( $Item.type) items were found. Verify precedence!") } } } @($Config.Result).foreach{ try { $_ | Export-Csv $OutputFile -NoTypeInformation -Append } catch { $_ }} if (Test-Path $OutputFile) { Get-ChildItem $OutputFile | Select-Object FullName,Length,LastWriteTime } } } |