Skip to content

Commit

Permalink
Merge pull request #677 from Security-Onion-Solutions/2.4/sigma-autoe…
Browse files Browse the repository at this point in the history
…nable

Flexible autoenable sigma
  • Loading branch information
defensivedepth authored Nov 21, 2024
2 parents 1fdf18a + e005429 commit bab76b0
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 9 deletions.
98 changes: 89 additions & 9 deletions server/modules/elastalert/elastalert.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ const (
DEFAULT_AUTO_UPDATE_ENABLED = false
)

type RuleCriteria struct {
Ruleset []string `yaml:"ruleset" json:"ruleset"`
Level []string `yaml:"level" json:"level"`
Product []string `yaml:"product" json:"product"`
Category []string `yaml:"category" json:"category"`
Service []string `yaml:"service" json:"service"`
}

var ( // treat as constant
DEFAULT_RULES_REPOS = []*model.RuleRepo{
{
Expand Down Expand Up @@ -96,6 +104,7 @@ type ElastAlertEngine struct {
sigmaPipelinesFingerprintFile string
sigmaRulePackages []string
autoEnabledSigmaRules []string
enabledSigmaRules []RuleCriteria
additionalAlerters []string
additionalAlerterParams string
informationalSeverityAlerters []string
Expand Down Expand Up @@ -128,20 +137,90 @@ type ElastAlertEngine struct {
model.EngineState
}

func loadEnabledSigmaRules(config module.ModuleConfig) []RuleCriteria {
defaultRuleFilters := []RuleCriteria{
{
Ruleset: []string{"securityonion-resources"},
Level: []string{"critical", "high"},
Product: []string{"*"},
Category: []string{"*"},
Service: []string{"*"},
},
{
Ruleset: []string{"core"},
Level: []string{"critical"},
Product: []string{"*"},
Category: []string{"process_creation", "file_event", "registry_event", "network_connection", "dns_query"},
Service: []string{"*"},
},
}

rawRuleFilters, ok := config["enabledSigmaRules"]
if !ok {
log.Info("enabledSigmaRules not found in config, using defaults.")
return defaultRuleFilters
}

var configData []RuleCriteria
err := yaml.Unmarshal([]byte(rawRuleFilters.(string)), &configData)
if err != nil {
log.WithError(err).Error("Failed to unmarshal YAML data for enabledSigmaRules")
return defaultRuleFilters
}

// Use the parsed filters if available, otherwise return defaults
if len(configData) > 0 {
return configData
}

return defaultRuleFilters
}

// Function to check if a rule should be enabled based on criteria
func checkRulesetEnabled(e *ElastAlertEngine, det *model.Detection) {
det.IsEnabled = false
if det.Ruleset == "" || det.Severity == "" {
return

if len(e.autoEnabledSigmaRules) != 0 {

// Deprecated in 2.4.120, will be removed in a future release
log.Warn("Use of autoEnabledSigmaRules is deprecated, use enabledSigmaRules instead")
// Combine Ruleset and Severity into a single string
metaCombined := det.Ruleset + "+" + string(det.Severity)
for _, rule := range e.autoEnabledSigmaRules {
if strings.EqualFold(rule, metaCombined) {
det.IsEnabled = true
break
}
}

} else {

for _, rule := range e.enabledSigmaRules {
if matchArrayField(rule.Ruleset, det.Ruleset) &&
matchArrayField(rule.Level, string(det.Severity)) &&
matchArrayField(rule.Product, det.Product) &&
matchArrayField(rule.Category, det.Category) &&
matchArrayField(rule.Service, det.Service) {
det.IsEnabled = true
break
}
}
}
}

// Combine Ruleset and Severity into a single string
metaCombined := det.Ruleset + "+" + string(det.Severity)
for _, rule := range e.autoEnabledSigmaRules {
if strings.EqualFold(rule, metaCombined) {
det.IsEnabled = true
break
// Helper to match array fields with wildcard support
func matchArrayField(configValues []string, ruleValue string) bool {
ruleValue = strings.TrimSpace(strings.ToLower(ruleValue))
if len(configValues) == 0 {
return true
}
for _, value := range configValues {
value = strings.TrimSpace(strings.ToLower(value))
if value == "*" || value == ruleValue {
return true
}
}
return false
}

func NewElastAlertEngine(srv *server.Server) *ElastAlertEngine {
Expand Down Expand Up @@ -178,7 +257,8 @@ func (e *ElastAlertEngine) Init(config module.ModuleConfig) (err error) {
e.sigmaPipelineSO = module.GetStringDefault(config, "sigmaPipelineSO", DEFAULT_SIGMA_PIPELINE_SO_FILE)
e.sigmaPipelinesFingerprintFile = module.GetStringDefault(config, "sigmaPipelinesFingerprintFile", DEFAULT_SIGMA_PIPELINES_FINGERPRINT_FILE)
e.rulesFingerprintFile = module.GetStringDefault(config, "rulesFingerprintFile", DEFAULT_RULES_FINGERPRINT_FILE)
e.autoEnabledSigmaRules = module.GetStringArrayDefault(config, "autoEnabledSigmaRules", []string{"securityonion-resources+critical", "securityonion-resources+high"})
e.enabledSigmaRules = loadEnabledSigmaRules(config)
e.autoEnabledSigmaRules = module.GetStringArrayDefault(config, "autoEnabledSigmaRules", []string{})
e.CommunityRulesImportErrorSeconds = module.GetIntDefault(config, "communityRulesImportErrorSeconds", DEFAULT_COMMUNITY_RULES_IMPORT_ERROR_SECS)
e.failAfterConsecutiveErrorCount = module.GetIntDefault(config, "failAfterConsecutiveErrorCount", DEFAULT_FAIL_AFTER_CONSECUTIVE_ERROR_COUNT)
e.additionalAlerters = module.GetStringArrayDefault(config, "additionalAlerters", []string{})
Expand Down
51 changes: 51 additions & 0 deletions server/modules/elastalert/elastalert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,57 @@ func TestCheckAutoEnabledSigmaRule(t *testing.T) {
}
}

func TestCheckEnabledSigmaRule(t *testing.T) {
e := &ElastAlertEngine{
enabledSigmaRules: []RuleCriteria{
{
Ruleset: []string{"securityonion-resources", "core"},
Level: []string{"high"},
Product: []string{"windows"},
Category: []string{"process_creation"},
Service: []string{"sysmon"},
},
{
Ruleset: []string{"*"},
Level: []string{"critical"},
Product: []string{"*"},
Category: []string{"*"},
Service: []string{"*"},
},
},
}

tests := []struct {
name string
ruleset string
severity model.Severity
product string
category string
service string
expected bool
}{
{"core rule with matching fields and upper case, rule enabled", "core", model.SeverityHigh, "WINDOWS", "process_creation", "sysmon", true},
{"core rule with wrong category, rule disabled", "core", model.SeverityHigh, "windows", "file_creation", "windows", false},
{"securityonion-resources rule with matching fields, rule enabled", "securityonion-resources", model.SeverityHigh, "windows", "process_creation", "sysmon", true},
{"core++ rule with critical severity, rule enabled", "core++", model.SeverityCritical, "linux", "file_event", "auditd", true},
{"core++ rule with medium severity, rule disabled", "core++", model.SeverityMedium, "windows", "process_creation", "sysmon", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
det := &model.Detection{
Ruleset: tt.ruleset,
Severity: tt.severity,
Product: tt.product,
Category: tt.category,
Service: tt.service,
}
checkRulesetEnabled(e, det)
assert.Equal(t, tt.expected, det.IsEnabled)
})
}
}

func TestElastAlertModule(t *testing.T) {
srv := &server.Server{
DetectionEngines: map[model.EngineName]server.DetectionEngine{},
Expand Down

0 comments on commit bab76b0

Please sign in to comment.