From 26fcf8993da3e2f5d06126ec7d3f4759492028b6 Mon Sep 17 00:00:00 2001 From: magodo Date: Thu, 24 Mar 2022 15:44:14 +0800 Subject: [PATCH 1/5] New resource: `azurerm_sentinel_alert_rule_nrt` --- internal/services/sentinel/registration.go | 1 + .../services/sentinel/sentinel_alert_rule.go | 288 +++++++++++ .../sentinel_alert_rule_nrt_resource.go | 471 ++++++++++++++++++ .../sentinel_alert_rule_nrt_resource_test.go | 284 +++++++++++ .../sentinel_alert_rule_scheduled_resource.go | 299 +---------- ...entinel_alert_rule_template_data_source.go | 80 ++- ...el_alert_rule_template_data_source_test.go | 27 +- ...sentinel_alert_rule_template.html.markdown | 14 + .../r/sentinel_alert_rule_nrt.html.markdown | 172 +++++++ 9 files changed, 1343 insertions(+), 293 deletions(-) create mode 100644 internal/services/sentinel/sentinel_alert_rule_nrt_resource.go create mode 100644 internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go create mode 100644 website/docs/r/sentinel_alert_rule_nrt.html.markdown diff --git a/internal/services/sentinel/registration.go b/internal/services/sentinel/registration.go index e8399e8e4695..4bd36cacaf0e 100644 --- a/internal/services/sentinel/registration.go +++ b/internal/services/sentinel/registration.go @@ -40,6 +40,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_sentinel_alert_rule_machine_learning_behavior_analytics": resourceSentinelAlertRuleMLBehaviorAnalytics(), "azurerm_sentinel_alert_rule_ms_security_incident": resourceSentinelAlertRuleMsSecurityIncident(), "azurerm_sentinel_alert_rule_scheduled": resourceSentinelAlertRuleScheduled(), + "azurerm_sentinel_alert_rule_nrt": resourceSentinelAlertRuleNrt(), "azurerm_sentinel_data_connector_aws_cloud_trail": resourceSentinelDataConnectorAwsCloudTrail(), "azurerm_sentinel_data_connector_azure_active_directory": resourceSentinelDataConnectorAzureActiveDirectory(), "azurerm_sentinel_data_connector_azure_advanced_threat_protection": resourceSentinelDataConnectorAzureAdvancedThreatProtection(), diff --git a/internal/services/sentinel/sentinel_alert_rule.go b/internal/services/sentinel/sentinel_alert_rule.go index 0a899c3b856a..a872824ac21a 100644 --- a/internal/services/sentinel/sentinel_alert_rule.go +++ b/internal/services/sentinel/sentinel_alert_rule.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" ) func alertRuleID(rule securityinsight.BasicAlertRule) *string { @@ -23,6 +24,8 @@ func alertRuleID(rule securityinsight.BasicAlertRule) *string { return rule.ID case securityinsight.MLBehaviorAnalyticsAlertRule: return rule.ID + case securityinsight.NrtAlertRule: + return rule.ID default: return nil } @@ -59,9 +62,294 @@ func assertAlertRuleKind(rule securityinsight.BasicAlertRule, expectKind securit kind = securityinsight.AlertRuleKindMicrosoftSecurityIncidentCreation case securityinsight.ScheduledAlertRule: kind = securityinsight.AlertRuleKindScheduled + case securityinsight.NrtAlertRule: + kind = securityinsight.AlertRuleKindNRT } if expectKind != kind { return fmt.Errorf("Sentinel Alert Rule has mismatched kind, expected: %q, got %q", expectKind, kind) } return nil } + +func expandAlertRuleTactics(input []interface{}) *[]securityinsight.AttackTactic { + result := make([]securityinsight.AttackTactic, 0) + + for _, e := range input { + result = append(result, securityinsight.AttackTactic(e.(string))) + } + + return &result +} + +func flattenAlertRuleTactics(input *[]securityinsight.AttackTactic) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make([]interface{}, 0) + + for _, e := range *input { + output = append(output, string(e)) + } + + return output +} + +func expandAlertRuleIncidentConfiguration(input []interface{}) *securityinsight.IncidentConfiguration { + if len(input) == 0 || input[0] == nil { + return nil + } + + raw := input[0].(map[string]interface{}) + + output := &securityinsight.IncidentConfiguration{ + CreateIncident: utils.Bool(raw["create_incident"].(bool)), + GroupingConfiguration: expandAlertRuleGrouping(raw["grouping"].([]interface{})), + } + + return output +} + +func flattenAlertRuleIncidentConfiguration(input *securityinsight.IncidentConfiguration) []interface{} { + if input == nil { + return []interface{}{} + } + + createIncident := false + if input.CreateIncident != nil { + createIncident = *input.CreateIncident + } + + return []interface{}{ + map[string]interface{}{ + "create_incident": createIncident, + "grouping": flattenAlertRuleGrouping(input.GroupingConfiguration), + }, + } +} + +func expandAlertRuleGrouping(input []interface{}) *securityinsight.GroupingConfiguration { + if len(input) == 0 || input[0] == nil { + return nil + } + + raw := input[0].(map[string]interface{}) + + output := &securityinsight.GroupingConfiguration{ + Enabled: utils.Bool(raw["enabled"].(bool)), + ReopenClosedIncident: utils.Bool(raw["reopen_closed_incidents"].(bool)), + LookbackDuration: utils.String(raw["lookback_duration"].(string)), + MatchingMethod: securityinsight.MatchingMethod(raw["entity_matching_method"].(string)), + } + + groupByEntitiesList := raw["group_by_entities"].([]interface{}) + groupByEntities := make([]securityinsight.EntityMappingType, len(groupByEntitiesList)) + for idx, t := range groupByEntitiesList { + groupByEntities[idx] = securityinsight.EntityMappingType(t.(string)) + } + output.GroupByEntities = &groupByEntities + + groupByAlertDetailsList := raw["group_by_alert_details"].([]interface{}) + groupByAlertDetails := make([]securityinsight.AlertDetail, len(groupByAlertDetailsList)) + for idx, t := range groupByAlertDetailsList { + groupByAlertDetails[idx] = securityinsight.AlertDetail(t.(string)) + } + output.GroupByAlertDetails = &groupByAlertDetails + + output.GroupByCustomDetails = utils.ExpandStringSlice(raw["group_by_custom_details"].([]interface{})) + + return output +} + +func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration) []interface{} { + if input == nil { + return []interface{}{} + } + + enabled := false + if input.Enabled != nil { + enabled = *input.Enabled + } + + lookbackDuration := "" + if input.LookbackDuration != nil { + lookbackDuration = *input.LookbackDuration + } + + reopenClosedIncidents := false + if input.ReopenClosedIncident != nil { + reopenClosedIncidents = *input.ReopenClosedIncident + } + + var groupByEntities []interface{} + if input.GroupByEntities != nil { + for _, entity := range *input.GroupByEntities { + groupByEntities = append(groupByEntities, string(entity)) + } + } + + var groupByAlertDetails []interface{} + if input.GroupByAlertDetails != nil { + for _, detail := range *input.GroupByAlertDetails { + groupByAlertDetails = append(groupByAlertDetails, string(detail)) + } + } + + var groupByCustomDetails []interface{} + if input.GroupByCustomDetails != nil { + for _, detail := range *input.GroupByCustomDetails { + groupByCustomDetails = append(groupByCustomDetails, detail) + } + } + + return []interface{}{ + map[string]interface{}{ + "enabled": enabled, + "lookback_duration": lookbackDuration, + "reopen_closed_incidents": reopenClosedIncidents, + "entity_matching_method": string(input.MatchingMethod), + "group_by_entities": groupByEntities, + "group_by_alert_details": groupByAlertDetails, + "group_by_custom_details": groupByCustomDetails, + }, + } +} + +func expandAlertRuleAlertDetailsOverride(input []interface{}) *securityinsight.AlertDetailsOverride { + if len(input) == 0 || input[0] == nil { + return nil + } + + b := input[0].(map[string]interface{}) + output := &securityinsight.AlertDetailsOverride{} + + if v := b["description_format"]; v != "" { + output.AlertDescriptionFormat = utils.String(v.(string)) + } + if v := b["display_name_format"]; v != "" { + output.AlertDisplayNameFormat = utils.String(v.(string)) + } + if v := b["severity_column_name"]; v != "" { + output.AlertSeverityColumnName = utils.String(v.(string)) + } + if v := b["tactics_column_name"]; v != "" { + output.AlertTacticsColumnName = utils.String(v.(string)) + } + + return output +} + +func flattenAlertRuleAlertDetailsOverride(input *securityinsight.AlertDetailsOverride) []interface{} { + if input == nil { + return []interface{}{} + } + + var descriptionFormat string + if input.AlertDescriptionFormat != nil { + descriptionFormat = *input.AlertDescriptionFormat + } + + var displayNameFormat string + if input.AlertDisplayNameFormat != nil { + displayNameFormat = *input.AlertDisplayNameFormat + } + + var severityColumnName string + if input.AlertSeverityColumnName != nil { + severityColumnName = *input.AlertSeverityColumnName + } + + var tacticsColumnName string + if input.AlertTacticsColumnName != nil { + tacticsColumnName = *input.AlertTacticsColumnName + } + + return []interface{}{ + map[string]interface{}{ + "description_format": descriptionFormat, + "display_name_format": displayNameFormat, + "severity_column_name": severityColumnName, + "tactics_column_name": tacticsColumnName, + }, + } +} + +func expandAlertRuleEntityMapping(input []interface{}) *[]securityinsight.EntityMapping { + if len(input) == 0 { + return nil + } + + result := make([]securityinsight.EntityMapping, 0) + + for _, e := range input { + b := e.(map[string]interface{}) + result = append(result, securityinsight.EntityMapping{ + EntityType: securityinsight.EntityMappingType(b["entity_type"].(string)), + FieldMappings: expandAlertRuleFieldMapping(b["field_mapping"].([]interface{})), + }) + } + + return &result +} + +func flattenAlertRuleEntityMapping(input *[]securityinsight.EntityMapping) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make([]interface{}, 0) + + for _, e := range *input { + output = append(output, map[string]interface{}{ + "entity_type": string(e.EntityType), + "field_mapping": flattenAlertRuleFieldMapping(e.FieldMappings), + }) + } + + return output +} + +func expandAlertRuleFieldMapping(input []interface{}) *[]securityinsight.FieldMapping { + if len(input) == 0 { + return nil + } + + result := make([]securityinsight.FieldMapping, 0) + + for _, e := range input { + b := e.(map[string]interface{}) + result = append(result, securityinsight.FieldMapping{ + Identifier: utils.String(b["identifier"].(string)), + ColumnName: utils.String(b["column_name"].(string)), + }) + } + + return &result +} + +func flattenAlertRuleFieldMapping(input *[]securityinsight.FieldMapping) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make([]interface{}, 0) + + for _, e := range *input { + var identifier string + if e.Identifier != nil { + identifier = *e.Identifier + } + + var columnName string + if e.ColumnName != nil { + columnName = *e.ColumnName + } + + output = append(output, map[string]interface{}{ + "identifier": identifier, + "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 new file mode 100644 index 000000000000..4987bd1ddf8b --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go @@ -0,0 +1,471 @@ +package sentinel + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2021-09-01-preview/securityinsight" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/helpers/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + loganalyticsParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/loganalytics/parse" + loganalyticsValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/loganalytics/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { + var entityMappingTypes = []string{ + string(securityinsight.EntityMappingTypeAccount), + string(securityinsight.EntityMappingTypeAzureResource), + string(securityinsight.EntityMappingTypeCloudApplication), + string(securityinsight.EntityMappingTypeDNS), + string(securityinsight.EntityMappingTypeFile), + string(securityinsight.EntityMappingTypeFileHash), + string(securityinsight.EntityMappingTypeHost), + string(securityinsight.EntityMappingTypeIP), + string(securityinsight.EntityMappingTypeMailbox), + string(securityinsight.EntityMappingTypeMailCluster), + string(securityinsight.EntityMappingTypeMailMessage), + string(securityinsight.EntityMappingTypeMalware), + string(securityinsight.EntityMappingTypeProcess), + string(securityinsight.EntityMappingTypeRegistryKey), + string(securityinsight.EntityMappingTypeRegistryValue), + string(securityinsight.EntityMappingTypeSecurityGroup), + string(securityinsight.EntityMappingTypeSubmissionMail), + string(securityinsight.EntityMappingTypeURL), + } + return &pluginsdk.Resource{ + Create: resourceSentinelAlertRuleNrtCreateUpdate, + Read: resourceSentinelAlertRuleNrtRead, + Update: resourceSentinelAlertRuleNrtCreateUpdate, + Delete: resourceSentinelAlertRuleNrtDelete, + + Importer: pluginsdk.ImporterValidatingResourceIdThen(func(id string) error { + _, err := parse.AlertRuleID(id) + return err + }, importSentinelAlertRule(securityinsight.AlertRuleKindNRT)), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: loganalyticsValidate.LogAnalyticsWorkspaceID, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "alert_rule_template_guid": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + + "alert_rule_template_version": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tactics": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.AttackTacticCollection), + string(securityinsight.AttackTacticCommandAndControl), + string(securityinsight.AttackTacticCredentialAccess), + string(securityinsight.AttackTacticDefenseEvasion), + string(securityinsight.AttackTacticDiscovery), + string(securityinsight.AttackTacticExecution), + string(securityinsight.AttackTacticExfiltration), + string(securityinsight.AttackTacticImpact), + string(securityinsight.AttackTacticInitialAccess), + string(securityinsight.AttackTacticLateralMovement), + string(securityinsight.AttackTacticPersistence), + string(securityinsight.AttackTacticPrivilegeEscalation), + string(securityinsight.AttackTacticPreAttack), + }, false), + }, + }, + + "incident_configuration": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + MinItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "create_incident": { + Required: true, + Type: pluginsdk.TypeBool, + }, + "grouping": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + MinItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + "lookback_duration": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validate.ISO8601Duration, + Default: "PT5M", + }, + "reopen_closed_incidents": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + "entity_matching_method": { + Type: pluginsdk.TypeString, + Optional: true, + Default: securityinsight.MatchingMethodAnyAlert, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.MatchingMethodAnyAlert), + string(securityinsight.MatchingMethodSelected), + string(securityinsight.MatchingMethodAllEntities), + }, false), + }, + "group_by_entities": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice(entityMappingTypes, false), + }, + }, + "group_by_alert_details": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.AlertDetailDisplayName), + string(securityinsight.AlertDetailSeverity), + }, + false), + }, + }, + "group_by_custom_details": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + }, + }, + + "severity": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.AlertSeverityHigh), + string(securityinsight.AlertSeverityMedium), + string(securityinsight.AlertSeverityLow), + string(securityinsight.AlertSeverityInformational), + }, false), + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "query": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "suppression_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + "suppression_duration": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "PT5H", + ValidateFunc: validate.ISO8601DurationBetween("PT5M", "PT24H"), + }, + "alert_details_override": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description_format": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "display_name_format": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "severity_column_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "tactics_column_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + "custom_details": { + Type: pluginsdk.TypeMap, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + "entity_mapping": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 5, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "entity_type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(entityMappingTypes, false), + }, + "field_mapping": { + Type: pluginsdk.TypeList, + MaxItems: 3, + Required: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "identifier": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "column_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceSentinelAlertRuleNrtCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + workspaceID, err := loganalyticsParse.LogAnalyticsWorkspaceID(d.Get("log_analytics_workspace_id").(string)) + if err != nil { + return err + } + id := parse.NewAlertRuleID(workspaceID.SubscriptionId, workspaceID.ResourceGroup, workspaceID.WorkspaceName, name) + + if d.IsNewResource() { + resp, err := client.Get(ctx, workspaceID.ResourceGroup, workspaceID.WorkspaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("checking for existing %q: %+v", id, err) + } + } + + id := alertRuleID(resp.Value) + if id != nil && *id != "" { + return tf.ImportAsExistsError("azurerm_sentinel_alert_rule_nrt", *id) + } + } + + // query frequency must <= suppression duration: otherwise suppression has no effect. + suppressionDuration := d.Get("suppression_duration").(string) + suppressionEnabled := d.Get("suppression_enabled").(bool) + + param := securityinsight.NrtAlertRule{ + Kind: securityinsight.KindBasicAlertRuleKindNRT, + NrtAlertRuleProperties: &securityinsight.NrtAlertRuleProperties{ + Description: utils.String(d.Get("description").(string)), + DisplayName: utils.String(d.Get("display_name").(string)), + Tactics: expandAlertRuleTactics(d.Get("tactics").(*pluginsdk.Set).List()), + IncidentConfiguration: expandAlertRuleIncidentConfiguration(d.Get("incident_configuration").([]interface{})), + Severity: securityinsight.AlertSeverity(d.Get("severity").(string)), + Enabled: utils.Bool(d.Get("enabled").(bool)), + Query: utils.String(d.Get("query").(string)), + SuppressionEnabled: &suppressionEnabled, + SuppressionDuration: &suppressionDuration, + }, + } + + if v, ok := d.GetOk("alert_rule_template_guid"); ok { + param.NrtAlertRuleProperties.AlertRuleTemplateName = utils.String(v.(string)) + } + if v, ok := d.GetOk("alert_rule_template_version"); ok { + param.NrtAlertRuleProperties.TemplateVersion = utils.String(v.(string)) + } + if v, ok := d.GetOk("alert_details_override"); ok { + param.NrtAlertRuleProperties.AlertDetailsOverride = expandAlertRuleAlertDetailsOverride(v.([]interface{})) + } + if v, ok := d.GetOk("custom_details"); ok { + param.NrtAlertRuleProperties.CustomDetails = utils.ExpandMapStringPtrString(v.(map[string]interface{})) + } + if v, ok := d.GetOk("entity_mapping"); ok { + param.NrtAlertRuleProperties.EntityMappings = expandAlertRuleEntityMapping(v.([]interface{})) + } + + // Service avoid concurrent update of this resource via checking the "etag" to guarantee it is the same value as last Read. + if !d.IsNewResource() { + resp, err := client.Get(ctx, workspaceID.ResourceGroup, workspaceID.WorkspaceName, name) + if err != nil { + return fmt.Errorf("retrieving %q: %+v", id, err) + } + + if err := assertAlertRuleKind(resp.Value, securityinsight.AlertRuleKindNRT); err != nil { + return fmt.Errorf("asserting %q: %+v", id, err) + } + param.Etag = resp.Value.(securityinsight.NrtAlertRule).Etag + } + + if _, err := client.CreateOrUpdate(ctx, workspaceID.ResourceGroup, workspaceID.WorkspaceName, name, param); err != nil { + return fmt.Errorf("creating %q: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceSentinelAlertRuleNrtRead(d, meta) +} + +func resourceSentinelAlertRuleNrtRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AlertRuleID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] %q was not found - removing from state!", id) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %q: %+v", id, err) + } + + if err := assertAlertRuleKind(resp.Value, securityinsight.AlertRuleKindNRT); err != nil { + return fmt.Errorf("asserting %q: %+v", id, err) + } + rule := resp.Value.(securityinsight.NrtAlertRule) + + d.Set("name", id.Name) + + workspaceId := loganalyticsParse.NewLogAnalyticsWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + d.Set("log_analytics_workspace_id", workspaceId.ID()) + + if prop := rule.NrtAlertRuleProperties; prop != nil { + d.Set("description", prop.Description) + d.Set("display_name", prop.DisplayName) + if err := d.Set("tactics", flattenAlertRuleTactics(prop.Tactics)); err != nil { + return fmt.Errorf("setting `tactics`: %+v", err) + } + if err := d.Set("incident_configuration", flattenAlertRuleIncidentConfiguration(prop.IncidentConfiguration)); err != nil { + return fmt.Errorf("setting `incident_configuration`: %+v", err) + } + d.Set("severity", string(prop.Severity)) + d.Set("enabled", prop.Enabled) + d.Set("query", prop.Query) + + d.Set("suppression_enabled", prop.SuppressionEnabled) + d.Set("suppression_duration", prop.SuppressionDuration) + d.Set("alert_rule_template_guid", prop.AlertRuleTemplateName) + d.Set("alert_rule_template_version", prop.TemplateVersion) + + if err := d.Set("alert_details_override", flattenAlertRuleAlertDetailsOverride(prop.AlertDetailsOverride)); err != nil { + return fmt.Errorf("setting `alert_details_override`: %+v", err) + } + if err := d.Set("custom_details", utils.FlattenMapStringPtrString(prop.CustomDetails)); err != nil { + return fmt.Errorf("setting `custom_details`: %+v", err) + } + if err := d.Set("entity_mapping", flattenAlertRuleEntityMapping(prop.EntityMappings)); err != nil { + return fmt.Errorf("setting `entity_mapping`: %+v", err) + } + } + + return nil +} + +func resourceSentinelAlertRuleNrtDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.AlertRulesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AlertRuleID(d.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.Name); err != nil { + return fmt.Errorf("deleting Sentinel Alert Rule Nrt %q: %+v", id, 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 new file mode 100644 index 000000000000..e52139c428a5 --- /dev/null +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go @@ -0,0 +1,284 @@ +package sentinel_test + +import ( + "context" + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2021-09-01-preview/securityinsight" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type SentinelAlertRuleNrtResource struct{} + +func TestAccSentinelAlertRuleNrt_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_nrt", "test") + r := SentinelAlertRuleNrtResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSentinelAlertRuleNrt_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_nrt", "test") + r := SentinelAlertRuleNrtResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSentinelAlertRuleNrt_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_nrt", "test") + r := SentinelAlertRuleNrtResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.completeUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSentinelAlertRuleNrt_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_nrt", "test") + r := SentinelAlertRuleNrtResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccSentinelAlertRuleNrt_withAlertRuleTemplateGuid(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_nrt", "test") + r := SentinelAlertRuleNrtResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.alertRuleTemplateGuid(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (t SentinelAlertRuleNrtResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.AlertRuleID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Sentinel.AlertRulesClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + return nil, fmt.Errorf("reading %q: %v", id, err) + } + + rule, ok := resp.Value.(securityinsight.NrtAlertRule) + if !ok { + return nil, fmt.Errorf("the Alert Rule %q is not a NRT Alert Rule", id) + } + + return utils.Bool(rule.ID != nil), nil +} + +func (r SentinelAlertRuleNrtResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_alert_rule_nrt" "test" { + name = "acctest-SentinelAlertRule-NRT-%d" + log_analytics_workspace_id = azurerm_log_analytics_solution.test.workspace_resource_id + display_name = "Some Rule" + severity = "High" + query = < Date: Wed, 1 Jun 2022 10:15:05 +0800 Subject: [PATCH 2/5] rename properties --- .../services/sentinel/sentinel_alert_rule.go | 51 ++++++++++++++----- .../sentinel_alert_rule_nrt_resource.go | 16 +++--- .../sentinel_alert_rule_nrt_resource_test.go | 10 ++-- .../sentinel_alert_rule_scheduled_resource.go | 9 +++- .../r/sentinel_alert_rule_nrt.html.markdown | 12 ++--- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/internal/services/sentinel/sentinel_alert_rule.go b/internal/services/sentinel/sentinel_alert_rule.go index a872824ac21a..6ae12a29927a 100644 --- a/internal/services/sentinel/sentinel_alert_rule.go +++ b/internal/services/sentinel/sentinel_alert_rule.go @@ -95,7 +95,7 @@ func flattenAlertRuleTactics(input *[]securityinsight.AttackTactic) []interface{ return output } -func expandAlertRuleIncidentConfiguration(input []interface{}) *securityinsight.IncidentConfiguration { +func expandAlertRuleIncidentConfiguration(input []interface{}, createIncidentKey string, withGroupByPrefix bool) *securityinsight.IncidentConfiguration { if len(input) == 0 || input[0] == nil { return nil } @@ -103,14 +103,14 @@ func expandAlertRuleIncidentConfiguration(input []interface{}) *securityinsight. raw := input[0].(map[string]interface{}) output := &securityinsight.IncidentConfiguration{ - CreateIncident: utils.Bool(raw["create_incident"].(bool)), - GroupingConfiguration: expandAlertRuleGrouping(raw["grouping"].([]interface{})), + CreateIncident: utils.Bool(raw[createIncidentKey].(bool)), + GroupingConfiguration: expandAlertRuleGrouping(raw["grouping"].([]interface{}), withGroupByPrefix), } return output } -func flattenAlertRuleIncidentConfiguration(input *securityinsight.IncidentConfiguration) []interface{} { +func flattenAlertRuleIncidentConfiguration(input *securityinsight.IncidentConfiguration, createIncidentKey string, withGroupByPrefix bool) []interface{} { if input == nil { return []interface{}{} } @@ -122,13 +122,13 @@ func flattenAlertRuleIncidentConfiguration(input *securityinsight.IncidentConfig return []interface{}{ map[string]interface{}{ - "create_incident": createIncident, - "grouping": flattenAlertRuleGrouping(input.GroupingConfiguration), + createIncidentKey: createIncident, + "grouping": flattenAlertRuleGrouping(input.GroupingConfiguration, withGroupByPrefix), }, } } -func expandAlertRuleGrouping(input []interface{}) *securityinsight.GroupingConfiguration { +func expandAlertRuleGrouping(input []interface{}, withGroupByPrefix bool) *securityinsight.GroupingConfiguration { if len(input) == 0 || input[0] == nil { return nil } @@ -142,26 +142,38 @@ func expandAlertRuleGrouping(input []interface{}) *securityinsight.GroupingConfi MatchingMethod: securityinsight.MatchingMethod(raw["entity_matching_method"].(string)), } - groupByEntitiesList := raw["group_by_entities"].([]interface{}) + key := "entities" + if withGroupByPrefix { + key = "group_by_" + key + } + groupByEntitiesList := raw[key].([]interface{}) groupByEntities := make([]securityinsight.EntityMappingType, len(groupByEntitiesList)) for idx, t := range groupByEntitiesList { groupByEntities[idx] = securityinsight.EntityMappingType(t.(string)) } output.GroupByEntities = &groupByEntities - groupByAlertDetailsList := raw["group_by_alert_details"].([]interface{}) + key = "alert_details" + if withGroupByPrefix { + key = "group_by_" + key + } + groupByAlertDetailsList := raw[key].([]interface{}) groupByAlertDetails := make([]securityinsight.AlertDetail, len(groupByAlertDetailsList)) for idx, t := range groupByAlertDetailsList { groupByAlertDetails[idx] = securityinsight.AlertDetail(t.(string)) } output.GroupByAlertDetails = &groupByAlertDetails - output.GroupByCustomDetails = utils.ExpandStringSlice(raw["group_by_custom_details"].([]interface{})) + key = "custom_details" + if withGroupByPrefix { + key = "group_by_" + key + } + output.GroupByCustomDetails = utils.ExpandStringSlice(raw[key].([]interface{})) return output } -func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration) []interface{} { +func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration, withGroupByPrefix bool) []interface{} { if input == nil { return []interface{}{} } @@ -202,15 +214,26 @@ func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration) []in } } + var ( + k1 = "entities" + k2 = "alert_details" + k3 = "custom_details" + ) + + if withGroupByPrefix { + k1 = "group_by_" + k1 + k2 = "group_by_" + k2 + k3 = "group_by_" + k3 + } return []interface{}{ map[string]interface{}{ "enabled": enabled, "lookback_duration": lookbackDuration, "reopen_closed_incidents": reopenClosedIncidents, "entity_matching_method": string(input.MatchingMethod), - "group_by_entities": groupByEntities, - "group_by_alert_details": groupByAlertDetails, - "group_by_custom_details": groupByCustomDetails, + k1: groupByEntities, + k2: groupByAlertDetails, + k3: groupByCustomDetails, }, } } diff --git a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go index 4987bd1ddf8b..def65d670af4 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go @@ -120,7 +120,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { }, }, - "incident_configuration": { + "incident": { Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -128,7 +128,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { MinItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "create_incident": { + "create_incident_enabled": { Required: true, Type: pluginsdk.TypeBool, }, @@ -165,7 +165,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { string(securityinsight.MatchingMethodAllEntities), }, false), }, - "group_by_entities": { + "entities": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ @@ -173,7 +173,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { ValidateFunc: validation.StringInSlice(entityMappingTypes, false), }, }, - "group_by_alert_details": { + "alert_details": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ @@ -185,7 +185,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { false), }, }, - "group_by_custom_details": { + "custom_details": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ @@ -343,7 +343,7 @@ func resourceSentinelAlertRuleNrtCreateUpdate(d *pluginsdk.ResourceData, meta in Description: utils.String(d.Get("description").(string)), DisplayName: utils.String(d.Get("display_name").(string)), Tactics: expandAlertRuleTactics(d.Get("tactics").(*pluginsdk.Set).List()), - IncidentConfiguration: expandAlertRuleIncidentConfiguration(d.Get("incident_configuration").([]interface{})), + IncidentConfiguration: expandAlertRuleIncidentConfiguration(d.Get("incident").([]interface{}), "create_incident_enabled", false), Severity: securityinsight.AlertSeverity(d.Get("severity").(string)), Enabled: utils.Bool(d.Get("enabled").(bool)), Query: utils.String(d.Get("query").(string)), @@ -427,8 +427,8 @@ func resourceSentinelAlertRuleNrtRead(d *pluginsdk.ResourceData, meta interface{ if err := d.Set("tactics", flattenAlertRuleTactics(prop.Tactics)); err != nil { return fmt.Errorf("setting `tactics`: %+v", err) } - if err := d.Set("incident_configuration", flattenAlertRuleIncidentConfiguration(prop.IncidentConfiguration)); err != nil { - return fmt.Errorf("setting `incident_configuration`: %+v", err) + if err := d.Set("incident", flattenAlertRuleIncidentConfiguration(prop.IncidentConfiguration, "create_incident_enabled", false)); err != nil { + return fmt.Errorf("setting `incident`: %+v", err) } d.Set("severity", string(prop.Severity)) d.Set("enabled", prop.Enabled) 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 e52139c428a5..1cb7976f5183 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go @@ -155,16 +155,16 @@ resource "azurerm_sentinel_alert_rule_nrt" "test" { tactics = ["Collection", "CommandAndControl"] severity = "Low" enabled = false - incident_configuration { - create_incident = true + incident { + create_incident_enabled = true grouping { enabled = true lookback_duration = "P7D" reopen_closed_incidents = true entity_matching_method = "Selected" - group_by_entities = ["Host"] - group_by_alert_details = ["DisplayName"] - group_by_custom_details = ["OperatingSystemType", "OperatingSystemName"] + entities = ["Host"] + alert_details = ["DisplayName"] + custom_details = ["OperatingSystemType", "OperatingSystemName"] } } query = "Heartbeat" diff --git a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go index aefc50d5b6a1..3ae660259e8d 100644 --- a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go @@ -139,6 +139,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { }, }, + // TODO 4.0 - rename this to "incident" "incident_configuration": { Type: pluginsdk.TypeList, Optional: true, @@ -147,6 +148,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { MinItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + // TODO 4.0 - rename this to "create_incident_enabled" "create_incident": { Required: true, Type: pluginsdk.TypeBool, @@ -184,6 +186,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { string(securityinsight.MatchingMethodAllEntities), }, false), }, + // TODO 4.0 - rename this to "entities" "group_by_entities": { Type: pluginsdk.TypeList, Optional: true, @@ -192,6 +195,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { ValidateFunc: validation.StringInSlice(entityMappingTypes, false), }, }, + // TODO 4.0 - rename this to "alert_details" "group_by_alert_details": { Type: pluginsdk.TypeList, Optional: true, @@ -204,6 +208,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { false), }, }, + // TODO 4.0 - rename this to "custom_details" "group_by_custom_details": { Type: pluginsdk.TypeList, Optional: true, @@ -413,7 +418,7 @@ func resourceSentinelAlertRuleScheduledCreateUpdate(d *pluginsdk.ResourceData, m Description: utils.String(d.Get("description").(string)), DisplayName: utils.String(d.Get("display_name").(string)), Tactics: expandAlertRuleTactics(d.Get("tactics").(*pluginsdk.Set).List()), - IncidentConfiguration: expandAlertRuleIncidentConfiguration(d.Get("incident_configuration").([]interface{})), + IncidentConfiguration: expandAlertRuleIncidentConfiguration(d.Get("incident_configuration").([]interface{}), "create_incident", true), Severity: securityinsight.AlertSeverity(d.Get("severity").(string)), Enabled: utils.Bool(d.Get("enabled").(bool)), Query: utils.String(d.Get("query").(string)), @@ -504,7 +509,7 @@ func resourceSentinelAlertRuleScheduledRead(d *pluginsdk.ResourceData, meta inte if err := d.Set("tactics", flattenAlertRuleTactics(prop.Tactics)); err != nil { return fmt.Errorf("setting `tactics`: %+v", err) } - if err := d.Set("incident_configuration", flattenAlertRuleIncidentConfiguration(prop.IncidentConfiguration)); err != nil { + if err := d.Set("incident_configuration", flattenAlertRuleIncidentConfiguration(prop.IncidentConfiguration, "create_incident", true)); err != nil { return fmt.Errorf("setting `incident_configuration`: %+v", err) } d.Set("severity", string(prop.Severity)) diff --git a/website/docs/r/sentinel_alert_rule_nrt.html.markdown b/website/docs/r/sentinel_alert_rule_nrt.html.markdown index 68594efd1812..585adfa2099b 100644 --- a/website/docs/r/sentinel_alert_rule_nrt.html.markdown +++ b/website/docs/r/sentinel_alert_rule_nrt.html.markdown @@ -86,7 +86,7 @@ The following arguments are supported: * `entity_mapping` - (Optional) A list of `entity_mapping` blocks as defined below. -* `incident_configuration` - (Optional) A `incident_configuration` block as defined below. +* `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`. @@ -124,9 +124,9 @@ A `field_mapping` block supports the following: --- -A `incident_configuration` block supports the following: +A `incident` block supports the following: -* `create_incident` - (Required) Whether to create an incident from alerts triggered by this Sentinel NRT Alert Rule? +* `create_incident_enabled` - (Required) Whether to create an incident from alerts triggered by this Sentinel NRT Alert Rule? * `grouping` - (Optional) A `grouping` block as defined below. @@ -142,11 +142,11 @@ A `grouping` block supports the following: * `entity_matching_method` - (Optional) The method used to group incidents. Possible values are `AnyAlert`, `Selected` and `AllEntities`. Defaults to `AnyAlert`. -* `group_by_entities` - (Optional) A list of entity types to group by, only when the `entity_matching_method` is `Selected`. Possible values are `Account`, `AzureResource`, `CloudApplication`, `DNS`, `File`, `FileHash`, `Host`, `IP`, `Mailbox`, `MailCluster`, `MailMessage`, `Malware`, `Process`, `RegistryKey`, `RegistryValue`, `SecurityGroup`, `SubmissionMail`, `URL`. +* `entities` - (Optional) A list of entity types to group by, only when the `entity_matching_method` is `Selected`. Possible values are `Account`, `AzureResource`, `CloudApplication`, `DNS`, `File`, `FileHash`, `Host`, `IP`, `Mailbox`, `MailCluster`, `MailMessage`, `Malware`, `Process`, `RegistryKey`, `RegistryValue`, `SecurityGroup`, `SubmissionMail`, `URL`. -* `gorup_by_alert_details` - (Optional) A list of alert details to group by, only when the `entity_matching_method` is `Selected`. +* `alert_details` - (Optional) A list of alert details to group by, only when the `entity_matching_method` is `Selected`. -* `gorup_by_custom_details` - (Optional) A list of custom details keys to group by, only when the `entity_matching_method` is `Selected`. Only keys defined in the `custom_details` may be used. +* `custom_details` - (Optional) A list of custom details keys to group by, only when the `entity_matching_method` is `Selected`. Only keys defined in the `custom_details` may be used. ## Attributes Reference From 939f59198ea100964e57b1dfb1a104460cf50733 Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 6 Jul 2022 10:15:57 +0800 Subject: [PATCH 3/5] Rename --- .../services/sentinel/sentinel_alert_rule.go | 36 +++++++++---------- .../sentinel_alert_rule_nrt_resource.go | 6 ++-- .../sentinel_alert_rule_scheduled_resource.go | 6 ++-- .../r/sentinel_alert_rule_nrt.html.markdown | 6 ++-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/internal/services/sentinel/sentinel_alert_rule.go b/internal/services/sentinel/sentinel_alert_rule.go index 6ae12a29927a..f53b28a255c1 100644 --- a/internal/services/sentinel/sentinel_alert_rule.go +++ b/internal/services/sentinel/sentinel_alert_rule.go @@ -128,7 +128,7 @@ func flattenAlertRuleIncidentConfiguration(input *securityinsight.IncidentConfig } } -func expandAlertRuleGrouping(input []interface{}, withGroupByPrefix bool) *securityinsight.GroupingConfiguration { +func expandAlertRuleGrouping(input []interface{}, withGroupPrefix bool) *securityinsight.GroupingConfiguration { if len(input) == 0 || input[0] == nil { return nil } @@ -142,9 +142,9 @@ func expandAlertRuleGrouping(input []interface{}, withGroupByPrefix bool) *secur MatchingMethod: securityinsight.MatchingMethod(raw["entity_matching_method"].(string)), } - key := "entities" - if withGroupByPrefix { - key = "group_by_" + key + key := "by_entities" + if withGroupPrefix { + key = "group_" + key } groupByEntitiesList := raw[key].([]interface{}) groupByEntities := make([]securityinsight.EntityMappingType, len(groupByEntitiesList)) @@ -153,9 +153,9 @@ func expandAlertRuleGrouping(input []interface{}, withGroupByPrefix bool) *secur } output.GroupByEntities = &groupByEntities - key = "alert_details" - if withGroupByPrefix { - key = "group_by_" + key + key = "by_alert_details" + if withGroupPrefix { + key = "group_" + key } groupByAlertDetailsList := raw[key].([]interface{}) groupByAlertDetails := make([]securityinsight.AlertDetail, len(groupByAlertDetailsList)) @@ -164,16 +164,16 @@ func expandAlertRuleGrouping(input []interface{}, withGroupByPrefix bool) *secur } output.GroupByAlertDetails = &groupByAlertDetails - key = "custom_details" - if withGroupByPrefix { - key = "group_by_" + key + key = "by_custom_details" + if withGroupPrefix { + key = "group_" + key } output.GroupByCustomDetails = utils.ExpandStringSlice(raw[key].([]interface{})) return output } -func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration, withGroupByPrefix bool) []interface{} { +func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration, withGroupPrefix bool) []interface{} { if input == nil { return []interface{}{} } @@ -215,15 +215,15 @@ func flattenAlertRuleGrouping(input *securityinsight.GroupingConfiguration, with } var ( - k1 = "entities" - k2 = "alert_details" - k3 = "custom_details" + k1 = "by_entities" + k2 = "by_alert_details" + k3 = "by_custom_details" ) - if withGroupByPrefix { - k1 = "group_by_" + k1 - k2 = "group_by_" + k2 - k3 = "group_by_" + k3 + if withGroupPrefix { + k1 = "group_" + k1 + k2 = "group_" + k2 + k3 = "group_" + k3 } return []interface{}{ map[string]interface{}{ diff --git a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go index def65d670af4..202a80d2a781 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource.go @@ -165,7 +165,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { string(securityinsight.MatchingMethodAllEntities), }, false), }, - "entities": { + "by_entities": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ @@ -173,7 +173,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { ValidateFunc: validation.StringInSlice(entityMappingTypes, false), }, }, - "alert_details": { + "by_alert_details": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ @@ -185,7 +185,7 @@ func resourceSentinelAlertRuleNrt() *pluginsdk.Resource { false), }, }, - "custom_details": { + "by_custom_details": { Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Schema{ diff --git a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go index 3ae660259e8d..f38396051540 100644 --- a/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go +++ b/internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go @@ -186,7 +186,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { string(securityinsight.MatchingMethodAllEntities), }, false), }, - // TODO 4.0 - rename this to "entities" + // TODO 4.0 - rename this to "by_entities" "group_by_entities": { Type: pluginsdk.TypeList, Optional: true, @@ -195,7 +195,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { ValidateFunc: validation.StringInSlice(entityMappingTypes, false), }, }, - // TODO 4.0 - rename this to "alert_details" + // TODO 4.0 - rename this to "by_alert_details" "group_by_alert_details": { Type: pluginsdk.TypeList, Optional: true, @@ -208,7 +208,7 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource { false), }, }, - // TODO 4.0 - rename this to "custom_details" + // TODO 4.0 - rename this to "by_custom_details" "group_by_custom_details": { Type: pluginsdk.TypeList, Optional: true, diff --git a/website/docs/r/sentinel_alert_rule_nrt.html.markdown b/website/docs/r/sentinel_alert_rule_nrt.html.markdown index 585adfa2099b..338c1813fb25 100644 --- a/website/docs/r/sentinel_alert_rule_nrt.html.markdown +++ b/website/docs/r/sentinel_alert_rule_nrt.html.markdown @@ -142,11 +142,11 @@ A `grouping` block supports the following: * `entity_matching_method` - (Optional) The method used to group incidents. Possible values are `AnyAlert`, `Selected` and `AllEntities`. Defaults to `AnyAlert`. -* `entities` - (Optional) A list of entity types to group by, only when the `entity_matching_method` is `Selected`. Possible values are `Account`, `AzureResource`, `CloudApplication`, `DNS`, `File`, `FileHash`, `Host`, `IP`, `Mailbox`, `MailCluster`, `MailMessage`, `Malware`, `Process`, `RegistryKey`, `RegistryValue`, `SecurityGroup`, `SubmissionMail`, `URL`. +* `by_entities` - (Optional) A list of entity types to group by, only when the `entity_matching_method` is `Selected`. Possible values are `Account`, `AzureResource`, `CloudApplication`, `DNS`, `File`, `FileHash`, `Host`, `IP`, `Mailbox`, `MailCluster`, `MailMessage`, `Malware`, `Process`, `RegistryKey`, `RegistryValue`, `SecurityGroup`, `SubmissionMail`, `URL`. -* `alert_details` - (Optional) A list of alert details to group by, only when the `entity_matching_method` is `Selected`. +* `by_alert_details` - (Optional) A list of alert details to group by, only when the `entity_matching_method` is `Selected`. -* `custom_details` - (Optional) A list of custom details keys to group by, only when the `entity_matching_method` is `Selected`. Only keys defined in the `custom_details` may be used. +* `by_custom_details` - (Optional) A list of custom details keys to group by, only when the `entity_matching_method` is `Selected`. Only keys defined in the `custom_details` may be used. ## Attributes Reference From 0e33afa29e431c5a3f1786490e872dec95e3fd2a Mon Sep 17 00:00:00 2001 From: magodo Date: Fri, 19 Aug 2022 11:08:48 +0800 Subject: [PATCH 4/5] rename in the test --- .../sentinel/sentinel_alert_rule_nrt_resource_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 1cb7976f5183..2ee021f90bbc 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go @@ -162,9 +162,9 @@ resource "azurerm_sentinel_alert_rule_nrt" "test" { lookback_duration = "P7D" reopen_closed_incidents = true entity_matching_method = "Selected" - entities = ["Host"] - alert_details = ["DisplayName"] - custom_details = ["OperatingSystemType", "OperatingSystemName"] + by_entities = ["Host"] + by_alert_details = ["DisplayName"] + by_custom_details = ["OperatingSystemType", "OperatingSystemName"] } } query = "Heartbeat" From 4257f2750cfcdcd7b365ecbec479d301e0eff90c Mon Sep 17 00:00:00 2001 From: magodo Date: Fri, 19 Aug 2022 11:42:16 +0800 Subject: [PATCH 5/5] terrafmt --- .../sentinel/sentinel_alert_rule_nrt_resource_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 2ee021f90bbc..a40bacb3b1d0 100644 --- a/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go +++ b/internal/services/sentinel/sentinel_alert_rule_nrt_resource_test.go @@ -162,9 +162,9 @@ resource "azurerm_sentinel_alert_rule_nrt" "test" { lookback_duration = "P7D" reopen_closed_incidents = true entity_matching_method = "Selected" - by_entities = ["Host"] - by_alert_details = ["DisplayName"] - by_custom_details = ["OperatingSystemType", "OperatingSystemName"] + by_entities = ["Host"] + by_alert_details = ["DisplayName"] + by_custom_details = ["OperatingSystemType", "OperatingSystemName"] } } query = "Heartbeat"