Skip to content

Commit

Permalink
azurerm_sentinel_alert_rule_fusion - support for sourceblock (#19093
Browse files Browse the repository at this point in the history
)
  • Loading branch information
magodo authored Nov 6, 2022
1 parent 0d99605 commit 7739efe
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 14 deletions.
208 changes: 208 additions & 0 deletions internal/services/sentinel/sentinel_alert_rule_fusion_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,62 @@ func resourceSentinelAlertRuleFusion() *pluginsdk.Resource {
Optional: true,
Default: true,
},

"source": {
Type: pluginsdk.TypeList,
Optional: true,
// Service will auto-fill this if not given in request, based on the "alert_rule_template_guid".
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: true,
},
"sub_type": {
Type: pluginsdk.TypeList,
Optional: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: true,
},
"severities_allowed": {
Type: pluginsdk.TypeSet,
Required: true,
MinItems: 1,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringInSlice(
[]string{
string(securityinsight.AlertSeverityHigh),
string(securityinsight.AlertSeverityMedium),
string(securityinsight.AlertSeverityLow),
string(securityinsight.AlertSeverityInformational),
},
false,
),
},
},
},
},
},
},
},
},
},
}
}
Expand Down Expand Up @@ -98,6 +154,7 @@ func resourceSentinelAlertRuleFusionCreateUpdate(d *pluginsdk.ResourceData, meta
FusionAlertRuleProperties: &securityinsight.FusionAlertRuleProperties{
AlertRuleTemplateName: utils.String(d.Get("alert_rule_template_guid").(string)),
Enabled: utils.Bool(d.Get("enabled").(bool)),
SourceSettings: expandFusionSourceSettings(d.Get("source").([]interface{})),
},
}

Expand Down Expand Up @@ -155,6 +212,9 @@ func resourceSentinelAlertRuleFusionRead(d *pluginsdk.ResourceData, meta interfa
if prop := rule.FusionAlertRuleProperties; prop != nil {
d.Set("enabled", prop.Enabled)
d.Set("alert_rule_template_guid", prop.AlertRuleTemplateName)
if err := d.Set("source", flattenFusionSourceSettings(prop.SourceSettings)); err != nil {
return fmt.Errorf("setting `source`: %v", err)
}
}

return nil
Expand All @@ -176,3 +236,151 @@ func resourceSentinelAlertRuleFusionDelete(d *pluginsdk.ResourceData, meta inter

return nil
}

func expandFusionSourceSettings(input []interface{}) *[]securityinsight.FusionSourceSettings {
if len(input) == 0 {
return nil
}

result := make([]securityinsight.FusionSourceSettings, 0)

for _, e := range input {
e := e.(map[string]interface{})
setting := securityinsight.FusionSourceSettings{
Enabled: utils.Bool(e["enabled"].(bool)),
SourceName: utils.String(e["name"].(string)),
SourceSubTypes: expandFusionSourceSubTypes(e["sub_type"].([]interface{})),
}
result = append(result, setting)
}

return &result
}

func expandFusionSourceSubTypes(input []interface{}) *[]securityinsight.FusionSourceSubTypeSetting {
if len(input) == 0 {
return nil
}

result := make([]securityinsight.FusionSourceSubTypeSetting, 0)

for _, e := range input {
e := e.(map[string]interface{})
setting := securityinsight.FusionSourceSubTypeSetting{
Enabled: utils.Bool(e["enabled"].(bool)),
SourceSubTypeName: utils.String(e["name"].(string)),
SeverityFilters: &securityinsight.FusionSubTypeSeverityFilter{
Filters: expandFusionSubTypeSeverityFiltersItems(e["severities_allowed"].(*pluginsdk.Set).List()),
},
}
result = append(result, setting)
}

return &result
}

func expandFusionSubTypeSeverityFiltersItems(input []interface{}) *[]securityinsight.FusionSubTypeSeverityFiltersItem {
if len(input) == 0 {
return nil
}

result := make([]securityinsight.FusionSubTypeSeverityFiltersItem, 0)

// We can't simply remove the disabled properties in the request, as that will be reflected to the backend model (i.e. those unspecified severity will be absent also).
// As any absent severity then will not be shown in the Portal when users try to edit the alert rule. The drop down menu won't show these absent severities...
filters := map[string]bool{}
for _, e := range securityinsight.PossibleAlertSeverityValues() {
filters[string(e)] = false
}

for _, e := range input {
filters[e.(string)] = true
}

for severity, enabled := range filters {
item := securityinsight.FusionSubTypeSeverityFiltersItem{
Enabled: utils.Bool(enabled),
Severity: securityinsight.AlertSeverity(severity),
}
result = append(result, item)
}

return &result
}

func flattenFusionSourceSettings(input *[]securityinsight.FusionSourceSettings) []interface{} {
if input == nil {
return []interface{}{}
}

output := make([]interface{}, 0)

for _, e := range *input {
var name string
if e.SourceName != nil {
name = *e.SourceName
}

var enabled bool
if e.Enabled != nil {
enabled = *e.Enabled
}

output = append(output, map[string]interface{}{
"name": name,
"enabled": enabled,
"sub_type": flattenFusionSourceSubTypes(e.SourceSubTypes),
})
}

return output
}

func flattenFusionSourceSubTypes(input *[]securityinsight.FusionSourceSubTypeSetting) []interface{} {
if input == nil {
return []interface{}{}
}

output := make([]interface{}, 0)

for _, e := range *input {
var name string
if e.SourceSubTypeName != nil {
name = *e.SourceSubTypeName
}

var enabledSeverities []interface{}
if e.SeverityFilters != nil {
enabledSeverities = flattenFusionSubTypeSeverityFiltersItems(e.SeverityFilters.Filters)
}

var enabled bool
if e.Enabled != nil {
enabled = *e.Enabled
}

output = append(output, map[string]interface{}{
"name": name,
"enabled": enabled,
"severities_allowed": enabledSeverities,
})
}

return output
}

func flattenFusionSubTypeSeverityFiltersItems(input *[]securityinsight.FusionSubTypeSeverityFiltersItem) []interface{} {
if input == nil {
return []interface{}{}
}

output := make([]interface{}, 0)

for _, e := range *input {
if e.Enabled != nil && *e.Enabled {
output = append(output, string(e.Severity))
}
}

return output
}
109 changes: 95 additions & 14 deletions internal/services/sentinel/sentinel_alert_rule_fusion_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,42 +31,48 @@ func TestAccSentinelAlertRuleFusion_basic(t *testing.T) {
})
}

func TestAccSentinelAlertRuleFusion_complete(t *testing.T) {
func TestAccSentinelAlertRuleFusion_disable(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test")
r := SentinelAlertRuleFusionResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.complete(data),
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccSentinelAlertRuleFusion_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test")
r := SentinelAlertRuleFusionResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Config: r.disabled(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.complete(data),
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccSentinelAlertRuleFusion_sourceSetting(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_sentinel_alert_rule_fusion", "test")
r := SentinelAlertRuleFusionResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Config: r.sourceSetting(data, true),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
{
Config: r.sourceSetting(data, false),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
Expand Down Expand Up @@ -131,7 +137,7 @@ resource "azurerm_sentinel_alert_rule_fusion" "test" {
`, r.template(data), data.RandomInteger)
}

func (r SentinelAlertRuleFusionResource) complete(data acceptance.TestData) string {
func (r SentinelAlertRuleFusionResource) disabled(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand All @@ -149,6 +155,81 @@ resource "azurerm_sentinel_alert_rule_fusion" "test" {
`, r.template(data), data.RandomInteger)
}

func (r SentinelAlertRuleFusionResource) sourceSetting(data acceptance.TestData, enabled bool) string {
return fmt.Sprintf(`
%[1]s
data "azurerm_sentinel_alert_rule_template" "test" {
display_name = "Advanced Multistage Attack Detection"
log_analytics_workspace_id = azurerm_log_analytics_solution.test.workspace_resource_id
}
resource "azurerm_sentinel_alert_rule_fusion" "test" {
name = "acctest-SentinelAlertRule-Fusion-%[2]d"
log_analytics_workspace_id = azurerm_log_analytics_solution.test.workspace_resource_id
alert_rule_template_guid = data.azurerm_sentinel_alert_rule_template.test.name
source {
name = "Anomalies"
enabled = %[3]t
}
source {
name = "Alert providers"
enabled = %[3]t
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Azure Active Directory Identity Protection"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Microsoft 365 Defender"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Microsoft Cloud App Security"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Azure Defender"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Microsoft Defender for Endpoint"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Microsoft Defender for Identity"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Azure Defender for IoT"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Microsoft Defender for Office 365"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Azure Sentinel scheduled analytics rules"
enabled = %[3]t
}
sub_type {
severities_allowed = ["High", "Informational", "Low", "Medium"]
name = "Azure Sentinel NRT analytic rules"
enabled = %[3]t
}
}
}
`, r.template(data), data.RandomInteger, enabled)
}

func (r SentinelAlertRuleFusionResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down
Loading

0 comments on commit 7739efe

Please sign in to comment.