github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/file/cataloger/secrets/cataloger_test.go (about) 1 package secrets 2 3 import ( 4 "regexp" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 9 intFile "github.com/anchore/syft/internal/file" 10 "github.com/anchore/syft/syft/file" 11 ) 12 13 func TestSecretsCataloger(t *testing.T) { 14 tests := []struct { 15 name string 16 fixture string 17 reveal bool 18 maxSize int64 19 patterns map[string]string 20 expected []file.SearchResult 21 constructorErr bool 22 catalogErr bool 23 }{ 24 { 25 name: "go-case-find-and-reveal", 26 fixture: "test-fixtures/secrets/simple.txt", 27 reveal: true, 28 patterns: map[string]string{ 29 "simple-secret-key": `^secret_key=.*`, 30 }, 31 expected: []file.SearchResult{ 32 { 33 Classification: "simple-secret-key", 34 LineNumber: 2, 35 LineOffset: 0, 36 SeekPosition: 34, 37 Length: 21, 38 Value: "secret_key=clear_text", 39 }, 40 }, 41 }, 42 { 43 name: "dont-reveal-secret-value", 44 fixture: "test-fixtures/secrets/simple.txt", 45 reveal: false, 46 patterns: map[string]string{ 47 "simple-secret-key": `^secret_key=.*`, 48 }, 49 expected: []file.SearchResult{ 50 { 51 Classification: "simple-secret-key", 52 LineNumber: 2, 53 LineOffset: 0, 54 SeekPosition: 34, 55 Length: 21, 56 Value: "", 57 }, 58 }, 59 }, 60 { 61 name: "reveal-named-capture-group", 62 fixture: "test-fixtures/secrets/simple.txt", 63 reveal: true, 64 patterns: map[string]string{ 65 "simple-secret-key": `^secret_key=(?P<value>.*)`, 66 }, 67 expected: []file.SearchResult{ 68 { 69 Classification: "simple-secret-key", 70 LineNumber: 2, 71 LineOffset: 11, 72 SeekPosition: 45, 73 Length: 10, 74 Value: "clear_text", 75 }, 76 }, 77 }, 78 { 79 name: "multiple-secret-instances", 80 fixture: "test-fixtures/secrets/multiple.txt", 81 reveal: true, 82 patterns: map[string]string{ 83 "simple-secret-key": `secret_key=.*`, 84 }, 85 expected: []file.SearchResult{ 86 { 87 Classification: "simple-secret-key", 88 LineNumber: 1, 89 LineOffset: 0, 90 SeekPosition: 0, 91 Length: 22, 92 Value: "secret_key=clear_text1", 93 }, 94 { 95 Classification: "simple-secret-key", 96 LineNumber: 3, 97 LineOffset: 0, 98 SeekPosition: 57, 99 Length: 22, 100 Value: "secret_key=clear_text2", 101 }, 102 { 103 Classification: "simple-secret-key", 104 LineNumber: 4, 105 // note: this test captures a line offset case 106 LineOffset: 1, 107 SeekPosition: 81, 108 Length: 22, 109 Value: "secret_key=clear_text3", 110 }, 111 { 112 Classification: "simple-secret-key", 113 LineNumber: 6, 114 LineOffset: 0, 115 SeekPosition: 139, 116 Length: 22, 117 Value: "secret_key=clear_text4", 118 }, 119 }, 120 }, 121 { 122 name: "multiple-secret-instances-with-capture-group", 123 fixture: "test-fixtures/secrets/multiple.txt", 124 reveal: true, 125 patterns: map[string]string{ 126 "simple-secret-key": `secret_key=(?P<value>.*)`, 127 }, 128 expected: []file.SearchResult{ 129 { 130 Classification: "simple-secret-key", 131 LineNumber: 1, 132 // note: value capture group location 133 LineOffset: 11, 134 SeekPosition: 11, 135 Length: 11, 136 Value: "clear_text1", 137 }, 138 { 139 Classification: "simple-secret-key", 140 LineNumber: 3, 141 LineOffset: 11, 142 SeekPosition: 68, 143 Length: 11, 144 Value: "clear_text2", 145 }, 146 { 147 Classification: "simple-secret-key", 148 LineNumber: 4, 149 // note: value capture group location + offset 150 LineOffset: 12, 151 SeekPosition: 92, 152 Length: 11, 153 Value: "clear_text3", 154 }, 155 { 156 Classification: "simple-secret-key", 157 LineNumber: 6, 158 LineOffset: 11, 159 SeekPosition: 150, 160 Length: 11, 161 Value: "clear_text4", 162 }, 163 }, 164 }, 165 } 166 167 for _, test := range tests { 168 t.Run(test.name, func(t *testing.T) { 169 regexObjs := make(map[string]*regexp.Regexp) 170 for name, pattern := range test.patterns { 171 // always assume given patterns should be multiline 172 obj, err := regexp.Compile(`` + pattern) 173 if err != nil { 174 t.Fatalf("unable to parse regex: %+v", err) 175 } 176 regexObjs[name] = obj 177 } 178 179 c, err := NewCataloger(regexObjs, test.reveal, test.maxSize) 180 if err != nil && !test.constructorErr { 181 t.Fatalf("could not create cataloger (but should have been able to): %+v", err) 182 } else if err == nil && test.constructorErr { 183 t.Fatalf("expected constructor error but did not get one") 184 } else if test.constructorErr && err != nil { 185 return 186 } 187 188 resolver := file.NewMockResolverForPaths(test.fixture) 189 190 actualResults, err := c.Catalog(resolver) 191 if err != nil && !test.catalogErr { 192 t.Fatalf("could not catalog (but should have been able to): %+v", err) 193 } else if err == nil && test.catalogErr { 194 t.Fatalf("expected catalog error but did not get one") 195 } else if test.catalogErr && err != nil { 196 return 197 } 198 199 loc := file.NewLocation(test.fixture) 200 if _, exists := actualResults[loc.Coordinates]; !exists { 201 t.Fatalf("could not find location=%q in results", loc) 202 } 203 204 assert.Equal(t, test.expected, actualResults[loc.Coordinates], "mismatched secrets") 205 }) 206 } 207 } 208 209 func TestSecretsCataloger_DefaultSecrets(t *testing.T) { 210 regexObjs, err := GenerateSearchPatterns(DefaultSecretsPatterns, nil, nil) 211 if err != nil { 212 t.Fatalf("unable to get patterns: %+v", err) 213 } 214 215 tests := []struct { 216 fixture string 217 expected []file.SearchResult 218 }{ 219 { 220 fixture: "test-fixtures/secrets/default/aws.env", 221 expected: []file.SearchResult{ 222 { 223 Classification: "aws-access-key", 224 LineNumber: 2, 225 LineOffset: 25, 226 SeekPosition: 64, 227 Length: 20, 228 Value: "AKIAIOSFODNN7EXAMPLE", 229 }, 230 { 231 Classification: "aws-secret-key", 232 LineNumber: 3, 233 LineOffset: 29, 234 SeekPosition: 114, 235 Length: 40, 236 Value: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 237 }, 238 }, 239 }, 240 { 241 fixture: "test-fixtures/secrets/default/aws.ini", 242 expected: []file.SearchResult{ 243 { 244 Classification: "aws-access-key", 245 LineNumber: 3, 246 LineOffset: 18, 247 SeekPosition: 67, 248 Length: 20, 249 Value: "AKIAIOSFODNN7EXAMPLE", 250 }, 251 { 252 Classification: "aws-secret-key", 253 LineNumber: 4, 254 LineOffset: 22, 255 SeekPosition: 110, 256 Length: 40, 257 Value: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 258 }, 259 }, 260 }, 261 { 262 fixture: "test-fixtures/secrets/default/private-key.pem", 263 expected: []file.SearchResult{ 264 { 265 Classification: "pem-private-key", 266 LineNumber: 2, 267 LineOffset: 27, 268 SeekPosition: 66, 269 Length: 351, 270 Value: ` 271 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBj08sp5++4anG 272 cmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgYD 273 VQQDDBcqLmF3cy10ZXN0LnByb2dyZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 274 bml6YXRpb252YWxzaGEyZzIuY3JsMIGgBggrBgEFBQcBAQSBkzCBkDBNBggrBgEF 275 BQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmdh 276 z3P668YfhUbKdRF6S42Cg6zn 277 `, 278 }, 279 }, 280 }, 281 { 282 fixture: "test-fixtures/secrets/default/private-key-openssl.pem", 283 expected: []file.SearchResult{ 284 { 285 Classification: "pem-private-key", 286 LineNumber: 2, 287 LineOffset: 35, 288 SeekPosition: 74, 289 Length: 351, 290 Value: ` 291 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBj08sp5++4anG 292 cmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgYD 293 VQQDDBcqLmF3cy10ZXN0LnByb2dyZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 294 bml6YXRpb252YWxzaGEyZzIuY3JsMIGgBggrBgEFBQcBAQSBkzCBkDBNBggrBgEF 295 BQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmdh 296 z3P668YfhUbKdRF6S42Cg6zn 297 `, 298 }, 299 }, 300 }, 301 { 302 // note: this test proves that the PEM regex matches the smallest possible match 303 // since the test catches two adjacent secrets 304 fixture: "test-fixtures/secrets/default/private-keys.pem", 305 expected: []file.SearchResult{ 306 { 307 Classification: "pem-private-key", 308 LineNumber: 1, 309 LineOffset: 35, 310 SeekPosition: 35, 311 Length: 351, 312 Value: ` 313 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBj08sp5++4anG 314 cmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgYD 315 VQQDDBcqLmF3cy10ZXN0LnByb2dyZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 316 bml6YXRpb252YWxzaGEyZzIuY3JsMIGgBggrBgEFBQcBAQSBkzCBkDBNBggrBgEF 317 BQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmdh 318 z3P668YfhUbKdRF6S42Cg6zn 319 `, 320 }, 321 { 322 Classification: "pem-private-key", 323 LineNumber: 9, 324 LineOffset: 35, 325 SeekPosition: 455, 326 Length: 351, 327 Value: ` 328 MIIEvgTHISISNOTAREALKEYoIBAQDBj08DBj08DBj08DBj08DBj08DBsp5++4an3 329 cmQxJjAkBgNVBAoTHVByb2dyZXNzIFNvZnR3YXJlIENvcnBvcmF0aW9uMSAwHgY5 330 VQQDDBcqLmF3cy10ZXN0SISNOTAREALKEYoIBAQDBj08DfffKoZIhvcNAQEBBQA7 331 bml6SISNOTAREALKEYoIBAQDBj08DdssBggrBgEFBQcBAQSBkzCBkDBNBggrBgE8 332 BQcwAoZBaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nvcmd1 333 j4f668YfhUbKdRF6S6734856 334 `, 335 }, 336 }, 337 }, 338 { 339 fixture: "test-fixtures/secrets/default/private-key-false-positive.pem", 340 expected: nil, 341 }, 342 { 343 // this test represents: 344 // 1. a docker config 345 // 2. a named capture group with the correct line number and line offset case 346 // 3. the named capture group is in a different line than the match start, and both the match start and the capture group have different line offsets 347 fixture: "test-fixtures/secrets/default/docker-config.json", 348 expected: []file.SearchResult{ 349 { 350 Classification: "docker-config-auth", 351 LineNumber: 5, 352 LineOffset: 15, 353 SeekPosition: 100, 354 Length: 10, 355 Value: "tOpsyKreTz", 356 }, 357 }, 358 }, 359 { 360 fixture: "test-fixtures/secrets/default/not-docker-config.json", 361 expected: nil, 362 }, 363 { 364 fixture: "test-fixtures/secrets/default/api-key.txt", 365 expected: []file.SearchResult{ 366 { 367 Classification: "generic-api-key", 368 LineNumber: 2, 369 LineOffset: 7, 370 SeekPosition: 33, 371 Length: 20, 372 Value: "12345A7a901b34567890", 373 }, 374 { 375 Classification: "generic-api-key", 376 LineNumber: 3, 377 LineOffset: 9, 378 SeekPosition: 63, 379 Length: 30, 380 Value: "12345A7a901b345678901234567890", 381 }, 382 { 383 Classification: "generic-api-key", 384 LineNumber: 4, 385 LineOffset: 10, 386 SeekPosition: 104, 387 Length: 40, 388 Value: "12345A7a901b3456789012345678901234567890", 389 }, 390 { 391 Classification: "generic-api-key", 392 LineNumber: 5, 393 LineOffset: 10, 394 SeekPosition: 156, 395 Length: 50, 396 Value: "12345A7a901b34567890123456789012345678901234567890", 397 }, 398 { 399 Classification: "generic-api-key", 400 LineNumber: 6, 401 LineOffset: 16, 402 SeekPosition: 224, 403 Length: 60, 404 Value: "12345A7a901b345678901234567890123456789012345678901234567890", 405 }, 406 { 407 Classification: "generic-api-key", 408 LineNumber: 14, 409 LineOffset: 8, 410 SeekPosition: 502, 411 Length: 20, 412 Value: "11111111111111111111", 413 }, 414 }, 415 }, 416 } 417 418 for _, test := range tests { 419 t.Run(test.fixture, func(t *testing.T) { 420 421 c, err := NewCataloger(regexObjs, true, 10*intFile.MB) 422 if err != nil { 423 t.Fatalf("could not create cataloger: %+v", err) 424 } 425 426 resolver := file.NewMockResolverForPaths(test.fixture) 427 428 actualResults, err := c.Catalog(resolver) 429 if err != nil { 430 t.Fatalf("could not catalog: %+v", err) 431 } 432 433 loc := file.NewLocation(test.fixture) 434 if _, exists := actualResults[loc.Coordinates]; !exists && test.expected != nil { 435 t.Fatalf("could not find location=%q in results", loc) 436 } else if !exists && test.expected == nil { 437 return 438 } 439 440 assert.Equal(t, test.expected, actualResults[loc.Coordinates], "mismatched secrets") 441 }) 442 } 443 }