Skip to content

Commit cd1f2d7

Browse files
authored
Merge pull request #51 from hazcod/feat/withoutsensor
Feature: report on users without sensors/mdm
2 parents 4299dfe + bdb7f04 commit cd1f2d7

File tree

7 files changed

+158
-47
lines changed

7 files changed

+158
-47
lines changed

cmd/main.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/hazcod/crowdstrike-spotlight-slacker/pkg/overview/security"
77
"github.com/hazcod/crowdstrike-spotlight-slacker/pkg/overview/user"
88
"github.com/hazcod/crowdstrike-spotlight-slacker/pkg/ws1"
9+
"gopkg.in/errgo.v2/fmt/errors"
910
"os"
1011
"strings"
1112

@@ -49,12 +50,12 @@ func main() {
4950

5051
// ---
5152

52-
falconMessages, err := falcon.GetMessages(config, ctx)
53+
falconMessages, usersWithSensors, err := falcon.GetMessages(config, ctx)
5354
if err != nil {
5455
logrus.WithError(err).Fatal("could not get falcon messages")
5556
}
5657

57-
ws1Messages, err := ws1.GetMessages(config, ctx)
58+
ws1Messages, usersWithDevices, err := ws1.GetMessages(config, ctx)
5859
if err != nil {
5960
logrus.WithError(err).Fatal("could not get WS1 messages")
6061
}
@@ -89,7 +90,7 @@ func main() {
8990
for _, slackUser := range slackUsers {
9091
userEmail := strings.ToLower(slackUser.Profile.Email)
9192

92-
if slackUser.IsBot {
93+
if slackUser.IsBot || slackUser.Deleted {
9394
continue
9495
}
9596

@@ -102,6 +103,32 @@ func main() {
102103
numFindings += len(device.Findings)
103104
}
104105

106+
// check if every slack user has a device in MDM
107+
hasDevice := false
108+
for _, userWDevice := range usersWithDevices {
109+
if strings.EqualFold(userWDevice, userEmail) {
110+
hasDevice = true
111+
break
112+
}
113+
}
114+
115+
if !hasDevice {
116+
117+
isWhitelisted := false
118+
for _, whitelist := range config.Email.Whitelist {
119+
if strings.EqualFold(whitelist, userEmail) {
120+
isWhitelisted = true
121+
break
122+
}
123+
}
124+
125+
if !isWhitelisted {
126+
errorsToReport = append(errorsToReport, errors.Newf(
127+
"%s does not have a device in MDM nor a sensor", userEmail,
128+
))
129+
}
130+
}
131+
105132
if len(userFalconMsg.Devices) == 0 && numFindings == 0 {
106133
continue
107134
}
@@ -157,6 +184,39 @@ func main() {
157184
}
158185
}
159186

187+
// --- find users without sensors
188+
189+
for _, userWithSensor := range usersWithSensors {
190+
if strings.HasPrefix(userWithSensor, "_NOTAG/") {
191+
errorsToReport = append(errorsToReport, errors.Newf(
192+
"%s does not have a user email tag assigned", strings.Split("/", userWithSensor)[1],
193+
))
194+
}
195+
}
196+
197+
for _, userWDevice := range usersWithDevices {
198+
if strings.TrimSpace(userWDevice) == "" {
199+
continue
200+
}
201+
202+
found := false
203+
204+
for _, userWSensor := range usersWithSensors {
205+
if strings.EqualFold(userWDevice, userWSensor) {
206+
found = true
207+
break
208+
}
209+
}
210+
211+
if !found {
212+
errorsToReport = append(errorsToReport, errors.Newf(
213+
"%s does not have at least one sensor assigned", userWDevice),
214+
)
215+
}
216+
}
217+
218+
// ---
219+
160220
overviewText, err := security.BuildSecurityOverviewMessage(logrus.StandardLogger(), *config, falconMessages, ws1Messages, errorsToReport)
161221
if err != nil {
162222
logrus.WithError(err).Fatal("could not generate security overview")

config/config.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type Config struct {
1717
Token string `yaml:"token" env:"SLACK_TOKEN"`
1818
SecurityUser string `yaml:"security_user" emv:"SLACK_SECURITY_USER"`
1919

20-
SkipNoReport bool `yaml:"skip_no_report" env:"SLACK_SKIP_NO_REPORT"`
20+
SkipNoReport bool `yaml:"skip_no_report" env:"SLACK_SKIP_NO_REPORT"`
2121
SkipOnHoliday bool `yaml:"skip_on_holiday" env:"SLACK_SKIP_ON_HOLIDAY"`
2222
} `yaml:"slack"`
2323

@@ -26,31 +26,32 @@ type Config struct {
2626
Secret string `yaml:"secret" env:"FALCON_SECRET"`
2727
CloudRegion string `yaml:"cloud_region" env:"FALCON_CLOUD_REGION"`
2828

29-
SkipNoMitigation bool `yaml:"skip_no_mitigation" env:"FALCON_SKIP_NO_MITIGATION"`
30-
SkipSeverities []string `yaml:"skip_severities" env:"FALCON_SKIP_SEVERITIES"`
31-
MinCVEBaseScore int `yaml:"min_cve_base_score" env:"FALCON_MIN_CVE_BASE_SCORE"`
32-
SkipCVEs []string `yaml:"skip_cves" env:"FALCON_SKIP_CVES"`
33-
MinExprtAISeverity string `yaml:"min_exprtai_severity" env:"FALCON_MIN_EXPRTAI_SEVERITYs"`
29+
SkipNoMitigation bool `yaml:"skip_no_mitigation" env:"FALCON_SKIP_NO_MITIGATION"`
30+
SkipSeverities []string `yaml:"skip_severities" env:"FALCON_SKIP_SEVERITIES"`
31+
MinCVEBaseScore int `yaml:"min_cve_base_score" env:"FALCON_MIN_CVE_BASE_SCORE"`
32+
SkipCVEs []string `yaml:"skip_cves" env:"FALCON_SKIP_CVES"`
33+
MinExprtAISeverity string `yaml:"min_exprtai_severity" env:"FALCON_MIN_EXPRTAI_SEVERITYs"`
3434
} `yaml:"falcon"`
3535

3636
WS1 struct {
3737
Endpoint string `yaml:"api_url" env:"WS1_API_URL"`
38-
APIKey string `yaml:"api_key" env:"WS1_API_KEY"`
39-
User string `yaml:"user" env:"WS1_USER"`
38+
APIKey string `yaml:"api_key" env:"WS1_API_KEY"`
39+
User string `yaml:"user" env:"WS1_USER"`
4040
Password string `yaml:"password" env:"WS1_PASSWORD"`
4141

4242
SkipFilters []struct {
4343
Policy string `yaml:"policy"`
44-
User string `yaml:"user"`
45-
} `yaml:"skip"`
44+
User string `yaml:"user"`
45+
} `yaml:"skip"`
4646
} `yaml:"ws1"`
4747

4848
Email struct {
49-
Domains []string `yaml:"domains" env:"DOMAINS"`
49+
Domains []string `yaml:"domains" env:"DOMAINS"`
50+
Whitelist []string `yaml:"whitelist" env:"WHITELIST"`
5051
} `yaml:"email"`
5152

5253
Templates struct {
53-
UserMessage string `yaml:"user_message" env:"USER_MESSAGE"`
54+
UserMessage string `yaml:"user_message" env:"USER_MESSAGE"`
5455
SecurityOverviewMessage string `yaml:"security_overview_message" env:"SECURITY_OVERVIEW_MESSAGE"`
5556
} `yaml:"templates"`
5657
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ require (
88
github.com/pkg/errors v0.9.1
99
github.com/sirupsen/logrus v1.8.1
1010
github.com/slack-go/slack v0.10.2
11+
gopkg.in/errgo.v2 v2.1.0
1112
gopkg.in/yaml.v2 v2.4.0
1213
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
629629
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
630630
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
631631
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
632+
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
632633
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
633634
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
634635
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

pkg/falcon/extractor.go

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"fmt"
9+
"github.com/crowdstrike/gofalcon/falcon/client/hosts"
910
"github.com/pkg/errors"
1011
"strings"
1112

@@ -19,6 +20,7 @@ import (
1920
const (
2021
tagEmailPrefix = "email/"
2122
tagFalconPrefix = "FalconGroupingTags/"
23+
tagSensorPrefix = "SensorGroupingTags/"
2224
)
2325

2426
type FalconResult struct {
@@ -27,17 +29,18 @@ type FalconResult struct {
2729
}
2830

2931
type UserDevice struct {
32+
Hostname string
3033
MachineName string
3134
Tags []string
3235
Findings []UserDeviceFinding
3336
}
3437

3538
type UserDeviceFinding struct {
36-
ProductName string
37-
CveID string
38-
CveSeverity string
39-
TimestampFound string
40-
Mitigations []string
39+
ProductName string
40+
CveID string
41+
CveSeverity string
42+
TimestampFound string
43+
Mitigations []string
4144
}
4245

4346
func getUniqueDeviceID(hostInfo models.DomainAPIVulnerabilityHostFacetV2) (string, error) {
@@ -56,6 +59,7 @@ func findEmailTag(tags []string, emailDomains []string) (email string, err error
5659
for _, tag := range tags {
5760
tag = strings.ToLower(tag)
5861
tag = strings.TrimPrefix(tag, strings.ToLower(tagFalconPrefix))
62+
tag = strings.TrimPrefix(tag, strings.ToLower(tagSensorPrefix))
5963

6064
logrus.WithField("tag", tag).Trace("looking at falcon tag")
6165

@@ -75,7 +79,7 @@ func findEmailTag(tags []string, emailDomains []string) (email string, err error
7579
for _, domain := range emailDomains {
7680
encodedDomain := strings.ToLower(strings.ReplaceAll(domain, ".", "/"))
7781

78-
if ! strings.HasSuffix(email, encodedDomain) {
82+
if !strings.HasSuffix(email, encodedDomain) {
7983
continue
8084
}
8185

@@ -110,7 +114,9 @@ func appendUnique(main, adder []string) []string {
110114
}
111115
}
112116

113-
if found { continue }
117+
if found {
118+
continue
119+
}
114120

115121
main = append(main, adder[i])
116122
}
@@ -135,7 +141,7 @@ func getSeverityScore(severity string) (int, error) {
135141
return -1, errors.New("unknown severity: " + severity)
136142
}
137143

138-
func GetMessages(config *config.Config, ctx context.Context) (results map[string]FalconResult, err error) {
144+
func GetMessages(config *config.Config, ctx context.Context) (results map[string]FalconResult, usersWithSensors []string, err error) {
139145
falconAPIMaxRecords := int64(400)
140146

141147
results = map[string]FalconResult{}
@@ -147,31 +153,62 @@ func GetMessages(config *config.Config, ctx context.Context) (results map[string
147153
Context: ctx,
148154
})
149155
if err != nil {
150-
return nil, errors.Wrap(err, "could not initialize Falcon client")
156+
return nil, nil, errors.Wrap(err, "could not initialize Falcon client")
157+
}
158+
159+
hostResult, err := client.Hosts.QueryDevicesByFilter(
160+
&hosts.QueryDevicesByFilterParams{
161+
Filter: nil,
162+
Limit: &falconAPIMaxRecords,
163+
Offset: nil,
164+
Sort: nil,
165+
Context: ctx,
166+
},
167+
)
168+
if err != nil {
169+
return nil, nil, errors.Wrap(err, "could not query all hosts")
170+
}
171+
172+
hostDetail, err := client.Hosts.GetDeviceDetails(&hosts.GetDeviceDetailsParams{
173+
Ids: hostResult.Payload.Resources,
174+
Context: ctx,
175+
HTTPClient: nil,
176+
})
177+
if err != nil {
178+
return nil, nil, errors.Wrap(err, "could not query all host details")
179+
}
180+
181+
for _, detail := range hostDetail.Payload.Resources {
182+
email, err := findEmailTag(detail.Tags, config.Email.Domains)
183+
if err != nil || email == "" {
184+
email = "_NOTAG/" + detail.Hostname
185+
}
186+
187+
usersWithSensors = append(usersWithSensors, strings.ToLower(email))
151188
}
152189

153190
queryResult, err := client.SpotlightVulnerabilities.CombinedQueryVulnerabilities(
154191
&spotlight_vulnerabilities.CombinedQueryVulnerabilitiesParams{
155192
Context: ctx,
156193
Filter: "status:'open'",
157194
Limit: &falconAPIMaxRecords,
158-
Facet: []string{"host_info", "cve", "remediation"},
195+
Facet: []string{"host_info", "cve", "remediation"},
159196
},
160197
)
161198
if err != nil {
162-
return nil, errors.Wrap(err, "could not query vulnerabilities")
199+
return nil, nil, errors.Wrap(err, "could not query vulnerabilities")
163200
}
164201

165202
if queryResult == nil {
166-
return nil, errors.New("QueryVulnerabilities result was nil")
203+
return nil, nil, errors.New("QueryVulnerabilities result was nil")
167204
}
168205

169206
var hostTags []string
170207
devices := map[string]UserDevice{}
171208

172209
minExpertAIScore := 0
173210
if newScore, err := getSeverityScore(config.Falcon.MinExprtAISeverity); err != nil {
174-
return nil, errors.Wrap(err, "unknown minimum exprtai severity specified")
211+
return nil, nil, errors.Wrap(err, "unknown minimum exprtai severity specified")
175212
} else {
176213
minExpertAIScore = newScore
177214
}
@@ -279,6 +316,7 @@ func GetMessages(config *config.Config, ctx context.Context) (results map[string
279316

280317
if _, ok := devices[uniqueDeviceID]; !ok {
281318
devices[uniqueDeviceID] = UserDevice{
319+
Hostname: *vuln.HostInfo.Hostname,
282320
MachineName: fmt.Sprintf(
283321
"%s %s",
284322
*vuln.HostInfo.OsVersion,
@@ -313,11 +351,11 @@ func GetMessages(config *config.Config, ctx context.Context) (results map[string
313351
}
314352

315353
if len(devices) == 0 {
316-
return results, nil
354+
return results, nil, nil
317355
}
318356

319357
if len(hostTags) == 0 {
320-
return nil, errors.New("no tags found on decices")
358+
return nil, nil, errors.New("no tags found on decices")
321359
}
322360

323361
logrus.WithField("devices", len(devices)).Info("found vulnerable devices")
@@ -335,7 +373,7 @@ func GetMessages(config *config.Config, ctx context.Context) (results map[string
335373
}
336374
}
337375

338-
if !hasMitigations{
376+
if !hasMitigations {
339377
logrus.WithField("device", device.MachineName).
340378
Debug("skipping device with vulnerabilities but no mitigations")
341379
continue
@@ -366,5 +404,5 @@ func GetMessages(config *config.Config, ctx context.Context) (results map[string
366404
results[userEmail] = user
367405
}
368406

369-
return results, nil
407+
return results, usersWithSensors, nil
370408
}

pkg/overview/security/builder.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ func BuildSecurityOverviewMessage(logger *logrus.Logger, config config.Config, f
4040
logrus.Debugf("findings: falcon: %d ws1: %d", len(allFalcon), len(allWS1))
4141

4242
variables := struct {
43-
Falcon []falcon.FalconResult
44-
WS1 []ws1.WS1Result
45-
Date time.Time
46-
Errors []error
43+
Falcon []falcon.FalconResult
44+
WS1 []ws1.WS1Result
45+
Date time.Time
46+
Errors []error
47+
MissingSensor []ws1.UserDevice
4748
}{
4849
Date: time.Now(),
4950
Falcon: allFalcon,
5051
WS1: allWS1,
5152
Errors: reportedErrors,
53+
//MissingSensor: devicesWithoutFalcon,
5254
}
5355

5456
var buffer bytes.Buffer

0 commit comments

Comments
 (0)