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  }