Skip to content

Commit

Permalink
Merge pull request #399 from Security-Onion-Solutions/cogburn/stop-en…
Browse files Browse the repository at this point in the history
…gines

Better Behavior around Ctrl+C
  • Loading branch information
coreyogburn authored Mar 27, 2024
2 parents 9ae08c0 + e634c97 commit 422cd57
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 24 deletions.
22 changes: 10 additions & 12 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1405,13 +1405,15 @@ <h3 class="text--primary">{{ i18n.commentAddDetection }}</h3>
7. Custom Filter - engine:elastalert
-->
<!-- isEnabled -->
<div @click="startOverrideEdit('override-enabled', item, 'isEnabled')" :class="{ clicktoedit: !isOverrideEdit('override-enabled') }">
<div @click="startOverrideEdit('override-enabled', item, 'isEnabled')" :class="{ clicktoedit: !isOverrideEdit('override-enabled') }" data-aid="detection_override_enabled_toggle">
<div class="font-weight-bold mt-4">
{{i18n.enabled}}:
</div>
<span id="override-enabled" v-if="!isOverrideEdit('override-enabled')" data-aid="detection_override_enabled_display">
{{item.isEnabled}}
</span>
<v-checkbox v-else id="override-enabled-edit" ref="override-enabled" hide-details="auto" outlined v-model="item.isEnabled"
persistent-hint :hint="i18n.enabled" v-on:change="stopOverrideEdit(true)"></v-checkbox>
</div>
<!-- regex -->
<div v-if="item.type === 'modify'" @click="startOverrideEdit('override-regex', item, 'regex')" :class="{ clicktoedit: !isOverrideEdit('override-regex') }">
Expand Down Expand Up @@ -1501,16 +1503,18 @@ <h3 class="text--primary">{{ i18n.commentAddDetection }}</h3>
</td>
<td :colspan="headers.length" class="pa-4" v-else-if="detect.engine === 'elastalert'">
<!-- isEnabled -->
<div @click="startOverrideEdit('override-enabled', item, 'isEnabled')" class="clicktoedit">
<div @click="startOverrideEdit('override-enabled', item, 'isEnabled')" :class="{ clicktoedit: !isOverrideEdit('override-enabled') }" data-aid="detection_override_enabled_toggle">
<div class="font-weight-bold mt-2">
{{i18n.enabled}}:
</div>
<span id="override-enabled" v-if="!isOverrideEdit('override-enabled')" data-aid="detection_override_enabled_display">
{{item.isEnabled}}
</span>
<v-checkbox v-else id="override-enabled-edit" ref="override-enabled" hide-details="auto" outlined v-model="item.isEnabled"
persistent-hint :hint="i18n.enabled" v-on:change="stopOverrideEdit(true)"></v-checkbox>
</div>
<!-- custom filter -->
<div @click="startOverrideEdit('override-custom-filter', item, 'customFilter')" class="clicktoedit">
<div @click="startOverrideEdit('override-custom-filter', item, 'customFilter')" :class="{ clicktoedit: !isOverrideEdit('override-custom-filter') }">
<div class="font-weight-bold mt-2">
{{i18n.customFilter}}:
</div>
Expand Down Expand Up @@ -1736,23 +1740,17 @@ <h3 class="text--primary">{{ i18n.commentAddDetection }}</h3>
<h3>{{ i18n.operations }}</h3>
</v-expansion-panel-header>
<v-expansion-panel-content>
<span class="clicktoedit">
<span @click="startEdit('detection-enabled', 'isEnabled')" :class="{ clicktoedit: !isEdit('detection-enabled') }">
<div class="ops-header">
{{ i18n.status }}:
</div>
<div class="ops-value" data-aid="detection_metadata_enabled_toggle">
<span @click="startEdit('detection-enabled', 'isEnabled')" v-if="!isEdit('detection-enabled')">
<span v-if="!isEdit('detection-enabled')">
{{ detect.isEnabled ? i18n.enabled : i18n.disabled }}
</span>
<v-checkbox id="detection-enabled-edit" v-model="detect.isEnabled" v-else @change="stopEdit(true)" />
</div>
</span>
<!-- <div class="ops-header">
Related Playbooks:
</div>
<div class="ops-value">
TODO
</div> -->
<div id="ops-buttons">
<v-btn id="detection-duplicate" color="primary" @click="duplicateDetection()" data-aid="detection_metadata_duplicate">
{{i18n.duplicate}}
Expand All @@ -1764,7 +1762,7 @@ <h3>{{ i18n.operations }}</h3>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel v-if="!isNew()" data-aid="detection_metadata_details">
<v-expansion-panel-header id="detection-related-playbooks">
<v-expansion-panel-header id="detection-extracted-details">
<h3>{{ i18n.details }}</h3>
</v-expansion-panel-header>
<v-expansion-panel-content>
Expand Down
1 change: 1 addition & 0 deletions server/detectionengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ type DetectionEngine interface {
SyncLocalDetections(ctx context.Context, detections []*model.Detection) (errMap map[string]string, err error)
ConvertRule(ctx context.Context, detect *model.Detection) (string, error)
ExtractDetails(detect *model.Detection) error
InterruptSleep()
}
77 changes: 74 additions & 3 deletions server/modules/elastalert/elastalert.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
"gopkg.in/yaml.v3"
)

var errModuleStopped = fmt.Errorf("module has stopped running")
var errModuleStopped = fmt.Errorf("elastalert module has stopped running")

var acceptedExtensions = map[string]bool{
".yml": true,
Expand Down Expand Up @@ -65,6 +65,8 @@ type ElastAlertEngine struct {
reposFolder string
isRunning bool
thread *sync.WaitGroup
interrupt chan struct{}
interm sync.Mutex
allowRegex *regexp.Regexp
denyRegex *regexp.Regexp
autoUpdateEnabled bool
Expand All @@ -84,6 +86,7 @@ func (e *ElastAlertEngine) PrerequisiteModules() []string {

func (e *ElastAlertEngine) Init(config module.ModuleConfig) (err error) {
e.thread = &sync.WaitGroup{}
e.interrupt = make(chan struct{}, 1)

e.communityRulesImportFrequencySeconds = module.GetIntDefault(config, "communityRulesImportFrequencySeconds", 86400)
e.sigmaPackageDownloadTemplate = module.GetStringDefault(config, "sigmaPackageDownloadTemplate", "https://github.com/SigmaHQ/sigma/releases/latest/download/sigma_%s.zip")
Expand Down Expand Up @@ -139,10 +142,30 @@ func (e *ElastAlertEngine) Start() error {

func (e *ElastAlertEngine) Stop() error {
e.isRunning = false
e.InterruptSleep()
e.thread.Wait()

return nil
}

func (e *ElastAlertEngine) InterruptSleep() {
e.interm.Lock()
defer e.interm.Unlock()

if len(e.interrupt) == 0 {
e.interrupt <- struct{}{}
}
}

func (e *ElastAlertEngine) resetInterrupt() {
e.interm.Lock()
defer e.interm.Unlock()

if len(e.interrupt) != 0 {
<-e.interrupt
}
}

func (e *ElastAlertEngine) IsRunning() bool {
return e.isRunning
}
Expand Down Expand Up @@ -308,9 +331,17 @@ func (e *ElastAlertEngine) startCommunityRuleImport() {
templateFound := false

for e.isRunning {
time.Sleep(time.Duration(e.communityRulesImportFrequencySeconds) * time.Second)
e.resetInterrupt()

timer := time.NewTimer(time.Second * time.Duration(e.communityRulesImportFrequencySeconds))

select {
case <-timer.C:
case <-e.interrupt:
}

if !e.isRunning {
return
break
}

start := time.Now()
Expand Down Expand Up @@ -387,18 +418,30 @@ func (e *ElastAlertEngine) startCommunityRuleImport() {
}
}

if !e.isRunning {
break
}

detections, errMap := e.parseZipRules(zips)
if errMap != nil {
log.WithField("error", errMap).Error("something went wrong while parsing sigma rule files from zips")
continue
}

if errMap["module"] == errModuleStopped || !e.isRunning {
break
}

repoDets, errMap := e.parseRepoRules(allRepos)
if errMap != nil {
log.WithField("error", errMap).Error("something went wrong while parsing sigma rule files from repos")
continue
}

if errMap["module"] == errModuleStopped || !e.isRunning {
break
}

detections = append(detections, repoDets...)

errMap, err = e.syncCommunityDetections(ctx, detections)
Expand Down Expand Up @@ -460,6 +503,10 @@ func (e *ElastAlertEngine) updateRepos() (allRepos map[string]*module.RuleRepo,

// pull or clone repos
for _, repo := range e.rulesRepos {
if !e.isRunning {
return nil, false, errModuleStopped
}

parser, err := url.Parse(repo.Repo)
if err != nil {
log.WithError(err).WithField("repo", repo).Error("Failed to parse repo URL, doing nothing with it")
Expand Down Expand Up @@ -529,13 +576,21 @@ func (e *ElastAlertEngine) parseZipRules(pkgZips map[string][]byte) (detections
}()

for pkg, zipData := range pkgZips {
if !e.isRunning {
return nil, map[string]error{"module": errModuleStopped}
}

reader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
if err != nil {
errMap[pkg] = err
continue
}

for _, file := range reader.File {
if !e.isRunning {
return nil, map[string]error{"module": errModuleStopped}
}

if file.FileInfo().IsDir() || !acceptedExtensions[strings.ToLower(filepath.Ext(file.Name))] {
continue
}
Expand Down Expand Up @@ -590,6 +645,10 @@ func (e *ElastAlertEngine) parseRepoRules(allRepos map[string]*module.RuleRepo)
}()

for repopath, repo := range allRepos {
if !e.isRunning {
return nil, map[string]error{"module": errModuleStopped}
}

baseDir := repopath
if repo.Folder != nil {
baseDir = filepath.Join(baseDir, *repo.Folder)
Expand All @@ -601,6 +660,10 @@ func (e *ElastAlertEngine) parseRepoRules(allRepos map[string]*module.RuleRepo)
return nil
}

if !e.isRunning {
return errModuleStopped
}

if d.IsDir() {
return nil
}
Expand Down Expand Up @@ -671,6 +734,10 @@ func (e *ElastAlertEngine) syncCommunityDetections(ctx context.Context, detectio
errMap = map[string]error{} // map[publicID]error

for _, det := range detections {
if !e.isRunning {
return nil, errModuleStopped
}

path, ok := index[det.PublicID]
if !ok {
path = index[det.Title]
Expand Down Expand Up @@ -741,6 +808,10 @@ func (e *ElastAlertEngine) syncCommunityDetections(ctx context.Context, detectio
}

for publicId := range toDelete {
if !e.isRunning {
return nil, errModuleStopped
}

_, err = e.srv.Detectionstore.DeleteDetection(ctx, community[publicId].Id)
if err != nil {
errMap[publicId] = fmt.Errorf("unable to delete detection: %s", err)
Expand Down
5 changes: 4 additions & 1 deletion server/modules/elastalert/elastalert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ level: high
"all_rules": buf.Bytes(),
}

engine := ElastAlertEngine{}
engine := ElastAlertEngine{
isRunning: true,
}
engine.allowRegex = regexp.MustCompile("00000000-0000-0000-0000-00000000")
engine.denyRegex = regexp.MustCompile("deny")

Expand Down Expand Up @@ -424,6 +426,7 @@ level: high
mio.EXPECT().ReadFile(gomock.Eq("rules/so_soc_failed_login.yml")).Return([]byte(data), nil)

engine := ElastAlertEngine{
isRunning: true,
IOManager: mio,
}
engine.allowRegex = regexp.MustCompile("bf86ef21-41e6-417b-9a05-b9ea6bf28a38")
Expand Down
Loading

0 comments on commit 422cd57

Please sign in to comment.