I am trying to bulk enable Analytcis rules in Sentinel using
azurerm_sentinel_alert_rule_scheduled
I am loading yaml files from local directory and decoding them with yamldecode function. I can access fields like each.value.severity, each.value.description, each.value.tactics (list), however fields queryFrequency and queryPeriod for some reason are not available.
locals {
yaml_files = fileset("D:\\Terraform\\RG_LAW_SENTINEL\\AnalyticsYaml\\", "*.yaml")
yaml_data = [for f in local.yaml_files : yamldecode(file("D:\\Terraform\\RG_LAW_SENTINEL\\AnalyticsYaml\\${f}"))]
}
resource "azurerm_sentinel_alert_rule_scheduled" "test1" {
for_each = { for f in local.yaml_data : f.name => f }
name = each.value.name
log_analytics_workspace_id = azurerm_sentinel_log_analytics_workspace_onboarding.sentinel-terraform.workspace_id
display_name = each.value.name
severity = each.value.severity
description = each.value.description
tactics = each.value.tactics
query_frequency = each.value.queryFrequency
query = <<QUERY
${each.value.query}
QUERY
event_grouping {
aggregation_method = "AlertPerResult"
}
incident_configuration {
create_incident = "true"
grouping {
enabled = "true"
lookback_duration = "P1D"
entity_matching_method = "AllEntities"
}
}
}
Code without "query_frequency = each.value.queryFrequency" is working properly. Yaml sample:
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"
//extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type == "User"
| extend UserPrincipalName = extract(@'([a-f0-9]{32})?(.*)', 2, tostring(TargetResource.userPrincipalName))
)
| extend DeletedByUser = tostring(InitiatedBy.user.userPrincipalName), DeletedByIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend DeletedByApp = tostring(InitiatedBy.app.displayName)
| project Deletion_TimeGenerated = TimeGenerated, UserPrincipalName, DeletedByUser, DeletedByIPAddress, DeletedByApp, 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 UserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName))
)
| project-rename Creation_TimeGenerated = TimeGenerated
) on UserPrincipalName
| extend TimeDelta = Deletion_TimeGenerated - Creation_TimeGenerated
| where TimeDelta between (time(0s) .. queryperiod)
| extend CreatedByUser = tostring(InitiatedBy.user.userPrincipalName), CreatedByIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend CreatedByApp = tostring(InitiatedBy.app.displayName)
| project Creation_TimeGenerated, Deletion_TimeGenerated, TimeDelta, UserPrincipalName, DeletedByUser, DeletedByIPAddress, DeletedByApp, CreatedByUser, CreatedByIPAddress, CreatedByApp, Creation_AdditionalDetails = AdditionalDetails, Creation_InitiatedBy = InitiatedBy, Creation_TargetResources = TargetResources, Deletion_AdditionalDetails, Deletion_InitiatedBy, Deletion_TargetResources
| extend timestamp = Deletion_TimeGenerated, Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DeletedByIPAddress
version: 1.0.3
kind: Scheduled
Error: Unsupported attribute
│
│ on analytics_mass.tf line 16, in resource "azurerm_sentinel_alert_rule_scheduled" "test1":
│ 16: query_frequency = each.value.queryFrequency
│ ├────────────────
│ │ each.value is object with 11 attributes
│
│ This object does not have an attribute named "queryFrequency".
Thanks @Marko E for guiding in the right direction.
To add to this, I noticed some changes that need to be made in the YAML file, which cause an issue because Terraform is case-sensitive in attribute names. The attribute names in your YAML file do not match exactly those expected by the Terraform
azurerm_sentinel_alert_rule_scheduledresource configuration. In particular, the YAML file usesqueryFrequencyandqueryPeriod, but your Terraform code usesquery_frequencyandquery_period.This difference results in the error message you saw, which says that the object does not have an attribute named
queryFrequencybecause Terraform requires the precise attribute name as defined in the schema of the Terraform provider's resource.Using the Yaml file mentioned with some necessary changes case sensitive characters I was able to overcome the issues mentioned
My terraform configuration:
Output: