From 384eb6653d3013ce60e18cf13ba3b0731415f581 Mon Sep 17 00:00:00 2001 From: ziyeqf Date: Tue, 31 Jan 2023 11:05:15 +0800 Subject: [PATCH] `azurerm_sentinel_alert_rule_nrt`, `azurerm_sentinel_alert_rule_scheduled`: support `sentinel_entity_mapping` --- .../services/sentinel/sentinel_alert_rule.go | 38 +++++++++++++++++++ .../sentinel_alert_rule_nrt_resource.go | 30 +++++++++++++++ .../sentinel_alert_rule_nrt_resource_test.go | 3 ++ .../sentinel_alert_rule_scheduled_resource.go | 30 +++++++++++++++ ...inel_alert_rule_scheduled_resource_test.go | 3 ++ .../r/sentinel_alert_rule_nrt.html.markdown | 10 +++++ ...entinel_alert_rule_scheduled.html.markdown | 10 +++++ 7 files changed, 124 insertions(+) diff --git a/internal/services/sentinel/sentinel_alert_rule.go b/internal/services/sentinel/sentinel_alert_rule.go index 272ba444b19b..369d8db0271d 100644 --- a/internal/services/sentinel/sentinel_alert_rule.go +++ b/internal/services/sentinel/sentinel_alert_rule.go @@ -386,3 +386,41 @@ func flattenAlertRuleFieldMapping(input *[]securityinsight.FieldMapping) []inter return output } + +func expandAlertRuleSentinelEntityMapping(input []interface{}) *[]securityinsight.SentinelEntityMapping { + if len(input) == 0 { + return nil + } + + result := make([]securityinsight.SentinelEntityMapping, 0) + + for _, e := range input { + b := e.(map[string]interface{}) + result = append(result, securityinsight.SentinelEntityMapping{ + ColumnName: utils.String(b["column_name"].(string)), + }) + } + + return &result +} + +func flattenAlertRuleSentinelEntityMapping(input *[]securityinsight.SentinelEntityMapping) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make([]interface{}, 0) + + for _, e := range *input { + var columnName string + if e.ColumnName != nil { + columnName = *e.ColumnName + } + + output = append(output, map[string]interface{}{ + "column_name": columnName, + }) + } + + return output +} diff --git a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go index eeeeb9a60498..f5d5022ee92e 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go @@ -311,6 +311,20 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { }, }, }, + "sentinel_entity_mapping": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 5, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "column_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, }, } } @@ -373,8 +387,21 @@ func resourceSentinelAlertRuleNrtCreateUpdate(d *pluginsdk.ResourceData, meta in if v, ok := d.GetOk("custom_details"); ok { param.NrtAlertRuleProperties.CustomDetails = utils.ExpandMapStringPtrString(v.(map[string]interface{})) } + + entityMappingCount := 0 + sentinelEntityMappingCount := 0 if v, ok := d.GetOk("entity_mapping"); ok { param.NrtAlertRuleProperties.EntityMappings = expandAlertRuleEntityMapping(v.([]interface{})) + entityMappingCount = len(*param.NrtAlertRuleProperties.EntityMappings) + } + if v, ok := d.GetOk("sentinel_entity_mapping"); ok { + param.NrtAlertRuleProperties.SentinelEntitiesMappings = expandAlertRuleSentinelEntityMapping(v.([]interface{})) + sentinelEntityMappingCount = len(*param.NrtAlertRuleProperties.SentinelEntitiesMappings) + } + + // the max number of `sentinel_entity_mapping` and `entity_mapping` together is 5 + if entityMappingCount+sentinelEntityMappingCount > 5 { + return fmt.Errorf("`entity_mapping` and `sentinel_entity_mapping` together can't exceed 5") } // Service avoid concurrent update of this resource via checking the "etag" to guarantee it is the same value as last Read. @@ -460,6 +487,9 @@ func resourceSentinelAlertRuleNrtRead(d *pluginsdk.ResourceData, meta interface{ if err := d.Set("entity_mapping", flattenAlertRuleEntityMapping(prop.EntityMappings)); err != nil { return fmt.Errorf("setting `entity_mapping`: %+v", err) } + if err := d.Set("sentinel_entity_mapping", flattenAlertRuleSentinelEntityMapping(prop.SentinelEntitiesMappings)); err != nil { + return fmt.Errorf("setting `sentinel_entity_mapping`: %+v", err) + } } return nil diff --git a/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go index e3d3244d7b7e..152b3aff12f6 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go @@ -184,6 +184,9 @@ resource "azurerm_sentinel_alert_rule_nrt" "test" { column_name = "Computer" } } + sentinel_entity_mapping { + column_name = "Category" + } entity_mapping { entity_type = "IP" field_mapping { diff --git a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go index 18a0fdea0977..a212e98345d3 100644 --- a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go @@ -372,6 +372,20 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { }, }, }, + "sentinel_entity_mapping": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 5, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "column_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, }, } } @@ -459,8 +473,21 @@ func resourceSentinelAlertRuleScheduledCreateUpdate(d *pluginsdk.ResourceData, m if v, ok := d.GetOk("custom_details"); ok { param.ScheduledAlertRuleProperties.CustomDetails = utils.ExpandMapStringPtrString(v.(map[string]interface{})) } + + entityMappingCount := 0 + sentinelEntityMappingCount := 0 if v, ok := d.GetOk("entity_mapping"); ok { param.ScheduledAlertRuleProperties.EntityMappings = expandAlertRuleEntityMapping(v.([]interface{})) + entityMappingCount = len(*param.ScheduledAlertRuleProperties.EntityMappings) + } + if v, ok := d.GetOk("sentinel_entity_mapping"); ok { + param.ScheduledAlertRuleProperties.SentinelEntitiesMappings = expandAlertRuleSentinelEntityMapping(v.([]interface{})) + sentinelEntityMappingCount = len(*param.ScheduledAlertRuleProperties.SentinelEntitiesMappings) + } + + // the max number of `sentinel_entity_mapping` and `entity_mapping` together is 5 + if entityMappingCount+sentinelEntityMappingCount > 5 { + return fmt.Errorf("`entity_mapping` and `sentinel_entity_mapping` together can't exceed 5") } if !d.IsNewResource() { @@ -556,6 +583,9 @@ func resourceSentinelAlertRuleScheduledRead(d *pluginsdk.ResourceData, meta inte if err := d.Set("entity_mapping", flattenAlertRuleEntityMapping(prop.EntityMappings)); err != nil { return fmt.Errorf("setting `entity_mapping`: %+v", err) } + if err := d.Set("sentinel_entity_mapping", flattenAlertRuleSentinelEntityMapping(prop.SentinelEntitiesMappings)); err != nil { + return fmt.Errorf("setting `sentinel_entity_mapping`: %+v", err) + } } return nil diff --git a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource_test.go b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource_test.go index 1305e632ae97..63b2b2116c9a 100644 --- a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource_test.go +++ b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource_test.go @@ -210,6 +210,9 @@ resource "azurerm_sentinel_alert_rule_scheduled" "test" { column_name = "Computer" } } + sentinel_entity_mapping { + column_name = "Category" + } entity_mapping { entity_type = "IP" field_mapping { diff --git a/website/docs/r/sentinel_alert_rule_nrt.html.markdown b/website/docs/r/sentinel_alert_rule_nrt.html.markdown index 4779e5b9015e..2e6afe3f1a3a 100644 --- a/website/docs/r/sentinel_alert_rule_nrt.html.markdown +++ b/website/docs/r/sentinel_alert_rule_nrt.html.markdown @@ -86,6 +86,10 @@ The following arguments are supported: * `entity_mapping` - (Optional) A list of `entity_mapping` blocks as defined below. +* `sentinel_entity_mapping` - (Optional) A list of `sentinel_entity_mapping` blocks as defined below. + +-> **NOTE:** `entity_mapping` and `sentinel_entity_mapping` together can't exceed 5. + * `incident` - (Optional) A `incident` block as defined below. * `suppression_duration` - (Optional) If `suppression_enabled` is `true`, this is ISO 8601 timespan duration, which specifies the amount of time the query should stop running after alert is generated. Defaults to `PT5H`. @@ -118,6 +122,12 @@ An `entity_mapping` block supports the following: --- +A `sentinel_entity_mapping` block supports the following: + +* `column_name` - (Required) The column name to be mapped to the identifier. + +--- + A `field_mapping` block supports the following: * `identifier` - (Required) The identifier of the entity. diff --git a/website/docs/r/sentinel_alert_rule_scheduled.html.markdown b/website/docs/r/sentinel_alert_rule_scheduled.html.markdown index d9675052a9bc..e6d40c5137f1 100644 --- a/website/docs/r/sentinel_alert_rule_scheduled.html.markdown +++ b/website/docs/r/sentinel_alert_rule_scheduled.html.markdown @@ -102,6 +102,10 @@ The following arguments are supported: * `suppression_enabled` - (Optional) Should the Sentinel Scheduled Alert Rulea stop running query after alert is generated? Defaults to `false`. +* `sentinel_entity_mapping` - (Optional) A list of `sentinel_entity_mapping` blocks as defined below. + +-> **NOTE:** `entity_mapping` and `sentinel_entity_mapping` together can't exceed 5. + * `tactics` - (Optional) A list of categories of attacks by which to classify the rule. Possible values are `Collection`, `CommandAndControl`, `CredentialAccess`, `DefenseEvasion`, `Discovery`, `Execution`, `Exfiltration`, `ImpairProcessControl`, `InhibitResponseFunction`, `Impact`, `InitialAccess`, `LateralMovement`, `Persistence`, `PrivilegeEscalation`, `PreAttack`, `Reconnaissance` and `ResourceDevelopment`. * `techniques` - (Optional) A list of techniques of attacks by which to classify the rule. @@ -132,6 +136,12 @@ An `entity_mapping` block supports the following: --- +A `sentinel_entity_mapping` block supports the following: + +* `column_name` - (Required) The column name to be mapped to the identifier. + +--- + A `event_grouping` block supports the following: * `aggregation_method` - (Required) The aggregation type of grouping the events. Possible values are `AlertPerResult` and `SingleAlert`.