From d84db20d55dd2625c02775e13410fbab6885f33b Mon Sep 17 00:00:00 2001 From: Fabian Bader Date: Tue, 6 Aug 2024 10:17:11 +0200 Subject: [PATCH] test: :test_tube: Add tests for missing incidentConfiguration container --- README.md | 3 + src/SentinelARConverter.psd1 | 2 +- tests/Convert-SentinelARYamlToArm.tests.ps1 | 25 +++++ .../IncidentConfigurationMissing.yaml | 105 ++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tests/examples/IncidentConfigurationMissing.yaml diff --git a/README.md b/README.md index 9526450..a461152 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,9 @@ This way the following KQL query will be converted... ## Changelog +### 2.4.1 + * FIX: Handle error if `incidentConfiguration` section is missing from source YAML in `Convert-SentinelARYamlToArm` when using `-DisableIncidentCreation` + ### 2.4.0 * FEATURE: Support for MITRE sub-Techniques and update default ARM version to `2024-01-01-preview` Thanks to [@Konverto-MartinGasser)](https://github.com/Konverto-MartinGasser) SentinelARConverter now supports MITRE sub-techniques which were introduced in ARM template version `2023-12-01-preview`. diff --git a/src/SentinelARConverter.psd1 b/src/SentinelARConverter.psd1 index df8967b..9086b80 100644 --- a/src/SentinelARConverter.psd1 +++ b/src/SentinelARConverter.psd1 @@ -12,7 +12,7 @@ RootModule = 'SentinelARConverter.psm1' # Version number of this module. - ModuleVersion = '2.4.0' + ModuleVersion = '2.4.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/tests/Convert-SentinelARYamlToArm.tests.ps1 b/tests/Convert-SentinelARYamlToArm.tests.ps1 index 474d3c7..9671003 100644 --- a/tests/Convert-SentinelARYamlToArm.tests.ps1 +++ b/tests/Convert-SentinelARYamlToArm.tests.ps1 @@ -18,6 +18,9 @@ param( [String] $exampleScheduledParameterFilePath = "./tests/examples/ScheduledParam.params.yaml", [Parameter()] + [String] + $exampleIncidentConfigurationMissingFilePath = "./tests/examples/IncidentConfigurationMissing.yaml", + [Parameter()] [Switch] $RetainTestFiles = $false ) @@ -439,6 +442,28 @@ Describe "Convert-SentinelARYamlToArm" { } } + Context "Scheduled with disabled incident creation and incidentConfiguration container missing" { + BeforeAll { + Copy-Item -Path $exampleIncidentConfigurationMissingFilePath -Destination "TestDrive:/Scheduled.yaml" -Force + Convert-SentinelARYamlToArm -Filename "TestDrive:/Scheduled.yaml" -OutFile "TestDrive:/Scheduled.json" -DisableIncidentCreation + $armTemplate = Get-Content -Path "TestDrive:/Scheduled.json" -Raw | ConvertFrom-Json + } + + AfterEach { + if ( -not $RetainTestFiles) { + Remove-Item -Path "TestDrive:/*" -Include *.json -Force + } + } + + It "Should have the incident creation disabled" { + $armTemplate.resources[0].properties.incidentConfiguration.createIncident | Should -Be $false + } + + It "Should not fail when incidentConfiguration is missing" { + { Convert-SentinelARYamlToArm -Filename "TestDrive:/Scheduled.yaml" -OutFile "TestDrive:/Scheduled.json" -DisableIncidentCreation } | Should -Not -Throw + } + } + Context "Scheduled with parameter file provided" { BeforeAll { Copy-Item -Path $exampleScheduledWithVariablesFilePath -Destination "TestDrive:/Scheduled.yaml" -Force diff --git a/tests/examples/IncidentConfigurationMissing.yaml b/tests/examples/IncidentConfigurationMissing.yaml new file mode 100644 index 0000000..ef13370 --- /dev/null +++ b/tests/examples/IncidentConfigurationMissing.yaml @@ -0,0 +1,105 @@ +id: bb616d82-108f-47d3-9dec-9652ea0d3bf6 +name: Account Created and Deleted in Short Timeframe +description: | + 'Search for user principal name (UPN) events. Look for accounts created and then deleted in under 24 hours. Attackers may create an account for their use, and then remove the account when no longer needed. + Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#short-lived-account' +severity: High +requiredDataConnectors: + - connectorId: AzureActiveDirectory + dataTypes: + - SigninLogs +queryFrequency: 1h +queryPeriod: 1d +triggerOperator: gt +triggerThreshold: 0 +status: Available +tactics: + - InitialAccess +relevantTechniques: + - T1078.004 +tags: + - AADSecOpsGuide +query: | + let queryfrequency = 1h; + let queryperiod = 1d; + AuditLogs + | where TimeGenerated > ago(queryfrequency) + | where OperationName =~ "Delete user" + | mv-apply TargetResource = TargetResources on + ( + where TargetResource.type == "User" + | extend TargetUserPrincipalName = extract(@'([a-f0-9]{32})?(.*)', 2, tostring(TargetResource.userPrincipalName)) + ) + | extend DeletedByApp = tostring(InitiatedBy.app.displayName), + DeletedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId), + DeletedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName), + DeletedByAadUserId = tostring(InitiatedBy.user.id), + DeletedByIPAddress = tostring(InitiatedBy.user.ipAddress) + | project Deletion_TimeGenerated = TimeGenerated, TargetUserPrincipalName, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress, + Deletion_AdditionalDetails = AdditionalDetails, Deletion_InitiatedBy = InitiatedBy, Deletion_TargetResources = TargetResources + | join kind=inner ( + AuditLogs + | where TimeGenerated > ago(queryperiod) + | where OperationName =~ "Add user" + | mv-apply TargetResource = TargetResources on + ( + where TargetResource.type == "User" + | extend TargetUserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName)) + ) + | project-rename Creation_TimeGenerated = TimeGenerated + ) on TargetUserPrincipalName + | extend TimeDelta = Deletion_TimeGenerated - Creation_TimeGenerated + | where TimeDelta between (time(0s) .. queryperiod) + | extend CreatedByApp = tostring(InitiatedBy.app.displayName), + CreatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId), + CreatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName), + CreatedByAadUserId = tostring(InitiatedBy.user.id), + CreatedByIPAddress = tostring(InitiatedBy.user.ipAddress) + | project Creation_TimeGenerated, Deletion_TimeGenerated, TimeDelta, TargetUserPrincipalName, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress, + CreatedByApp, CreatedByAppServicePrincipalId, CreatedByUserPrincipalName, CreatedByAadUserId, CreatedByIPAddress, Creation_AdditionalDetails = AdditionalDetails, Creation_InitiatedBy = InitiatedBy, Creation_TargetResources = TargetResources, Deletion_AdditionalDetails, Deletion_InitiatedBy, Deletion_TargetResources + | extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0]) + | extend CreatedByName = tostring(split(CreatedByUserPrincipalName,'@',0)[0]), CreatedByUPNSuffix = tostring(split(CreatedByUserPrincipalName,'@',1)[0]) + | extend DeletedByName = tostring(split(DeletedByUserPrincipalName,'@',0)[0]), DeletedByUPNSuffix = tostring(split(DeletedByUserPrincipalName,'@',1)[0]) +entityMappings: + - entityType: Account + fieldMappings: + - identifier: FullName + columnName: TargetUserPrincipalName + - identifier: Name + columnName: TargetName + - identifier: UPNSuffix + columnName: TargetUPNSuffix + - entityType: Account + fieldMappings: + - identifier: FullName + columnName: CreatedByUserPrincipalName + - identifier: Name + columnName: CreatedByName + - identifier: UPNSuffix + columnName: CreatedByUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: CreatedByAadUserId + - entityType: Account + fieldMappings: + - identifier: FullName + columnName: DeletedByUserPrincipalName + - identifier: Name + columnName: DeletedByName + - identifier: UPNSuffix + columnName: DeletedByUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: DeletedByAadUserId + - entityType: IP + fieldMappings: + - identifier: Address + columnName: CreatedByIPAddress + - entityType: IP + fieldMappings: + - identifier: Address + columnName: DeletedByIPAddress +version: 1.1.0 +kind: Scheduled