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