Skip to content

Commit

Permalink
Merge pull request #607 from SumoLogic/DET-383-insights-with-dynamic-…
Browse files Browse the repository at this point in the history
…severity

DET-383: Dynamic severity for Custom Insights
  • Loading branch information
pwardecki-sumo authored Jan 17, 2024
2 parents 8a6b441 + 0580f17 commit ee44260
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 55 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 2.28.1 (Unreleased)

ENHANCEMENTS:
* Added support for `dynamic_severity` for the CSE Custom Insight. (GH-607)

BUG FIXES:
* Minor fixes enabling proper resource import for CSE Rules (`severity`, `severity_mapping`, `aggregation_functions`). (GH-606)

Expand Down
80 changes: 63 additions & 17 deletions sumologic/resource_sumologic_cse_custom_insight.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ func resourceSumologicCSECustomInsight() *schema.Resource {
Required: true,
ValidateFunc: validation.StringInSlice([]string{"HIGH", "MEDIUM", "LOW", "CRITICAL"}, false),
},
"dynamic_severity": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"minimum_signal_severity": {
Type: schema.TypeInt,
Required: true,
},
"insight_severity": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"HIGH", "MEDIUM", "LOW", "CRITICAL"}, false),
},
},
},
},
"signal_names": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -86,6 +103,7 @@ func resourceSumologicCSECustomInsightRead(d *schema.ResourceData, meta interfac
d.Set("ordered", CSECustomInsightGet.Ordered)
d.Set("rule_ids", CSECustomInsightGet.RuleIds)
d.Set("severity", CSECustomInsightGet.Severity)
d.Set("dynamic_severity", dynamicSeverityArrayToResource(CSECustomInsightGet.DynamicSeverity))
d.Set("signal_names", CSECustomInsightGet.SignalNames)
d.Set("tags", CSECustomInsightGet.Tags)

Expand All @@ -103,14 +121,15 @@ func resourceSumologicCSECustomInsightCreate(d *schema.ResourceData, meta interf

if d.Id() == "" {
id, err := c.CreateCSECustomInsight(CSECustomInsight{
Description: d.Get("description").(string),
Enabled: d.Get("enabled").(bool),
RuleIds: resourceToStringArray(d.Get("rule_ids").([]interface{})),
Name: d.Get("name").(string),
Ordered: d.Get("ordered").(bool),
Severity: d.Get("severity").(string),
SignalNames: resourceToStringArray(d.Get("signal_names").([]interface{})),
Tags: resourceToStringArray(d.Get("tags").([]interface{})),
Description: d.Get("description").(string),
Enabled: d.Get("enabled").(bool),
RuleIds: resourceToStringArray(d.Get("rule_ids").([]interface{})),
Name: d.Get("name").(string),
Ordered: d.Get("ordered").(bool),
Severity: d.Get("severity").(string),
DynamicSeverity: resourceToDynamicSeverityArray(d.Get("dynamic_severity").([]interface{})),
SignalNames: resourceToStringArray(d.Get("signal_names").([]interface{})),
Tags: resourceToStringArray(d.Get("tags").([]interface{})),
})

if err != nil {
Expand Down Expand Up @@ -143,14 +162,41 @@ func resourceToCSECustomInsight(d *schema.ResourceData) (CSECustomInsight, error
}

return CSECustomInsight{
ID: id,
Description: d.Get("description").(string),
Enabled: d.Get("enabled").(bool),
RuleIds: resourceToStringArray(d.Get("rule_ids").([]interface{})),
Name: d.Get("name").(string),
Ordered: d.Get("ordered").(bool),
Severity: d.Get("severity").(string),
SignalNames: resourceToStringArray(d.Get("signal_names").([]interface{})),
Tags: resourceToStringArray(d.Get("tags").([]interface{})),
ID: id,
Description: d.Get("description").(string),
Enabled: d.Get("enabled").(bool),
RuleIds: resourceToStringArray(d.Get("rule_ids").([]interface{})),
Name: d.Get("name").(string),
Ordered: d.Get("ordered").(bool),
Severity: d.Get("severity").(string),
DynamicSeverity: resourceToDynamicSeverityArray(d.Get("dynamic_severity").([]interface{})),
SignalNames: resourceToStringArray(d.Get("signal_names").([]interface{})),
Tags: resourceToStringArray(d.Get("tags").([]interface{})),
}, nil
}

func resourceToDynamicSeverityArray(resourceDynamicSeverity []interface{}) []DynamicSeverity {
result := make([]DynamicSeverity, len(resourceDynamicSeverity))

for i, resourceDynamicSeverity := range resourceDynamicSeverity {
result[i] = DynamicSeverity{
MinimumSignalSeverity: resourceDynamicSeverity.(map[string]interface{})["minimum_signal_severity"].(int),
InsightSeverity: resourceDynamicSeverity.(map[string]interface{})["insight_severity"].(string),
}
}

return result
}

func dynamicSeverityArrayToResource(dynamicSeverities []DynamicSeverity) []map[string]interface{} {
result := make([]map[string]interface{}, len(dynamicSeverities))

for i, dynamicSeverity := range dynamicSeverities {
result[i] = map[string]interface{}{
"minimum_signal_severity": dynamicSeverity.MinimumSignalSeverity,
"insight_severity": dynamicSeverity.InsightSeverity,
}
}

return result
}
124 changes: 96 additions & 28 deletions sumologic/resource_sumologic_cse_custom_insight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/stretchr/testify/assert"
)

func TestAccSumologicCSECustomInsight_createAndUpdate(t *testing.T) {
Expand Down Expand Up @@ -35,7 +36,7 @@ func TestAccSumologicCSECustomInsight_createAndUpdate(t *testing.T) {
ordered, name, severity, signalName1, signalName2, tag),
Check: resource.ComposeTestCheckFunc(
testCheckCSECustomInsightExists(resourceName, &CustomInsight),
testCheckCustomInsightValues(&CustomInsight, description, enabled,
testCheckCustomInsightValues(t, &CustomInsight, description, enabled,
ordered, name, severity, signalName1, signalName2, tag),
resource.TestCheckResourceAttrSet(resourceName, "id"),
),
Expand All @@ -46,12 +47,62 @@ func TestAccSumologicCSECustomInsight_createAndUpdate(t *testing.T) {
signalName2, tag),
Check: resource.ComposeTestCheckFunc(
testCheckCSECustomInsightExists(resourceName, &CustomInsight),
testCheckCustomInsightValues(&CustomInsight, description, enabled,
testCheckCustomInsightValues(t, &CustomInsight, description, enabled,
ordered, nameUpdated, severityUpdated, signalName1,
signalName2, tag),
resource.TestCheckResourceAttrSet(resourceName, "id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSumologicCSECustomInsight_createAndUpdateWithDynamicSeverity(t *testing.T) {
SkipCseTest(t)

var CustomInsight CSECustomInsight
minimumSignalSeverity1 := 5
dynamicSeverity1 := "MEDIUM"
minimumSignalSeverity2 := 8
dynamicSeverity2 := "HIGH"

updatedMinimumSignalSeverity2 := 9
updatedDynamicSeverity2 := "CRITICAL"

resourceName := "sumologic_cse_custom_insight.custom_insight2"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCSECustomInsightDestroy,
Steps: []resource.TestStep{
{
Config: testCreateCSECustomInsightConfigWithDynamicSeverity(minimumSignalSeverity1, dynamicSeverity1, minimumSignalSeverity2, dynamicSeverity2),
Check: resource.ComposeTestCheckFunc(
testCheckCSECustomInsightExists(resourceName, &CustomInsight),
testCheckCustomInsightDynamicSeverity(t, &CustomInsight,
minimumSignalSeverity1, dynamicSeverity1, minimumSignalSeverity2, dynamicSeverity2),
resource.TestCheckResourceAttrSet(resourceName, "id"),
),
},
{
Config: testCreateCSECustomInsightConfigWithDynamicSeverity(minimumSignalSeverity1, dynamicSeverity1, updatedMinimumSignalSeverity2, updatedDynamicSeverity2),
Check: resource.ComposeTestCheckFunc(
testCheckCSECustomInsightExists(resourceName, &CustomInsight),
testCheckCustomInsightDynamicSeverity(t, &CustomInsight,
minimumSignalSeverity1, dynamicSeverity1, updatedMinimumSignalSeverity2, updatedDynamicSeverity2),
resource.TestCheckResourceAttrSet(resourceName, "id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down Expand Up @@ -96,6 +147,28 @@ resource "sumologic_cse_custom_insight" "custom_insight" {
signalName2, tag)
}

func testCreateCSECustomInsightConfigWithDynamicSeverity(
minimumSignalSeverity1 int, dynamicSeverity1 string, minimumSignalSeverity2 int, dynamicSeverity2 string) string {
return fmt.Sprintf(`
resource "sumologic_cse_custom_insight" "custom_insight2" {
description = "Dynamic severity insight"
enabled = true
ordered = true
name = "Dynamic severity insight"
severity = "LOW"
dynamic_severity {
minimum_signal_severity = %d
insight_severity = "%s"
}
dynamic_severity {
minimum_signal_severity = %d
insight_severity = "%s"
}
tags = ["test tag"]
}
`, minimumSignalSeverity1, dynamicSeverity1, minimumSignalSeverity2, dynamicSeverity2)
}

func testCheckCSECustomInsightExists(n string, CustomInsight *CSECustomInsight) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand All @@ -104,7 +177,7 @@ func testCheckCSECustomInsightExists(n string, CustomInsight *CSECustomInsight)
}

if rs.Primary.ID == "" {
return fmt.Errorf("chain rule ID is not set")
return fmt.Errorf("CustomInsight ID is not set")
}

c := testAccProvider.Meta().(*Client)
Expand All @@ -119,35 +192,30 @@ func testCheckCSECustomInsightExists(n string, CustomInsight *CSECustomInsight)
}
}

func testCheckCustomInsightValues(CustomInsight *CSECustomInsight, description string,
func testCheckCustomInsightValues(t *testing.T, CustomInsight *CSECustomInsight, description string,
enabled bool, ordered bool, name string, severity string, signalName1 string,
signalName2 string, tag string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if CustomInsight.Description != description {
return fmt.Errorf("bad description, expected \"%s\", got %#v", description, CustomInsight.Description)
}
if CustomInsight.Enabled != enabled {
return fmt.Errorf("bad enabled, expected \"%t\", got %#v", enabled, CustomInsight.Enabled)
}
if CustomInsight.Ordered != ordered {
return fmt.Errorf("bad ordered, expected \"%t\", got %#v", ordered, CustomInsight.Ordered)
}
if CustomInsight.Name != name {
return fmt.Errorf("bad name, expected \"%s\", got %#v", name, CustomInsight.Name)
}
if CustomInsight.Severity != severity {
return fmt.Errorf("bad severity, expected \"%s\", got %#v", severity, CustomInsight.Severity)
}
if CustomInsight.SignalNames[0] != signalName1 {
return fmt.Errorf("bad signalName1, expected \"%s\", got %#v", signalName1, CustomInsight.SignalNames[0])
}
if CustomInsight.SignalNames[1] != signalName2 {
return fmt.Errorf("bad signalName2, expected \"%s\", got %#v", signalName2, CustomInsight.SignalNames[1])
}
if CustomInsight.Tags[0] != tag {
return fmt.Errorf("bad tag, expected \"%s\", got %#v", tag, CustomInsight.Tags[0])
}
assert.Equal(t, description, CustomInsight.Description)
assert.Equal(t, enabled, CustomInsight.Enabled)
assert.Equal(t, ordered, CustomInsight.Ordered)
assert.Equal(t, name, CustomInsight.Name)
assert.Equal(t, severity, CustomInsight.Severity)
assert.Equal(t, signalName1, CustomInsight.SignalNames[0])
assert.Equal(t, signalName2, CustomInsight.SignalNames[1])
assert.Equal(t, tag, CustomInsight.Tags[0])
return nil
}
}

func testCheckCustomInsightDynamicSeverity(t *testing.T, CustomInsight *CSECustomInsight,
minimumSignalSeverity1 int, dynamicSeverity1 string, minimumSignalSeverity2 int, dynamicSeverity2 string) resource.TestCheckFunc {

return func(s *terraform.State) error {
assert.Equal(t, minimumSignalSeverity1, CustomInsight.DynamicSeverity[0].MinimumSignalSeverity)
assert.Equal(t, dynamicSeverity1, CustomInsight.DynamicSeverity[0].InsightSeverity)
assert.Equal(t, minimumSignalSeverity2, CustomInsight.DynamicSeverity[1].MinimumSignalSeverity)
assert.Equal(t, dynamicSeverity2, CustomInsight.DynamicSeverity[1].InsightSeverity)
return nil
}
}
24 changes: 15 additions & 9 deletions sumologic/sumologic_cse_custom_insight.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,20 @@ type CSECustomInsightResponse struct {
CSECustomInsight CSECustomInsight `json:"data"`
}

type DynamicSeverity struct {
MinimumSignalSeverity int `json:"minimumSignalSeverity"`
InsightSeverity string `json:"insightSeverity"`
}

type CSECustomInsight struct {
ID string `json:"id,omitempty"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Name string `json:"name"`
Ordered bool `json:"ordered"`
RuleIds []string `json:"ruleIds"`
Severity string `json:"severity"`
SignalNames []string `json:"signalNames"`
Tags []string `json:"tags"`
ID string `json:"id,omitempty"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Name string `json:"name"`
Ordered bool `json:"ordered"`
RuleIds []string `json:"ruleIds"`
Severity string `json:"severity"`
DynamicSeverity []DynamicSeverity `json:"dynamicSeverity"`
SignalNames []string `json:"signalNames"`
Tags []string `json:"tags"`
}
9 changes: 8 additions & 1 deletion website/docs/r/cse_custom_insight.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ resource "sumologic_cse_custom_insight" "custom_insight" {
name = "Custom Insight Example"
rule_ids = ["MATCH-S00001", "THRESHOLD-U00005"]
severity = "HIGH"
dynamic_severity {
minimum_signal_severity = 8
insight_severity = "CRITICAL"
}
signal_names = ["Some Signal Name", "Wildcard Signal Name *"]
tags = ["_mitreAttackTactic:TA0009"]
}
Expand All @@ -31,7 +35,10 @@ The following arguments are supported:
- `ordered` - (Required) Whether the signals matching the rule IDs/signal names must be in the same chronological order as they are listed in the Custom Insight
- `name` - (Required) The name of the Custom Insight and the generated Insights
- `rule_ids` - (Optional) The Rule IDs to match to generate an Insight (exactly one of rule_ids or signal_names must be specified)
- `severity` - (Required) The severity of the generated Insights (HIGH, MEDIUM, or LOW)
- `severity` - (Required) The severity of the generated Insights (CRITICAL, HIGH, MEDIUM, or LOW)
- `dynamic_severity` - (Optional) The severity of the generated Insight that is based on the severity of the Signals that trigger the Insight.
+ `minimum_signal_severity` - (Required) minimum Signal severity as the threshold for an Insight severity level
+ `insight_severity` - (Required) The severity of the generated Insight (CRITICAL, HIGH, MEDIUM, or LOW)
- `signal_names` - (Optional) The Signal names to match to generate an Insight (exactly one of rule_ids or signal_names must be specified)
- `tags` - (Required) The tags of the generated Insights

Expand Down

0 comments on commit ee44260

Please sign in to comment.