diff --git a/tables/crowdstrike_falcon/crowdstrike_falcon.go b/tables/crowdstrike_falcon/crowdstrike_falcon.go index fb24394..b24fcb5 100644 --- a/tables/crowdstrike_falcon/crowdstrike_falcon.go +++ b/tables/crowdstrike_falcon/crowdstrike_falcon.go @@ -127,7 +127,10 @@ func HydrateCommandOutput(cmdOut string, output CrowdStrikeOutput) CrowdStrikeOu } versionRegex := regexp.MustCompile(`version\s?=\s?(\d\.\d{2}\.\d{5}\.\d)`) - output.FalconVersion = versionRegex.FindStringSubmatch(out)[1] + maybeVersion := versionRegex.FindStringSubmatch(out) + if len(maybeVersion) > 1 { + output.FalconVersion = maybeVersion[1] + } // as of 7.29, `rfm-state` is always returned on a newline, and always has a trailing comma, but might be "not set" rfmStateRegex := regexp.MustCompile(`rfm-state\s?=\s?(true|false),?`) diff --git a/tables/crowdstrike_falcon/crowdstrike_falcon_test.go b/tables/crowdstrike_falcon/crowdstrike_falcon_test.go index c8b293b..026a4b6 100644 --- a/tables/crowdstrike_falcon/crowdstrike_falcon_test.go +++ b/tables/crowdstrike_falcon/crowdstrike_falcon_test.go @@ -8,6 +8,20 @@ import ( "github.com/stretchr/testify/assert" ) +type errorOsqueryClient struct { + err error +} + +func (e errorOsqueryClient) QueryRows(query string) ([]map[string]string, error) { + return nil, e.err +} + +func (e errorOsqueryClient) QueryRow(query string) (map[string]string, error) { + return nil, e.err +} + +func (e errorOsqueryClient) Close() {} + func TestRunCrowdstrikeFalconDarwin(t *testing.T) { tests := []struct { name string @@ -66,12 +80,24 @@ func TestRunCrowdstrikeFalconDarwin(t *testing.T) { fileExist: true, wantErr: true, }, + { + name: "File stat error", + mockCmd: utils.MockCmdRunner{ + Output: "", + Err: nil, + }, + fileExist: true, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { runner := utils.Runner{Runner: tt.mockCmd} fs := utils.MockFileSystem{FileExists: tt.fileExist} + if tt.name == "File stat error" { + fs.Err = errors.New("stat failed") + } output, err := runCrowdstrikeFalconDarwin(runner, fs) if tt.wantErr { @@ -149,12 +175,24 @@ rfm-state=true,`, "SELECT 1 FROM processes WHERE name like 'falcon-sensor%';": {}, }, }, + { + name: "File stat error", + mockCmd: utils.MockCmdRunner{ + Output: "", + Err: nil, + }, + fileExist: true, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { runner := utils.Runner{Runner: tt.mockCmd} fs := utils.MockFileSystem{FileExists: tt.fileExist} + if tt.name == "File stat error" { + fs.Err = errors.New("stat failed") + } mockOsqueryClient := &utils.MockOsqueryClient{ Data: tt.mockOsqData, } @@ -192,6 +230,16 @@ rfm-state=true,`, } } +func TestRunCrowdstrikeFalconLinuxQueryError(t *testing.T) { + runner := utils.Runner{Runner: utils.MockCmdRunner{}} + fs := utils.MockFileSystem{FileExists: true} + + output, err := runCrowdstrikeFalconLinux(runner, fs, errorOsqueryClient{err: errors.New("query failed")}) + assert.Error(t, err) + assert.Empty(t, output) + assert.ErrorContains(t, err, "query failed") +} + func TestLinuxParsing(t *testing.T) { tests := []struct { name string @@ -207,6 +255,24 @@ func TestLinuxParsing(t *testing.T) { FalconVersion: "7.30.18306.0", }, }, + { + name: "Uppercase agent ID and false rfm-state", + cmdOutput: "CID=\"79391C24113773B01D8181C38C3E111A\", AID=\"F3EDC954D286243B5BD94130C2F2647D\", version=7.29.18202.0\nrfm-state=false,", + expected: CrowdStrikeOutput{ + AgentID: "f3edc954d286243b5bd94130c2f2647d", + CID: "79391c24113773b01d8181c38c3e111a", + FalconVersion: "7.29.18202.0", + }, + sensorLoaded: true, + }, + { + name: "Missing version does not panic", + cmdOutput: `cid="79391c24113773b01d8181c38c3e111a", aid="f3edc954d286243b5bd94130c2f2647d"`, + expected: CrowdStrikeOutput{ + AgentID: "f3edc954d286243b5bd94130c2f2647d", + CID: "79391c24113773b01d8181c38c3e111a", + }, + }, } for _, tt := range tests {