Skip to content

Commit 38cc025

Browse files
kylos101ona-agent
andcommitted
Detects, classifies, and penalizes
Co-authored-by: Ona <[email protected]>
1 parent 73e67ed commit 38cc025

File tree

6 files changed

+536
-144
lines changed

6 files changed

+536
-144
lines changed

components/ee/agent-smith/pkg/agent/agent.go

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,22 +149,15 @@ func NewAgentSmith(cfg config.Config) (*Smith, error) {
149149
WorkingArea: cfg.FilesystemScanning.WorkingArea,
150150
}
151151

152-
filesystemDetec, err = detector.NewFilesystemDetector(fsConfig)
153-
if err != nil {
154-
log.WithError(err).Warn("failed to create filesystem detector")
155-
}
156-
157-
// Use the same signature classifier for filesystem signatures
158-
if sigClassifier, ok := class.(*classifier.SignatureMatchClassifier); ok {
159-
filesystemClass = sigClassifier
160-
} else if composite, ok := class.(classifier.CompositeClassifier); ok {
161-
// Look for SignatureMatchClassifier in composite
162-
for _, c := range composite {
163-
if sigClassifier, ok := c.(*classifier.SignatureMatchClassifier); ok {
164-
filesystemClass = sigClassifier
165-
break
166-
}
152+
// Check if the main classifier supports filesystem detection
153+
if fsc, ok := class.(classifier.FilesystemClassifier); ok {
154+
filesystemClass = fsc
155+
filesystemDetec, err = detector.NewFilesystemDetector(fsConfig, filesystemClass)
156+
if err != nil {
157+
log.WithError(err).Warn("failed to create filesystem detector")
167158
}
159+
} else {
160+
log.Warn("classifier does not support filesystem detection, filesystem scanning disabled")
168161
}
169162
}
170163

@@ -327,11 +320,14 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
327320
go func() {
328321
defer wg.Done()
329322
for file := range fli {
330-
class, err := agent.filesystemClassifier.MatchesFilesystemFile(file.Path)
323+
log.Infof("Classifying filesystem file: %s", file.Path)
324+
class, err := agent.filesystemClassifier.MatchesFile(file.Path)
331325
// Early out for no matches
332326
if err == nil && class.Level == classifier.LevelNoMatch {
327+
log.Infof("File classification: no match - %s", file.Path)
333328
continue
334329
}
330+
log.Infof("File classification result: %s (level: %s, err: %v)", file.Path, class.Level, err)
335331
flo <- classifiedFilesystemFile{F: file, C: class, Err: err}
336332
}
337333
}()
@@ -399,15 +395,18 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
399395
},
400396
})
401397
case fileClass := <-flo:
398+
log.Infof("Received classified file from flo channel")
402399
file, cl, err := fileClass.F, fileClass.C, fileClass.Err
403400
if err != nil {
404401
log.WithError(err).WithFields(log.OWI(file.Workspace.OwnerID, file.Workspace.WorkspaceID, file.Workspace.InstanceID)).WithField("path", file.Path).Error("cannot classify filesystem file")
405402
continue
406403
}
407404
if cl == nil || cl.Level == classifier.LevelNoMatch {
405+
log.Warn("filesystem signature not detected", "path", file.Path, "workspace", file.Workspace.WorkspaceID)
408406
continue
409407
}
410408

409+
log.Info("filesystem signature detected", "path", file.Path, "workspace", file.Workspace.WorkspaceID, "severity", cl.Level, "message", cl.Message)
411410
_, _ = agent.Penalize(InfringingWorkspace{
412411
SupervisorPID: file.Workspace.PID,
413412
Owner: file.Workspace.OwnerID,

components/ee/agent-smith/pkg/classifier/classifier.go

Lines changed: 123 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"io"
1111
"io/fs"
1212
"os"
13-
"path/filepath"
1413
"regexp"
1514
"strings"
1615

@@ -53,7 +52,8 @@ type ProcessClassifier interface {
5352
type FilesystemClassifier interface {
5453
prometheus.Collector
5554

56-
MatchesFilesystemFile(filePath string) (*Classification, error)
55+
MatchesFile(filePath string) (*Classification, error)
56+
GetFileSignatures() []*Signature
5757
}
5858

5959
func NewCommandlineClassifier(name string, level Level, allowList []string, blockList []string) (*CommandlineClassifier, error) {
@@ -252,8 +252,8 @@ func (sigcl *SignatureMatchClassifier) Matches(executable string, cmdline []stri
252252
return sigNoMatch, nil
253253
}
254254

255-
// MatchesFilesystemFile checks if a filesystem file matches any filesystem signatures
256-
func (sigcl *SignatureMatchClassifier) MatchesFilesystemFile(filePath string) (c *Classification, err error) {
255+
// MatchesFile checks if a filesystem file matches any filesystem signatures
256+
func (sigcl *SignatureMatchClassifier) MatchesFile(filePath string) (c *Classification, err error) {
257257
// Only check signatures with DomainFileSystem
258258
filesystemSignatures := make([]*Signature, 0)
259259
for _, sig := range sigcl.Signatures {
@@ -266,28 +266,10 @@ func (sigcl *SignatureMatchClassifier) MatchesFilesystemFile(filePath string) (c
266266
return sigNoMatch, nil
267267
}
268268

269-
// Check filename matching first (cheapest check)
270-
filename := filepath.Base(filePath)
271-
matchingSignatures := make([]*Signature, 0)
272-
273-
for _, sig := range filesystemSignatures {
274-
if len(sig.Filename) == 0 {
275-
// No filename restriction, include all signatures
276-
matchingSignatures = append(matchingSignatures, sig)
277-
} else {
278-
// Check if filename matches any of the signature filenames
279-
for _, sigFilename := range sig.Filename {
280-
if matched, _ := filepath.Match(sigFilename, filename); matched {
281-
matchingSignatures = append(matchingSignatures, sig)
282-
break
283-
}
284-
}
285-
}
286-
}
287-
288-
if len(matchingSignatures) == 0 {
289-
return sigNoMatch, nil
290-
}
269+
// Skip filename matching - the filesystem detector already filtered files
270+
// based on signature filename patterns, so any file that reaches here
271+
// should be checked for content matching against all filesystem signatures
272+
matchingSignatures := filesystemSignatures
291273

292274
// Open file for signature matching
293275
r, err := os.Open(filePath)
@@ -356,6 +338,17 @@ func (sigcl *SignatureMatchClassifier) Collect(m chan<- prometheus.Metric) {
356338
sigcl.filesystemMissTotal.Collect(m)
357339
}
358340

341+
// GetFileSignatures returns signatures that are configured for filesystem domain
342+
func (sigcl *SignatureMatchClassifier) GetFileSignatures() []*Signature {
343+
var filesystemSignatures []*Signature
344+
for _, sig := range sigcl.Signatures {
345+
if sig.Domain == DomainFileSystem {
346+
filesystemSignatures = append(filesystemSignatures, sig)
347+
}
348+
}
349+
return filesystemSignatures
350+
}
351+
359352
// CompositeClassifier combines multiple classifiers into one. The first match wins.
360353
type CompositeClassifier []ProcessClassifier
361354

@@ -514,6 +507,14 @@ func (cl *CountingMetricsClassifier) Matches(executable string, cmdline []string
514507
return cl.D.Matches(executable, cmdline)
515508
}
516509

510+
func (cl *CountingMetricsClassifier) MatchesFile(filePath string) (*Classification, error) {
511+
cl.callCount.Inc()
512+
if fsc, ok := cl.D.(FilesystemClassifier); ok {
513+
return fsc.MatchesFile(filePath)
514+
}
515+
return sigNoMatch, nil
516+
}
517+
517518
func (cl *CountingMetricsClassifier) Describe(d chan<- *prometheus.Desc) {
518519
cl.callCount.Describe(d)
519520
cl.D.Describe(d)
@@ -523,3 +524,99 @@ func (cl *CountingMetricsClassifier) Collect(m chan<- prometheus.Metric) {
523524
cl.callCount.Collect(m)
524525
cl.D.Collect(m)
525526
}
527+
528+
func (cl *CountingMetricsClassifier) GetFileSignatures() []*Signature {
529+
if fsc, ok := cl.D.(FilesystemClassifier); ok {
530+
return fsc.GetFileSignatures()
531+
}
532+
return nil
533+
}
534+
535+
func (cl GradedClassifier) MatchesFile(filePath string) (*Classification, error) {
536+
order := []Level{LevelVery, LevelBarely, LevelAudit}
537+
538+
var (
539+
c *Classification
540+
err error
541+
)
542+
for _, level := range order {
543+
classifier, exists := cl[level]
544+
if !exists {
545+
continue
546+
}
547+
548+
if fsc, ok := classifier.(FilesystemClassifier); ok {
549+
c, err = fsc.MatchesFile(filePath)
550+
if err != nil {
551+
return nil, err
552+
}
553+
if c.Level != LevelNoMatch {
554+
break
555+
}
556+
}
557+
}
558+
559+
if c == nil || c.Level == LevelNoMatch {
560+
return sigNoMatch, nil
561+
}
562+
563+
res := *c
564+
res.Classifier = ClassifierGraded + "." + res.Classifier
565+
return &res, nil
566+
}
567+
568+
func (cl GradedClassifier) GetFileSignatures() []*Signature {
569+
var allSignatures []*Signature
570+
571+
for _, classifier := range cl {
572+
if fsc, ok := classifier.(FilesystemClassifier); ok {
573+
signatures := fsc.GetFileSignatures()
574+
allSignatures = append(allSignatures, signatures...)
575+
}
576+
}
577+
578+
return allSignatures
579+
}
580+
581+
func (cl CompositeClassifier) MatchesFile(filePath string) (*Classification, error) {
582+
var (
583+
c *Classification
584+
err error
585+
)
586+
for _, classifier := range cl {
587+
if fsc, ok := classifier.(FilesystemClassifier); ok {
588+
c, err = fsc.MatchesFile(filePath)
589+
if err != nil {
590+
return nil, err
591+
}
592+
if c.Level != LevelNoMatch {
593+
break
594+
}
595+
}
596+
}
597+
598+
if c == nil || len(cl) == 0 {
599+
// empty composite classifier
600+
return sigNoMatch, nil
601+
}
602+
if c.Level == LevelNoMatch {
603+
return sigNoMatch, nil
604+
}
605+
606+
res := *c
607+
res.Classifier = ClassifierComposite + "." + res.Classifier
608+
return &res, nil
609+
}
610+
611+
func (cl CompositeClassifier) GetFileSignatures() []*Signature {
612+
var allSignatures []*Signature
613+
614+
for _, classifier := range cl {
615+
if fsc, ok := classifier.(FilesystemClassifier); ok {
616+
signatures := fsc.GetFileSignatures()
617+
allSignatures = append(allSignatures, signatures...)
618+
}
619+
}
620+
621+
return allSignatures
622+
}

components/ee/agent-smith/pkg/classifier/classifier_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,38 @@ func TestCommandlineClassifier(t *testing.T) {
9191
})
9292
}
9393
}
94+
95+
func TestCountingMetricsClassifierFilesystemInterface(t *testing.T) {
96+
// Create a signature classifier with filesystem signatures
97+
signatures := []*classifier.Signature{
98+
{
99+
Name: "test-filesystem",
100+
Domain: classifier.DomainFileSystem,
101+
Pattern: []byte("malware"),
102+
Filename: []string{"malware.exe"},
103+
},
104+
}
105+
106+
sigClassifier := classifier.NewSignatureMatchClassifier("test", classifier.LevelAudit, signatures)
107+
countingClassifier := classifier.NewCountingMetricsClassifier("counting", sigClassifier)
108+
109+
// Test that CountingMetricsClassifier implements FilesystemClassifier
110+
var fsc classifier.FilesystemClassifier = countingClassifier
111+
112+
// Test filesystem file matching (file doesn't exist, should return no match without error)
113+
result, err := fsc.MatchesFile("/nonexistent/path/malware.exe")
114+
if err != nil {
115+
t.Fatalf("MatchesFile failed: %v", err)
116+
}
117+
118+
// Should return no match since file doesn't exist, but no error
119+
if result.Level != classifier.LevelNoMatch {
120+
t.Errorf("Expected LevelNoMatch for nonexistent file, got %v", result.Level)
121+
}
122+
123+
// Test that the interface delegation works by checking that it doesn't panic
124+
// and returns a valid Classification
125+
if result == nil {
126+
t.Error("Expected non-nil Classification result")
127+
}
128+
}

0 commit comments

Comments
 (0)