Skip to content

Commit

Permalink
Merge pull request #567 from Security-Onion-Solutions/cogburn/integri…
Browse files Browse the repository at this point in the history
…ty-check-tests

Integrity Check Tests
  • Loading branch information
coreyogburn authored Jun 26, 2024
2 parents 79f39c0 + e48222a commit 8916f5d
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 52 deletions.
4 changes: 2 additions & 2 deletions server/modules/detections/integrity_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var ErrIntCheckerStopped = fmt.Errorf("integrity checker has stopped running")
var ErrIntCheckFailed = fmt.Errorf("integrity check failed; discrepancies found")

type IntegrityChecked interface {
IntegrityCheck(bool) error
IntegrityCheck(bool) ([]string, []string, error)
InterruptSync(forceFull bool, notify bool)
IsRunning() bool
}
Expand Down Expand Up @@ -56,7 +56,7 @@ func IntegrityChecker(engName model.EngineName, eng IntegrityChecked, data *Inte
continue
}

err := eng.IntegrityCheck(true)
_, _, err := eng.IntegrityCheck(true)
if err != nil {
if err != ErrIntCheckerStopped {
failCount++
Expand Down
26 changes: 13 additions & 13 deletions server/modules/elastalert/elastalert.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ func (e *ElastAlertEngine) startCommunityRuleImport() {
})
}

err = e.IntegrityCheck(false)
_, _, err = e.IntegrityCheck(false)

e.EngineState.IntegrityFailure = err != nil
lastSyncSuccess = util.Ptr(err == nil)
Expand Down Expand Up @@ -790,7 +790,7 @@ func (e *ElastAlertEngine) startCommunityRuleImport() {
})
}

err = e.IntegrityCheck(false)
_, _, err = e.IntegrityCheck(false)

e.EngineState.IntegrityFailure = err != nil
lastSyncSuccess = util.Ptr(err == nil)
Expand Down Expand Up @@ -1568,10 +1568,10 @@ func wrapRule(det *model.Detection, rule string, additionalAlerters []string) (s
return string(rawYaml), nil
}

func (e *ElastAlertEngine) IntegrityCheck(canInterrupt bool) error {
func (e *ElastAlertEngine) IntegrityCheck(canInterrupt bool) (deployedButNotEnabled []string, enabledButNotDeployed []string, err error) {
// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

logger := log.WithFields(log.Fields{
Expand All @@ -1582,37 +1582,37 @@ func (e *ElastAlertEngine) IntegrityCheck(canInterrupt bool) error {
deployed, err := e.getDeployedPublicIds()
if err != nil {
logger.WithError(err).Error("unable to get deployed publicIds")
return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

logger.WithField("deployedPublicIdsCount", len(deployed)).Debug("deployed publicIds")

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
logger.Info("integrity checker stopped")
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

ret, err := e.srv.Detectionstore.GetAllDetections(e.srv.Context, model.WithEngine(model.EngineNameElastAlert), model.WithEnabled(true))
if err != nil {
logger.WithError(err).Error("unable to query for enabled detections")
return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

enabled := make([]string, 0, len(ret))
for _, d := range ret {
enabled = append(enabled, d.PublicID)
for pid := range ret {
enabled = append(enabled, pid)
}

logger.WithField("enabledDetectionsCount", len(enabled)).Debug("enabled detections")

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
logger.Info("integrity checker stopped")
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

deployedButNotEnabled, enabledButNotDeployed, _ := detections.DiffLists(deployed, enabled)
deployedButNotEnabled, enabledButNotDeployed, _ = detections.DiffLists(deployed, enabled)

logger.WithFields(log.Fields{
"deployedButNotEnabled": deployedButNotEnabled,
Expand All @@ -1621,12 +1621,12 @@ func (e *ElastAlertEngine) IntegrityCheck(canInterrupt bool) error {

if len(deployedButNotEnabled) > 0 || len(enabledButNotDeployed) > 0 {
logger.Info("integrity check failed")
return detections.ErrIntCheckFailed
return deployedButNotEnabled, enabledButNotDeployed, detections.ErrIntCheckFailed
}

logger.Info("integrity check passed")

return nil
return deployedButNotEnabled, enabledButNotDeployed, nil
}

func (e *ElastAlertEngine) getDeployedPublicIds() (publicIds []string, err error) {
Expand Down
135 changes: 135 additions & 0 deletions server/modules/elastalert/elastalert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/security-onion-solutions/securityonion-soc/model"
"github.com/security-onion-solutions/securityonion-soc/module"
"github.com/security-onion-solutions/securityonion-soc/server"
servermock "github.com/security-onion-solutions/securityonion-soc/server/mock"
"github.com/security-onion-solutions/securityonion-soc/server/modules/detections"
"github.com/security-onion-solutions/securityonion-soc/server/modules/elastalert/mock"
"github.com/security-onion-solutions/securityonion-soc/util"
Expand Down Expand Up @@ -1138,3 +1139,137 @@ func TestBuildHttpClient(t *testing.T) {
})
}
}

func TestIntegrityCheck(t *testing.T) {
tests := []struct {
Name string
InitMock func(*mock.MockIOManager, *servermock.MockDetectionstore)
DbnE []string
EbnD []string
ExpError error
}{
{
Name: "No Rules",
InitMock: func(iom *mock.MockIOManager, detStore *servermock.MockDetectionstore) {
iom.EXPECT().ReadDir("rules/folder").Return([]fs.DirEntry{}, nil)

detStore.EXPECT().GetAllDetections(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, opts ...model.GetAllOption) (map[string]*model.Detection, error) {
expected := []string{
`query AND so_detection.engine:"elastalert"`,
`query AND so_detection.isEnabled:"true"`,
}

for i, opt := range opts {
value := opt("query", "so_")
assert.Equal(t, expected[i], value)
}

return map[string]*model.Detection{}, nil
})
},
DbnE: []string{},
EbnD: []string{},
},
{
Name: "1 Deployed, 0 Enabled",
InitMock: func(iom *mock.MockIOManager, detStore *servermock.MockDetectionstore) {
iom.EXPECT().ReadDir("rules/folder").Return([]fs.DirEntry{
&MockDirEntry{
name: "00000000-0000-0000-0000-000000000000.yml",
},
}, nil)

detStore.EXPECT().GetAllDetections(gomock.Any(), gomock.Any()).Return(map[string]*model.Detection{}, nil)
},
DbnE: []string{"00000000-0000-0000-0000-000000000000"},
EbnD: []string{},
ExpError: detections.ErrIntCheckFailed,
},
{
Name: "0 Deployed, 1 Enabled",
InitMock: func(iom *mock.MockIOManager, detStore *servermock.MockDetectionstore) {
iom.EXPECT().ReadDir("rules/folder").Return([]fs.DirEntry{}, nil)

detStore.EXPECT().GetAllDetections(gomock.Any(), gomock.Any()).Return(map[string]*model.Detection{
"00000000-0000-0000-0000-000000000000": {},
}, nil)
},
DbnE: []string{},
EbnD: []string{"00000000-0000-0000-0000-000000000000"},
ExpError: detections.ErrIntCheckFailed,
},
{
Name: "Multiple Fail",
InitMock: func(iom *mock.MockIOManager, detStore *servermock.MockDetectionstore) {
iom.EXPECT().ReadDir("rules/folder").Return([]fs.DirEntry{
&MockDirEntry{
name: "00000000-0000-0000-0000-000000000000.yml",
},
&MockDirEntry{
name: "11111111-1111-1111-1111-111111111111.yml",
},
}, nil)

detStore.EXPECT().GetAllDetections(gomock.Any(), gomock.Any()).Return(map[string]*model.Detection{
"00000000-0000-0000-0000-000000000000": {},
"22222222-2222-2222-2222-222222222222": {},
}, nil)
},
DbnE: []string{"11111111-1111-1111-1111-111111111111"},
EbnD: []string{"22222222-2222-2222-2222-222222222222"},
ExpError: detections.ErrIntCheckFailed,
},
{
Name: "Multiple Success",
InitMock: func(iom *mock.MockIOManager, detStore *servermock.MockDetectionstore) {
iom.EXPECT().ReadDir("rules/folder").Return([]fs.DirEntry{
&MockDirEntry{
name: "00000000-0000-0000-0000-000000000000.yml",
},
&MockDirEntry{
name: "11111111-1111-1111-1111-111111111111.yml",
},
}, nil)

detStore.EXPECT().GetAllDetections(gomock.Any(), gomock.Any()).Return(map[string]*model.Detection{
"00000000-0000-0000-0000-000000000000": {},
"11111111-1111-1111-1111-111111111111": {},
}, nil)
},
DbnE: []string{},
EbnD: []string{},
},
}

for _, test := range tests {
test := test
t.Run(test.Name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

detStore := servermock.NewMockDetectionstore(ctrl)
iom := mock.NewMockIOManager(ctrl)
test.InitMock(iom, detStore)

e := &ElastAlertEngine{
srv: &server.Server{
Detectionstore: detStore,
},
elastAlertRulesFolder: "rules/folder",
IOManager: iom,
}

DbnE, EbnD, err := e.IntegrityCheck(false)

if test.ExpError != nil {
assert.Error(t, err)
assert.Equal(t, err, test.ExpError)
} else {
assert.NoError(t, err)
}

assert.Equal(t, test.DbnE, DbnE)
assert.Equal(t, test.EbnD, EbnD)
})
}
}
34 changes: 17 additions & 17 deletions server/modules/strelka/strelka.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func (e *StrelkaEngine) startCommunityRuleImport() {
})
}

err = e.IntegrityCheck(false)
_, _, err = e.IntegrityCheck(false)

e.EngineState.IntegrityFailure = err != nil
lastSyncSuccess = util.Ptr(err == nil)
Expand Down Expand Up @@ -656,7 +656,7 @@ func (e *StrelkaEngine) startCommunityRuleImport() {
}
}

err = e.IntegrityCheck(false)
_, _, err = e.IntegrityCheck(false)

e.EngineState.IntegrityFailure = err != nil
lastSyncSuccess = util.Ptr(err == nil)
Expand Down Expand Up @@ -1015,10 +1015,10 @@ func (e *StrelkaEngine) GenerateUnusedPublicId(ctx context.Context) (string, err
return "", fmt.Errorf("not implemented")
}

func (e *StrelkaEngine) IntegrityCheck(canInterrupt bool) error {
func (e *StrelkaEngine) IntegrityCheck(canInterrupt bool) (deployedButNotEnabled []string, enabledButNotDeployed []string, err error) {
// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

logger := log.WithFields(log.Fields{
Expand All @@ -1030,13 +1030,13 @@ func (e *StrelkaEngine) IntegrityCheck(canInterrupt bool) error {
report, err := e.getCompilationReport()
if err != nil {
logger.WithError(err).Error("unable to get compilation report")
return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

err = e.verifyCompiledHash(report.CompiledRulesHash)
if err != nil {
logger.WithError(err).Error("compiled rules hash mismatch, this report is not for the latest compiled rules")
return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

logger.WithFields(log.Fields{
Expand All @@ -1055,46 +1055,46 @@ func (e *StrelkaEngine) IntegrityCheck(canInterrupt bool) error {

logger.WithField("failedPublicIDs", problemSample).Error("integrity check failed because some rules failed to deploy")

return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

deployed := getDeployed(report)

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

ret, err := e.srv.Detectionstore.GetAllDetections(e.srv.Context, model.WithEngine(model.EngineNameStrelka), model.WithEnabled(true))
if err != nil {
logger.WithError(err).Error("unable to query for enabled detections")
return detections.ErrIntCheckFailed
return nil, nil, detections.ErrIntCheckFailed
}

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

enabled := make([]string, 0, len(ret))
for _, d := range ret {
enabled = append(enabled, d.PublicID)
for pid := range ret {
enabled = append(enabled, pid)
}

logger.WithField("enabledDetectionsCount", len(enabled)).Debug("enabled detections")

// escape
if canInterrupt && !e.IntegrityCheckerData.IsRunning {
logger.Info("integrity checker stopped")
return detections.ErrIntCheckerStopped
return nil, nil, detections.ErrIntCheckerStopped
}

deployedButNotEnabled, enabledButNotDeployed, _ := detections.DiffLists(deployed, enabled)
deployedButNotEnabled, enabledButNotDeployed, _ = detections.DiffLists(deployed, enabled)

logger.WithFields(log.Fields{
"deployedButNotEnabled": deployedButNotEnabled,
Expand All @@ -1103,12 +1103,12 @@ func (e *StrelkaEngine) IntegrityCheck(canInterrupt bool) error {

if len(deployedButNotEnabled) > 0 || len(enabledButNotDeployed) > 0 {
logger.Info("integrity check failed")
return detections.ErrIntCheckFailed
return deployedButNotEnabled, enabledButNotDeployed, detections.ErrIntCheckFailed
}

logger.Info("integrity check passed")

return nil
return deployedButNotEnabled, enabledButNotDeployed, nil
}

func (e *StrelkaEngine) getCompilationReport() (*model.CompilationReport, error) {
Expand Down
Loading

0 comments on commit 8916f5d

Please sign in to comment.