Skip to content

Commit 83bc845

Browse files
Merge pull request #799 from SumoLogic/DET-728
DET-728: Added support for overriding sumo rules
2 parents f85c6df + 3c74b8a commit 83bc845

21 files changed

+1083
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Add new change notes here
33

44
ENHANCEMENTS:
5+
* Added support for overriding sumo built-in rules
56
* Updated Terraform version in GitHub Actions tests to 1.7.5
67

78
## 3.1.3 (August 6, 2025)

sumologic/resource_sumologic_cse_aggregation_rule.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ func resourceSumologicCSEAggregationRule() *schema.Resource {
6969
Optional: true,
7070
},
7171
"match_expression": {
72-
Type: schema.TypeString,
73-
Required: true,
72+
Type: schema.TypeString,
73+
Required: true,
74+
DiffSuppressFunc: suppressSpaceDiff,
7475
},
7576
"name": {
7677
Type: schema.TypeString,
@@ -206,13 +207,20 @@ func resourceSumologicCSEAggregationRuleCreate(d *schema.ResourceData, meta inte
206207
}
207208

208209
func resourceSumologicCSEAggregationRuleUpdate(d *schema.ResourceData, meta interface{}) error {
210+
ruleSource := getRuleSource(d.Id(), meta)
209211
CSEAggregationRule, err := resourceToCSEAggregationRule(d)
210212
if err != nil {
211213
return err
212214
}
213215

214216
c := meta.(*Client)
215-
if err = c.UpdateCSEAggregationRule(CSEAggregationRule); err != nil {
217+
if ruleSource == "user" {
218+
err = c.UpdateCSEAggregationRule(CSEAggregationRule)
219+
} else {
220+
err = c.OverrideCSEAggregationRule(CSEAggregationRule)
221+
}
222+
223+
if err != nil {
216224
return err
217225
}
218226

sumologic/resource_sumologic_cse_aggregation_rule_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,62 @@ func TestAccSumologicCSEAggregationRule_createAndUpdateWithCustomWindowSize(t *t
5656
})
5757
}
5858

59+
func TestAccSumologicCSEAggregationRule_Override(t *testing.T) {
60+
SkipCseTest(t)
61+
62+
var aggregationRule CSEAggregationRule
63+
descriptionExpression := "This rule detects when a user has utilized multiple distinct User Agents when performing authentication through Okta. This activity could potentially indicate credential theft or a general session anomaly. Examine other Okta related events surrounding the time period for this signal, pivoting off the username value to examine if any other suspicious activity has taken place. If this rule is generating false positives, adjust the threshold value and consider excluding certain user accounts via tuning expression."
64+
65+
resourceName := "sumologic_cse_aggregation_rule.sumo_aggregation_rule_test"
66+
resource.Test(t, resource.TestCase{
67+
PreCheck: func() { testAccPreCheck(t) },
68+
Providers: testAccProviders,
69+
CheckDestroy: testAccCSEAggregationRuleDestroy,
70+
Steps: []resource.TestStep{
71+
{
72+
Config: testOverrideCSEAggregationRuleConfig(descriptionExpression),
73+
ResourceName: resourceName,
74+
ImportState: true,
75+
ImportStateId: "AGGREGATION-S00009",
76+
ImportStateVerify: false,
77+
ImportStateVerifyIgnore: []string{"name"}, // Ignore fields that might differ
78+
ImportStatePersist: true,
79+
},
80+
{
81+
Config: testOverrideCSEAggregationRuleConfig(fmt.Sprintf("Updated %s", descriptionExpression)),
82+
Check: resource.ComposeTestCheckFunc(
83+
testCheckCSEAggregationRuleExists(resourceName, &aggregationRule),
84+
testCheckAggregationRuleOverrideValues(&aggregationRule, fmt.Sprintf("Updated %s", descriptionExpression)),
85+
resource.TestCheckResourceAttrSet(resourceName, "id"),
86+
resource.TestCheckResourceAttr(resourceName, "id", "AGGREGATION-S00009"),
87+
),
88+
},
89+
{
90+
Config: testOverrideCSEAggregationRuleConfig(descriptionExpression),
91+
Check: resource.ComposeTestCheckFunc(
92+
testCheckCSEAggregationRuleExists(resourceName, &aggregationRule),
93+
testCheckAggregationRuleOverrideValues(&aggregationRule, descriptionExpression),
94+
resource.TestCheckResourceAttrSet(resourceName, "id"),
95+
resource.TestCheckResourceAttr(resourceName, "id", "AGGREGATION-S00009"),
96+
),
97+
},
98+
{
99+
Config: getAggregationRuleRemovedBlock(),
100+
},
101+
},
102+
})
103+
}
104+
105+
func getAggregationRuleRemovedBlock() string {
106+
return fmt.Sprintf(`
107+
removed {
108+
from = sumologic_cse_aggregation_rule.sumo_aggregation_rule_test
109+
lifecycle {
110+
destroy = false
111+
}
112+
}`)
113+
}
114+
59115
func TestAccSumologicCSEAggregationRule_createAndUpdateToCustomWindowSize(t *testing.T) {
60116
SkipCseTest(t)
61117

@@ -171,6 +227,50 @@ func testAccCSEAggregationRuleDestroy(s *terraform.State) error {
171227
return nil
172228
}
173229

230+
func testOverrideCSEAggregationRuleConfig(descriptionExpression string) string {
231+
return fmt.Sprintf(`
232+
resource "sumologic_cse_aggregation_rule" "sumo_aggregation_rule_test" {
233+
description_expression = "%s"
234+
enabled = true
235+
group_by_entity = true
236+
group_by_fields = []
237+
is_prototype = true
238+
match_expression = <<-EOT
239+
metadata_vendor = "Okta"
240+
and metadata_deviceEventId = "user.authentication.sso"
241+
EOT
242+
name = "Okta - Session Anomaly (Multiple User Agents)"
243+
name_expression = "Okta - Session Anomaly (Multiple User Agents) for user: {{user_username}}"
244+
summary_expression = "{{user_username}} has utilized a number of distinct User Agents which has crossed the threshold (4) value within a 30-minute time period to perform Okta authentication."
245+
tags = [
246+
"_mitreAttackTactic:TA0001",
247+
"_mitreAttackTechnique:T1078.004",
248+
]
249+
trigger_expression = "distinct_userAgents > 4"
250+
window_size = "T30M"
251+
252+
aggregation_functions {
253+
arguments = [
254+
"fields[\"client.userAgent.rawUserAgent\"]",
255+
]
256+
function = "count_distinct"
257+
name = "distinct_userAgents"
258+
}
259+
260+
entity_selectors {
261+
entity_type = "_username"
262+
expression = "user_username"
263+
}
264+
265+
severity_mapping {
266+
default = 1
267+
field = null
268+
type = "constant"
269+
}
270+
}
271+
`, descriptionExpression)
272+
}
273+
174274
func testCreateCSEAggregationRuleConfig(t *testing.T, payload *CSEAggregationRule) string {
175275
resourceTemplate := `
176276
resource "sumologic_cse_aggregation_rule" "aggregation_rule" {
@@ -303,3 +403,12 @@ func testCheckCSEAggregationRuleValues(t *testing.T, expected *CSEAggregationRul
303403
return nil
304404
}
305405
}
406+
407+
func testCheckAggregationRuleOverrideValues(aggregationRule *CSEAggregationRule, descriptionExpression string) resource.TestCheckFunc {
408+
return func(s *terraform.State) error {
409+
if aggregationRule.DescriptionExpression != descriptionExpression {
410+
return fmt.Errorf("bad descriptionExpression, expected \"%s\", got %#v", descriptionExpression, aggregationRule.DescriptionExpression)
411+
}
412+
return nil
413+
}
414+
}

sumologic/resource_sumologic_cse_chain_rule.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ func resourceSumologicCSEChainRule() *schema.Resource {
3434
Elem: &schema.Resource{
3535
Schema: map[string]*schema.Schema{
3636
"expression": {
37-
Type: schema.TypeString,
38-
Required: true,
37+
Type: schema.TypeString,
38+
Required: true,
39+
DiffSuppressFunc: suppressSpaceDiff,
3940
},
4041
"limit": {
4142
Type: schema.TypeInt,
@@ -184,13 +185,20 @@ func resourceSumologicCSEChainRuleCreate(d *schema.ResourceData, meta interface{
184185
}
185186

186187
func resourceSumologicCSEChainRuleUpdate(d *schema.ResourceData, meta interface{}) error {
188+
ruleSource := getRuleSource(d.Id(), meta)
187189
CSEChainRule, err := resourceToCSEChainRule(d)
188190
if err != nil {
189191
return err
190192
}
191193

192194
c := meta.(*Client)
193-
if err = c.UpdateCSEChainRule(CSEChainRule); err != nil {
195+
if ruleSource == "user" {
196+
err = c.UpdateCSEChainRule(CSEChainRule)
197+
} else {
198+
err = c.OverrideCSEChainRule(CSEChainRule)
199+
}
200+
201+
if err != nil {
194202
return err
195203
}
196204

sumologic/resource_sumologic_cse_chain_rule_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,63 @@ func TestAccSumologicCSEChainRule_createAndUpdateWithCustomWindowSize(t *testing
5656
})
5757
}
5858

59+
func TestAccSumologicCSEChainRule_Override(t *testing.T) {
60+
SkipCseTest(t)
61+
62+
var ChainRule CSEChainRule
63+
descriptionExpression := "This rule utilizes Jamf telemetry and looks for osascript execution with a suspicious parent process indicating execution from a shell or terminal in addition to the osascript process making network connections to an external IP address."
64+
65+
resourceName := "sumologic_cse_chain_rule.sumo_chain_rule_test"
66+
resource.Test(t, resource.TestCase{
67+
PreCheck: func() { testAccPreCheck(t) },
68+
Providers: testAccProviders,
69+
CheckDestroy: testAccCSEChainRuleDestroy,
70+
Steps: []resource.TestStep{
71+
{
72+
Config: testOverrideCSEChainRuleConfig(descriptionExpression),
73+
ResourceName: resourceName,
74+
ImportState: true,
75+
ImportStateId: "CHAIN-S00016",
76+
ImportStateVerify: false,
77+
ImportStateVerifyIgnore: []string{"name"}, // Ignore fields that might differ
78+
ImportStatePersist: true,
79+
},
80+
{
81+
Config: testOverrideCSEChainRuleConfig(fmt.Sprintf("Updated %s", descriptionExpression)),
82+
Check: resource.ComposeTestCheckFunc(
83+
testCheckCSEChainRuleExists(resourceName, &ChainRule),
84+
testCheckChainRuleOverrideValues(&ChainRule, fmt.Sprintf("Updated %s", descriptionExpression)),
85+
resource.TestCheckResourceAttrSet(resourceName, "id"),
86+
resource.TestCheckResourceAttr(resourceName, "id", "CHAIN-S00016"),
87+
),
88+
},
89+
{
90+
Config: testOverrideCSEChainRuleConfig(descriptionExpression),
91+
Check: resource.ComposeTestCheckFunc(
92+
testCheckCSEChainRuleExists(resourceName, &ChainRule),
93+
testCheckChainRuleOverrideValues(&ChainRule, descriptionExpression),
94+
resource.TestCheckResourceAttrSet(resourceName, "id"),
95+
resource.TestCheckResourceAttr(resourceName, "id", "CHAIN-S00016"),
96+
),
97+
},
98+
{
99+
Config: getChainRuleRemovedBlock(),
100+
},
101+
},
102+
})
103+
}
104+
105+
func getChainRuleRemovedBlock() string {
106+
return fmt.Sprintf(`
107+
removed {
108+
from = sumologic_cse_chain_rule.sumo_chain_rule_test
109+
lifecycle {
110+
destroy = false
111+
}
112+
}
113+
`)
114+
}
115+
59116
func TestAccSumologicCSEChainRule_createAndUpdateToCustomWindowSize(t *testing.T) {
60117
SkipCseTest(t)
61118

@@ -219,6 +276,54 @@ func testCreateCSEChainRuleConfig(t *testing.T, payload *CSEChainRule) string {
219276
return buffer.String()
220277
}
221278

279+
func testOverrideCSEChainRuleConfig(descriptionExpression string) string {
280+
return fmt.Sprintf(`
281+
resource "sumologic_cse_chain_rule" "sumo_chain_rule_test" {
282+
description = "%s"
283+
enabled = true
284+
group_by_fields = [
285+
"user_username",
286+
]
287+
is_prototype = true
288+
name = "macOS - Suspicious Osascript Execution and Network Activity"
289+
ordered = false
290+
severity = 3
291+
summary_expression = "User: {{user_username}} has created and deleted an agent pool in a short period of time - inaminadika10"
292+
tags = [
293+
"_mitreAttackTactic:TA0002",
294+
"_mitreAttackTechnique:T1059.002",
295+
]
296+
window_size = "T05M"
297+
298+
entity_selectors {
299+
entity_type = "_hostname"
300+
expression = "device_hostname"
301+
}
302+
303+
expressions_and_limits {
304+
expression = <<-EOT
305+
baseImage = "/usr/bin/osascript"
306+
and !isNull(commandLine)
307+
and parentBaseImage matches /(\/bin)/
308+
EOT
309+
limit = 1
310+
}
311+
expressions_and_limits {
312+
expression = <<-EOT
313+
metadata_vendor = "Jamf"
314+
and metadata_product = "Jamf"
315+
and metadata_deviceEventId = "AUE_CONNECT"
316+
and parentBaseImage matches /(\/bin)/
317+
and baseImage = "/usr/bin/osascript"
318+
and !isNull(dstDevice_ip)
319+
and dstDevice_ip_isInternal = false
320+
EOT
321+
limit = 1
322+
}
323+
}
324+
`, descriptionExpression)
325+
}
326+
222327
func getCSEChainRuleTestPayload() CSEChainRule {
223328
return CSEChainRule{
224329
Description: "Test description",
@@ -282,3 +387,12 @@ func testCheckCSEChainRuleValues(t *testing.T, expected *CSEChainRule, actual *C
282387
return nil
283388
}
284389
}
390+
391+
func testCheckChainRuleOverrideValues(chainRule *CSEChainRule, descriptionExpression string) resource.TestCheckFunc {
392+
return func(s *terraform.State) error {
393+
if chainRule.Description != descriptionExpression {
394+
return fmt.Errorf("bad descriptionExpression, expected \"%s\", got %#v", descriptionExpression, chainRule.Description)
395+
}
396+
return nil
397+
}
398+
}

sumologic/resource_sumologic_cse_first_seen_rule.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ func resourceSumologicCSEFirstSeenRule() *schema.Resource {
3636
},
3737
"entity_selectors": getEntitySelectorsSchema(),
3838
"filter_expression": {
39-
Type: schema.TypeString,
40-
Required: true,
39+
Type: schema.TypeString,
40+
Required: true,
41+
DiffSuppressFunc: suppressSpaceDiff,
4142
},
4243
"group_by_fields": {
4344
Type: schema.TypeList,
@@ -183,13 +184,20 @@ func resourceSumologicCSEFirstSeenRuleCreate(d *schema.ResourceData, meta interf
183184
}
184185

185186
func resourceSumologicCSEFirstSeenRuleUpdate(d *schema.ResourceData, meta interface{}) error {
187+
ruleSource := getRuleSource(d.Id(), meta)
186188
CSEFirstSeenRule, err := resourceToCSEFirstSeenRule(d)
187189
if err != nil {
188190
return err
189191
}
190192

191193
c := meta.(*Client)
192-
if err = c.UpdateCSEFirstSeenRule(CSEFirstSeenRule); err != nil {
194+
if ruleSource == "user" {
195+
err = c.UpdateCSEFirstSeenRule(CSEFirstSeenRule)
196+
} else {
197+
err = c.OverrideCSEFirstSeenRule(CSEFirstSeenRule)
198+
}
199+
200+
if err != nil {
193201
return err
194202
}
195203

0 commit comments

Comments
 (0)