From 9f9b623e5842929f9f42e2743e8fa48f90fa42f0 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 27 Aug 2024 12:40:01 -0400 Subject: [PATCH] custom notify sets --- server/modules/elastalert/elastalert.go | 27 +++- server/modules/elastalert/elastalert_test.go | 149 +++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/server/modules/elastalert/elastalert.go b/server/modules/elastalert/elastalert.go index f2b085d7..52f3df81 100644 --- a/server/modules/elastalert/elastalert.go +++ b/server/modules/elastalert/elastalert.go @@ -119,6 +119,7 @@ type ElastAlertEngine struct { aiRepoUrl string aiRepoBranch string aiRepoPath string + moduleConfig *module.ModuleConfig detections.SyncSchedulerParams detections.IntegrityCheckerData detections.IOManager @@ -190,6 +191,8 @@ func (e *ElastAlertEngine) Init(config module.ModuleConfig) (err error) { e.highSeverityAlerterParams = module.GetStringDefault(config, "additionalSev4AlertersParams", "") e.criticalSeverityAlerters = module.GetStringArrayDefault(config, "additionalSev5Alerters", []string{}) e.criticalSeverityAlerterParams = module.GetStringDefault(config, "additionalSev5AlertersParams", "") + e.moduleConfig = &config + e.IntegrityCheckerData.FrequencySeconds = module.GetIntDefault(config, "integrityCheckFrequencySeconds", DEFAULT_INTEGRITY_CHECK_FREQUENCY_SECONDS) pkgs := module.GetStringArrayDefault(config, "sigmaRulePackages", []string{"core", "emerging_threats_addon"}) @@ -1578,6 +1581,25 @@ func (e *ElastAlertEngine) MergeAuxiliaryData(detect *model.Detection) error { return nil } +func (e *ElastAlertEngine) getCustomAlerters(tags []string) ([]string, string) { + alertersKey := "" + paramsKey := "" + if e.moduleConfig != nil { + for _, tag := range tags { + if strings.HasPrefix(tag, "so.alerters.") { + alertersKey = strings.TrimPrefix(tag, "so.alerters.") + } + if strings.HasPrefix(tag, "so.params.") { + paramsKey = strings.TrimPrefix(tag, "so.params.") + } + } + alerters := module.GetStringArrayDefault(*e.moduleConfig, alertersKey, []string{}) + params := module.GetStringDefault(*e.moduleConfig, paramsKey, "") + return alerters, params + } + return []string{}, "" +} + func (e *ElastAlertEngine) getAdditionalAlerters(severity int) ([]string, string) { // Start with default alerters alerters := e.additionalAlerters @@ -1782,7 +1804,10 @@ func (e *ElastAlertEngine) wrapRule(det *model.Detection, rule string) (string, model.SeverityCritical: 5, } - alerters, params := e.getAdditionalAlerters(severities[det.Severity]) + alerters, params := e.getCustomAlerters(det.Tags) + if len(alerters) == 0 { + alerters, params = e.getAdditionalAlerters(severities[det.Severity]) + } sevNum := severities[det.Severity] realert := TimeFrame{} diff --git a/server/modules/elastalert/elastalert_test.go b/server/modules/elastalert/elastalert_test.go index cd2c76fc..69a66181 100644 --- a/server/modules/elastalert/elastalert_test.go +++ b/server/modules/elastalert/elastalert_test.go @@ -452,6 +452,155 @@ foo: bar assert.YAMLEq(t, expected, wrappedRule) } +func TestSigmaToElastAlertCustomNotificationLicensed(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + iom := mock.NewMockIOManager(ctrl) + + iom.EXPECT().ExecCommand(gomock.Cond(func(x any) bool { + cmd := x.(*exec.Cmd) + + if !strings.HasSuffix(cmd.Path, "sigma") { + return false + } + + if !slices.Contains(cmd.Args, "convert") { + return false + } + + if cmd.Stdin == nil { + return false + } + + return true + })).Return([]byte(""), 0, time.Duration(0), nil) + + config := make(module.ModuleConfig) + alerters := make([]interface{}, 0) + alerters = append(alerters, "post2") + alerters = append(alerters, "pagerduty") + config["MyAlerters"] = alerters + config["MyParams"] = "foo: car" + + engine := ElastAlertEngine{ + IOManager: iom, + additionalAlerters: []string{"email", "slack"}, + additionalAlerterParams: "foo: bar", + moduleConfig: &config, + } + + det := &model.Detection{ + PublicID: "00000000-0000-0000-0000-000000000000", + Content: "totally good sigma", + Title: "Test Detection", + Tags: []string{"so.alerters.MyAlerters", "so.params.MyParams"}, + Severity: model.SeverityHigh, + } + + query, err := engine.sigmaToElastAlert(context.Background(), det) + assert.NoError(t, err) + + // License + licensing.Test(licensing.FEAT_NTF, 0, 0, "", "") + wrappedRule, err := engine.wrapRule(det, query) + assert.NoError(t, err) + + expected := `detection_title: Test Detection +detection_public_id: 00000000-0000-0000-0000-000000000000 +event.module: sigma +event.dataset: sigma.alert +event.severity: 4 +sigma_level: high +alert: + - modules.so.securityonion-es.SecurityOnionESAlerter + - post2 + - pagerduty +index: .ds-logs-* +name: Test Detection -- 00000000-0000-0000-0000-000000000000 +type: any +realert: + seconds: 0 +filter: + - eql: +foo: car +` + assert.YAMLEq(t, expected, wrappedRule) +} + +func TestSigmaToElastAlertCustomNotificationUnlicensed(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + iom := mock.NewMockIOManager(ctrl) + + iom.EXPECT().ExecCommand(gomock.Cond(func(x any) bool { + cmd := x.(*exec.Cmd) + + if !strings.HasSuffix(cmd.Path, "sigma") { + return false + } + + if !slices.Contains(cmd.Args, "convert") { + return false + } + + if cmd.Stdin == nil { + return false + } + + return true + })).Return([]byte(""), 0, time.Duration(0), nil) + + config := make(module.ModuleConfig) + alerters := make([]interface{}, 0) + alerters = append(alerters, "post2") + alerters = append(alerters, "pagerduty") + config["MyAlerters"] = alerters + config["MyParams"] = "foo: car" + + engine := ElastAlertEngine{ + IOManager: iom, + additionalAlerters: []string{"email", "slack"}, + additionalAlerterParams: "foo: bar", + moduleConfig: &config, + } + + det := &model.Detection{ + PublicID: "00000000-0000-0000-0000-000000000000", + Content: "totally good sigma", + Title: "Test Detection", + Tags: []string{"so.alerters.MyAlerters", "so.params.MyParams"}, + Severity: model.SeverityHigh, + } + + query, err := engine.sigmaToElastAlert(context.Background(), det) + assert.NoError(t, err) + + // License + licensing.Shutdown() + wrappedRule, err := engine.wrapRule(det, query) + assert.NoError(t, err) + + expected := `detection_title: Test Detection +detection_public_id: 00000000-0000-0000-0000-000000000000 +event.module: sigma +event.dataset: sigma.alert +event.severity: 4 +sigma_level: high +alert: + - modules.so.securityonion-es.SecurityOnionESAlerter +index: .ds-logs-* +name: Test Detection -- 00000000-0000-0000-0000-000000000000 +type: any +realert: + seconds: 0 +filter: + - eql: +` + assert.YAMLEq(t, expected, wrappedRule) +} + func TestSigmaToElastAlertNotificationOnlyLicensed(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()