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  }