Terraform使用yamldecode加载YAML时无法访问queryFrequency属性问题
问题:Terraform批量加载Sentinel规则时无法访问YAML中的queryFrequency/queryPeriod字段
使用azurerm_sentinel_alert_rule_scheduled资源批量启用Sentinel分析规则,通过yamldecode加载本地YAML文件。其他字段如severity、description、tactics都能正常访问,但queryFrequency和queryPeriod字段报错提示不存在,移除对应配置后代码可正常运行。
相关Terraform代码
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" } } }
YAML示例文件
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".
解决方案
问题核心是部分YAML文件缺失queryFrequency/queryPeriod字段,或字段名存在大小写/拼写差异。Terraform遍历所有YAML解析对象时,只要有一个对象缺少目标字段就会触发报错。下面是两种可行的解决方式:
方式1:为缺失字段设置默认值
在本地变量yaml_data中,用merge函数为每个YAML对象补充默认值,YAML本身有该字段时会覆盖默认值:
locals { yaml_files = fileset("D:\\Terraform\\RG_LAW_SENTINEL\\AnalyticsYaml\\", "*.yaml") yaml_data = [for f in local.yaml_files : merge( yamldecode(file("D:\\Terraform\\RG_LAW_SENTINEL\\AnalyticsYaml\\${f}")), { queryFrequency = "1h" # 默认查询频率 queryPeriod = "1d" # 默认查询周期 } )] }
方式2:过滤缺少必要字段的YAML文件
如果不想为缺失字段设置默认值,可在for_each中只保留包含目标字段的对象:
resource "azurerm_sentinel_alert_rule_scheduled" "test1" { for_each = { for f in local.yaml_data : f.name => f if contains(keys(f), "queryFrequency") && contains(keys(f), "queryPeriod") } # 其余配置保持不变 }
额外检查
确认所有YAML文件中的字段名是queryFrequency和queryPeriod(注意大小写),避免出现全小写或拼写错误的情况。
内容的提问来源于stack exchange,提问作者Oskar Kazmierczyk
相关产品推荐
相关产品推荐

