Skip to content

Commit 94cdca4

Browse files
kylos101ona-agent
andcommitted
feat(agent-smith): implement filesystem signature scanning
- Add filesystem scanning capability to detect suspicious files in workspaces - Scan workspace directories directly from WorkingArea/{InstanceID} paths - Support filesystem signatures with filename patterns and regex matching - Add FilesystemScanning configuration with WorkingArea path - Integrate filesystem detection with existing signature classifier - Fix regex pattern matching in signature matching logic - Add comprehensive filesystem scanning tests - Update example configuration with filesystem signatures Co-authored-by: Ona <[email protected]>
1 parent 0d84199 commit 94cdca4

File tree

13 files changed

+1511
-39
lines changed

13 files changed

+1511
-39
lines changed

components/ee/agent-smith/example-config.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,53 @@
1010
"kind": "elf",
1111
"pattern": "YWdlbnRTbWl0aFRlc3RUYXJnZXQ=",
1212
"regexp": false
13+
},
14+
{
15+
"name": "mining_pool_config",
16+
"domain": "filesystem",
17+
"pattern": "c3RyYXR1bSt0Y3A6Ly8=",
18+
"regexp": false,
19+
"filenames": ["*.conf", "mining.conf", "config.json"]
20+
},
21+
{
22+
"name": "crypto_wallet_file",
23+
"domain": "filesystem",
24+
"pattern": "d2FsbGV0",
25+
"regexp": false,
26+
"filenames": ["wallet.dat", "*.wallet"]
27+
},
28+
{
29+
"name": "reverse_shell_script",
30+
"domain": "filesystem",
31+
"pattern": "bmMgLWUgL2Jpbi9zaA==",
32+
"regexp": false,
33+
"filenames": ["*.sh", "*.py", "shell.*"]
34+
}
35+
]
36+
},
37+
"audit": {
38+
"signatures": [
39+
{
40+
"name": "suspicious_env_file",
41+
"domain": "filesystem",
42+
"pattern": "QVBJX0tFWT0=",
43+
"regexp": false,
44+
"filenames": [".env", "*.env", ".environment"]
45+
},
46+
{
47+
"name": "ssh_private_key",
48+
"domain": "filesystem",
49+
"pattern": "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t",
50+
"regexp": false,
51+
"filenames": ["id_rsa", "id_dsa", "id_ecdsa", "*.pem"]
1352
}
1453
]
1554
}
55+
},
56+
"filesystemScanning": {
57+
"enabled": true,
58+
"scanInterval": "5m",
59+
"maxFileSize": 1024,
60+
"workingArea": "/mnt/workingarea-mk2"
1661
}
1762
}

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

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ type Smith struct {
5151
timeElapsedHandler func(t time.Time) time.Duration
5252
notifiedInfringements *lru.Cache
5353

54-
detector detector.ProcessDetector
55-
classifier classifier.ProcessClassifier
54+
detector detector.ProcessDetector
55+
classifier classifier.ProcessClassifier
56+
fileDetector detector.FileDetector
57+
FileClassifier classifier.FileClassifier
5658
}
5759

5860
// NewAgentSmith creates a new agent smith
@@ -135,6 +137,30 @@ func NewAgentSmith(cfg config.Config) (*Smith, error) {
135137
return nil, err
136138
}
137139

140+
// Initialize filesystem detection if enabled
141+
var filesystemDetec detector.FileDetector
142+
var filesystemClass classifier.FileClassifier
143+
if cfg.FilesystemScanning != nil && cfg.FilesystemScanning.Enabled {
144+
// Create filesystem detector config
145+
fsConfig := detector.FilesystemScanningConfig{
146+
Enabled: cfg.FilesystemScanning.Enabled,
147+
ScanInterval: cfg.FilesystemScanning.ScanInterval.Duration,
148+
MaxFileSize: cfg.FilesystemScanning.MaxFileSize,
149+
WorkingArea: cfg.FilesystemScanning.WorkingArea,
150+
}
151+
152+
// Check if the main classifier supports filesystem detection
153+
if fsc, ok := class.(classifier.FileClassifier); ok {
154+
filesystemClass = fsc
155+
filesystemDetec, err = detector.NewfileDetector(fsConfig, filesystemClass)
156+
if err != nil {
157+
log.WithError(err).Warn("failed to create filesystem detector")
158+
}
159+
} else {
160+
log.Warn("classifier does not support filesystem detection, filesystem scanning disabled")
161+
}
162+
}
163+
138164
m := newAgentMetrics()
139165
res := &Smith{
140166
EnforcementRules: map[string]config.EnforcementRules{
@@ -150,8 +176,10 @@ func NewAgentSmith(cfg config.Config) (*Smith, error) {
150176

151177
wsman: wsman,
152178

153-
detector: detec,
154-
classifier: class,
179+
detector: detec,
180+
classifier: class,
181+
fileDetector: filesystemDetec,
182+
FileClassifier: filesystemClass,
155183

156184
notifiedInfringements: lru.New(notificationCacheSize),
157185
metrics: m,
@@ -227,17 +255,34 @@ type classifiedProcess struct {
227255
Err error
228256
}
229257

258+
type classifiedFilesystemFile struct {
259+
F detector.File
260+
C *classifier.Classification
261+
Err error
262+
}
263+
230264
// Start gets a stream of Infringements from Run and executes a callback on them to apply a Penalty
231265
func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace, []config.PenaltyKind)) {
232266
ps, err := agent.detector.DiscoverProcesses(ctx)
233267
if err != nil {
234268
log.WithError(err).Fatal("cannot start process detector")
235269
}
236270

271+
// Start filesystem detection if enabled
272+
var fs <-chan detector.File
273+
if agent.fileDetector != nil {
274+
fs, err = agent.fileDetector.DiscoverFiles(ctx)
275+
if err != nil {
276+
log.WithError(err).Warn("cannot start filesystem detector")
277+
}
278+
}
279+
237280
var (
238281
wg sync.WaitGroup
239282
cli = make(chan detector.Process, 500)
240283
clo = make(chan classifiedProcess, 50)
284+
fli = make(chan detector.File, 100)
285+
flo = make(chan classifiedFilesystemFile, 25)
241286
)
242287
agent.metrics.RegisterClassificationQueues(cli, clo)
243288

@@ -268,6 +313,27 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
268313
}()
269314
}
270315

316+
// Filesystem classification workers (fewer than process workers)
317+
if agent.FileClassifier != nil {
318+
for i := 0; i < 5; i++ {
319+
wg.Add(1)
320+
go func() {
321+
defer wg.Done()
322+
for file := range fli {
323+
log.Infof("Classifying filesystem file: %s", file.Path)
324+
class, err := agent.FileClassifier.MatchesFile(file.Path)
325+
// Early out for no matches
326+
if err == nil && class.Level == classifier.LevelNoMatch {
327+
log.Infof("File classification: no match - %s", file.Path)
328+
continue
329+
}
330+
log.Infof("File classification result: %s (level: %s, err: %v)", file.Path, class.Level, err)
331+
flo <- classifiedFilesystemFile{F: file, C: class, Err: err}
332+
}
333+
}()
334+
}
335+
}
336+
271337
defer log.Info("agent smith main loop ended")
272338

273339
// We want to fill the classifier in a Go routine seaparete from using the classification
@@ -288,6 +354,15 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
288354
// we're overfilling the classifier worker
289355
agent.metrics.classificationBackpressureInDrop.Inc()
290356
}
357+
case file, ok := <-fs:
358+
if !ok {
359+
continue
360+
}
361+
select {
362+
case fli <- file:
363+
default:
364+
// filesystem queue full, skip this file
365+
}
291366
}
292367
}
293368
}()
@@ -319,6 +394,33 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
319394
},
320395
},
321396
})
397+
case fileClass := <-flo:
398+
log.Infof("Received classified file from flo channel")
399+
file, cl, err := fileClass.F, fileClass.C, fileClass.Err
400+
if err != nil {
401+
log.WithError(err).WithFields(log.OWI(file.Workspace.OwnerID, file.Workspace.WorkspaceID, file.Workspace.InstanceID)).WithField("path", file.Path).Error("cannot classify filesystem file")
402+
continue
403+
}
404+
if cl == nil || cl.Level == classifier.LevelNoMatch {
405+
log.Warn("filesystem signature not detected", "path", file.Path, "workspace", file.Workspace.WorkspaceID)
406+
continue
407+
}
408+
409+
log.Info("filesystem signature detected", "path", file.Path, "workspace", file.Workspace.WorkspaceID, "severity", cl.Level, "message", cl.Message)
410+
_, _ = agent.Penalize(InfringingWorkspace{
411+
SupervisorPID: file.Workspace.PID,
412+
Owner: file.Workspace.OwnerID,
413+
InstanceID: file.Workspace.InstanceID,
414+
WorkspaceID: file.Workspace.WorkspaceID,
415+
GitRemoteURL: []string{file.Workspace.GitURL},
416+
Infringements: []Infringement{
417+
{
418+
Kind: config.GradeKind(config.InfringementExec, common.Severity(cl.Level)), // Reuse exec for now
419+
Description: fmt.Sprintf("filesystem signature: %s", cl.Message),
420+
CommandLine: []string{file.Path}, // Use file path as "command"
421+
},
422+
},
423+
})
322424
}
323425
}
324426
}
@@ -420,10 +522,22 @@ func (agent *Smith) Describe(d chan<- *prometheus.Desc) {
420522
agent.metrics.Describe(d)
421523
agent.classifier.Describe(d)
422524
agent.detector.Describe(d)
525+
if agent.fileDetector != nil {
526+
agent.fileDetector.Describe(d)
527+
}
528+
if agent.FileClassifier != nil {
529+
agent.FileClassifier.Describe(d)
530+
}
423531
}
424532

425533
func (agent *Smith) Collect(m chan<- prometheus.Metric) {
426534
agent.metrics.Collect(m)
427535
agent.classifier.Collect(m)
428536
agent.detector.Collect(m)
537+
if agent.fileDetector != nil {
538+
agent.fileDetector.Collect(m)
539+
}
540+
if agent.FileClassifier != nil {
541+
agent.FileClassifier.Collect(m)
542+
}
429543
}

0 commit comments

Comments
 (0)