Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_sentinel_alert_rule_scheduled - support for group_by_{alert|custom}_details, alert_details_override, entity_mapping, custom_details` #15901

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 301 additions & 20 deletions internal/services/sentinel/sentinel_alert_rule_scheduled_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ var entityMatchingMethodMap = map[string]string{
}

func resourceSentinelAlertRuleScheduled() *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: resourceSentinelAlertRuleScheduledCreateUpdate,
Read: resourceSentinelAlertRuleScheduledRead,
Expand Down Expand Up @@ -74,6 +94,12 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource {
ValidateFunc: validation.IsUUID,
},

"alert_rule_template_version": {
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
},

"description": {
Type: pluginsdk.TypeString,
Optional: true,
Expand Down Expand Up @@ -184,30 +210,31 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource {
},
},
"group_by": {
Type: pluginsdk.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as below) a GroupBy would be an order-statement in other cases, so is order relevant here/should this be a List?

Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringInSlice(entityMappingTypes, false),
},
},
"group_by_alert_details": {
Type: pluginsdk.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as below) a GroupBy would be an order-statement in other cases, so is order relevant here/should this be a List?

Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringInSlice([]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),
}, false),
string(securityinsight.AlertDetailDisplayName),
string(securityinsight.AlertDetailSeverity),
},
false),
},
},
"group_by_custom_details": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given this is a GroupBy, this'd be implicitly ordered - so should this be a List?

Type: pluginsdk.TypeSet,
Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
},
},
Expand Down Expand Up @@ -284,6 +311,75 @@ func resourceSentinelAlertRuleScheduled() *pluginsdk.Resource {
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,
},
},
},
},
},
},
},
},
}
}
Expand Down Expand Up @@ -358,10 +454,21 @@ func resourceSentinelAlertRuleScheduledCreateUpdate(d *pluginsdk.ResourceData, m
if v, ok := d.GetOk("alert_rule_template_guid"); ok {
param.ScheduledAlertRuleProperties.AlertRuleTemplateName = utils.String(v.(string))
}

if v, ok := d.GetOk("alert_rule_template_version"); ok {
param.ScheduledAlertRuleProperties.TemplateVersion = utils.String(v.(string))
}
if v, ok := d.GetOk("event_grouping"); ok {
param.ScheduledAlertRuleProperties.EventGroupingSettings = expandAlertRuleScheduledEventGroupingSetting(v.([]interface{}))
}
if v, ok := d.GetOk("alert_details_override"); ok {
param.ScheduledAlertRuleProperties.AlertDetailsOverride = expandAlertRuleScheduledAlertDetailsOverride(v.([]interface{}))
}
if v, ok := d.GetOk("custom_details"); ok {
param.ScheduledAlertRuleProperties.CustomDetails = utils.ExpandMapStringPtrString(v.(map[string]interface{}))
}
if v, ok := d.GetOk("entity_mapping"); ok {
param.ScheduledAlertRuleProperties.EntityMappings = expandAlertRuleScheduledEntityMapping(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() {
Expand Down Expand Up @@ -441,10 +548,20 @@ func resourceSentinelAlertRuleScheduledRead(d *pluginsdk.ResourceData, meta inte
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("event_grouping", flattenAlertRuleScheduledEventGroupingSetting(prop.EventGroupingSettings)); err != nil {
return fmt.Errorf("setting `event_grouping`: %+v", err)
}
if err := d.Set("alert_details_override", flattenAlertRuleScheduledAlertDetailsOverride(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", flattenAlertRuleScheduledEntityMapping(prop.EntityMappings)); err != nil {
return fmt.Errorf("setting `entity_mapping`: %+v", err)
}
}

return nil
Expand Down Expand Up @@ -549,6 +666,15 @@ func expandAlertRuleScheduledGrouping(input []interface{}) *securityinsight.Grou
}
output.GroupByEntities = &groupByEntities

groupByAlertDetailsSet := raw["group_by_alert_details"].(*pluginsdk.Set).List()
groupByAlertDetails := make([]securityinsight.AlertDetail, len(groupByAlertDetailsSet))
for idx, t := range groupByAlertDetailsSet {
groupByAlertDetails[idx] = securityinsight.AlertDetail(t.(string))
}
output.GroupByAlertDetails = &groupByAlertDetails

output.GroupByCustomDetails = utils.ExpandStringSlice(raw["group_by_custom_details"].(*pluginsdk.Set).List())

return output
}

Expand Down Expand Up @@ -594,13 +720,29 @@ func flattenAlertRuleScheduledGrouping(input *securityinsight.GroupingConfigurat
}
}

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, string(detail))
}
}

return []interface{}{
map[string]interface{}{
"enabled": enabled,
"lookback_duration": lookbackDuration,
"reopen_closed_incidents": reopenClosedIncidents,
"entity_matching_method": string(input.MatchingMethod),
"group_by": groupByEntities,
"group_by_alert_details": groupByAlertDetails,
"group_by_custom_details": groupByCustomDetails,
},
}
}
Expand All @@ -621,3 +763,142 @@ func flattenAlertRuleScheduledEventGroupingSetting(input *securityinsight.EventG
},
}
}

func expandAlertRuleScheduledAlertDetailsOverride(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 flattenAlertRuleScheduledAlertDetailsOverride(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 expandAlertRuleScheduledEntityMapping(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: expandAlertRuleScheduledFieldMapping(b["field_mapping"].([]interface{})),
})
}

return &result
}

func flattenAlertRuleScheduledEntityMapping(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": flattenAlertRuleScheduledFieldMapping(e.FieldMappings),
})
}

return output
}

func expandAlertRuleScheduledFieldMapping(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 flattenAlertRuleScheduledFieldMapping(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
}
Loading