github.com/ishita82/trivy-gitaction@v0.0.0-20240206054925-e937cc05f8e3/integration/repo_test.go (about)

     1  //go:build integration
     2  
     3  package integration
     4  
     5  import (
     6  	"fmt"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
    15  	"github.com/aquasecurity/trivy/pkg/types"
    16  	"github.com/aquasecurity/trivy/pkg/uuid"
    17  )
    18  
    19  // TestRepository tests `trivy repo` with the local code repositories
    20  func TestRepository(t *testing.T) {
    21  	type args struct {
    22  		scanner        types.Scanner
    23  		ignoreIDs      []string
    24  		policyPaths    []string
    25  		namespaces     []string
    26  		listAllPkgs    bool
    27  		input          string
    28  		secretConfig   string
    29  		filePatterns   []string
    30  		helmSet        []string
    31  		helmValuesFile []string
    32  		skipFiles      []string
    33  		skipDirs       []string
    34  		command        string
    35  		format         types.Format
    36  		includeDevDeps bool
    37  		parallel       int
    38  	}
    39  	tests := []struct {
    40  		name     string
    41  		args     args
    42  		golden   string
    43  		override func(*types.Report)
    44  	}{
    45  		{
    46  			name: "gomod",
    47  			args: args{
    48  				scanner: types.VulnerabilityScanner,
    49  				input:   "testdata/fixtures/repo/gomod",
    50  			},
    51  			golden: "testdata/gomod.json.golden",
    52  		},
    53  		{
    54  			name: "gomod with skip files",
    55  			args: args{
    56  				scanner:   types.VulnerabilityScanner,
    57  				input:     "testdata/fixtures/repo/gomod",
    58  				skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"},
    59  			},
    60  			golden: "testdata/gomod-skip.json.golden",
    61  		},
    62  		{
    63  			name: "gomod with skip dirs",
    64  			args: args{
    65  				scanner:  types.VulnerabilityScanner,
    66  				input:    "testdata/fixtures/repo/gomod",
    67  				skipDirs: []string{"testdata/fixtures/repo/gomod/submod2"},
    68  			},
    69  			golden: "testdata/gomod-skip.json.golden",
    70  		},
    71  		{
    72  			name: "gomod in series",
    73  			args: args{
    74  				scanner:  types.VulnerabilityScanner,
    75  				input:    "testdata/fixtures/repo/gomod",
    76  				parallel: 1,
    77  			},
    78  			golden: "testdata/gomod.json.golden",
    79  		},
    80  		{
    81  			name: "npm",
    82  			args: args{
    83  				scanner:     types.VulnerabilityScanner,
    84  				input:       "testdata/fixtures/repo/npm",
    85  				listAllPkgs: true,
    86  			},
    87  			golden: "testdata/npm.json.golden",
    88  		},
    89  		{
    90  			name: "npm with dev deps",
    91  			args: args{
    92  				scanner:        types.VulnerabilityScanner,
    93  				input:          "testdata/fixtures/repo/npm",
    94  				listAllPkgs:    true,
    95  				includeDevDeps: true,
    96  			},
    97  			golden: "testdata/npm-with-dev.json.golden",
    98  		},
    99  		{
   100  			name: "yarn",
   101  			args: args{
   102  				scanner:     types.VulnerabilityScanner,
   103  				input:       "testdata/fixtures/repo/yarn",
   104  				listAllPkgs: true,
   105  			},
   106  			golden: "testdata/yarn.json.golden",
   107  		},
   108  		{
   109  			name: "pnpm",
   110  			args: args{
   111  				scanner: types.VulnerabilityScanner,
   112  				input:   "testdata/fixtures/repo/pnpm",
   113  			},
   114  			golden: "testdata/pnpm.json.golden",
   115  		},
   116  		{
   117  			name: "pip",
   118  			args: args{
   119  				scanner:     types.VulnerabilityScanner,
   120  				listAllPkgs: true,
   121  				input:       "testdata/fixtures/repo/pip",
   122  			},
   123  			golden: "testdata/pip.json.golden",
   124  		},
   125  		{
   126  			name: "pipenv",
   127  			args: args{
   128  				scanner:     types.VulnerabilityScanner,
   129  				listAllPkgs: true,
   130  				input:       "testdata/fixtures/repo/pipenv",
   131  			},
   132  			golden: "testdata/pipenv.json.golden",
   133  		},
   134  		{
   135  			name: "poetry",
   136  			args: args{
   137  				scanner:     types.VulnerabilityScanner,
   138  				listAllPkgs: true,
   139  				input:       "testdata/fixtures/repo/poetry",
   140  			},
   141  			golden: "testdata/poetry.json.golden",
   142  		},
   143  		{
   144  			name: "pom",
   145  			args: args{
   146  				scanner: types.VulnerabilityScanner,
   147  				input:   "testdata/fixtures/repo/pom",
   148  			},
   149  			golden: "testdata/pom.json.golden",
   150  		},
   151  		{
   152  			name: "gradle",
   153  			args: args{
   154  				scanner: types.VulnerabilityScanner,
   155  				input:   "testdata/fixtures/repo/gradle",
   156  			},
   157  			golden: "testdata/gradle.json.golden",
   158  		},
   159  		{
   160  			name: "conan",
   161  			args: args{
   162  				scanner:     types.VulnerabilityScanner,
   163  				listAllPkgs: true,
   164  				input:       "testdata/fixtures/repo/conan",
   165  			},
   166  			golden: "testdata/conan.json.golden",
   167  		},
   168  		{
   169  			name: "nuget",
   170  			args: args{
   171  				scanner:     types.VulnerabilityScanner,
   172  				listAllPkgs: true,
   173  				input:       "testdata/fixtures/repo/nuget",
   174  			},
   175  			golden: "testdata/nuget.json.golden",
   176  		},
   177  		{
   178  			name: "dotnet",
   179  			args: args{
   180  				scanner:     types.VulnerabilityScanner,
   181  				listAllPkgs: true,
   182  				input:       "testdata/fixtures/repo/dotnet",
   183  			},
   184  			golden: "testdata/dotnet.json.golden",
   185  		},
   186  		{
   187  			name: "packages-props",
   188  			args: args{
   189  				scanner:     types.VulnerabilityScanner,
   190  				listAllPkgs: true,
   191  				input:       "testdata/fixtures/repo/packagesprops",
   192  			},
   193  			golden: "testdata/packagesprops.json.golden",
   194  		},
   195  		{
   196  			name: "swift",
   197  			args: args{
   198  				scanner:     types.VulnerabilityScanner,
   199  				listAllPkgs: true,
   200  				input:       "testdata/fixtures/repo/swift",
   201  			},
   202  			golden: "testdata/swift.json.golden",
   203  		},
   204  		{
   205  			name: "cocoapods",
   206  			args: args{
   207  				scanner:     types.VulnerabilityScanner,
   208  				listAllPkgs: true,
   209  				input:       "testdata/fixtures/repo/cocoapods",
   210  			},
   211  			golden: "testdata/cocoapods.json.golden",
   212  		},
   213  		{
   214  			name: "pubspec.lock",
   215  			args: args{
   216  				scanner:     types.VulnerabilityScanner,
   217  				listAllPkgs: true,
   218  				input:       "testdata/fixtures/repo/pubspec",
   219  			},
   220  			golden: "testdata/pubspec.lock.json.golden",
   221  		},
   222  		{
   223  			name: "mix.lock",
   224  			args: args{
   225  				scanner:     types.VulnerabilityScanner,
   226  				listAllPkgs: true,
   227  				input:       "testdata/fixtures/repo/mixlock",
   228  			},
   229  			golden: "testdata/mix.lock.json.golden",
   230  		},
   231  		{
   232  			name: "composer.lock",
   233  			args: args{
   234  				scanner:     types.VulnerabilityScanner,
   235  				listAllPkgs: true,
   236  				input:       "testdata/fixtures/repo/composer",
   237  			},
   238  			golden: "testdata/composer.lock.json.golden",
   239  		},
   240  		{
   241  			name: "dockerfile",
   242  			args: args{
   243  				scanner:    types.MisconfigScanner,
   244  				input:      "testdata/fixtures/repo/dockerfile",
   245  				namespaces: []string{"testing"},
   246  			},
   247  			golden: "testdata/dockerfile.json.golden",
   248  		},
   249  		{
   250  			name: "dockerfile with custom file pattern",
   251  			args: args{
   252  				scanner:      types.MisconfigScanner,
   253  				input:        "testdata/fixtures/repo/dockerfile_file_pattern",
   254  				namespaces:   []string{"testing"},
   255  				filePatterns: []string{"dockerfile:Customfile"},
   256  			},
   257  			golden: "testdata/dockerfile_file_pattern.json.golden",
   258  		},
   259  		{
   260  			name: "dockerfile with rule exception",
   261  			args: args{
   262  				scanner:     types.MisconfigScanner,
   263  				policyPaths: []string{"testdata/fixtures/repo/rule-exception/policy"},
   264  				input:       "testdata/fixtures/repo/rule-exception",
   265  			},
   266  			golden: "testdata/dockerfile-rule-exception.json.golden",
   267  		},
   268  		{
   269  			name: "dockerfile with namespace exception",
   270  			args: args{
   271  				scanner:     types.MisconfigScanner,
   272  				policyPaths: []string{"testdata/fixtures/repo/namespace-exception/policy"},
   273  				input:       "testdata/fixtures/repo/namespace-exception",
   274  			},
   275  			golden: "testdata/dockerfile-namespace-exception.json.golden",
   276  		},
   277  		{
   278  			name: "dockerfile with custom policies",
   279  			args: args{
   280  				scanner:     types.MisconfigScanner,
   281  				policyPaths: []string{"testdata/fixtures/repo/custom-policy/policy"},
   282  				namespaces:  []string{"user"},
   283  				input:       "testdata/fixtures/repo/custom-policy",
   284  			},
   285  			golden: "testdata/dockerfile-custom-policies.json.golden",
   286  		},
   287  		{
   288  			name: "tarball helm chart scanning with builtin policies",
   289  			args: args{
   290  				scanner: types.MisconfigScanner,
   291  				input:   "testdata/fixtures/repo/helm",
   292  			},
   293  			golden: "testdata/helm.json.golden",
   294  		},
   295  		{
   296  			name: "helm chart directory scanning with builtin policies",
   297  			args: args{
   298  				scanner: types.MisconfigScanner,
   299  				input:   "testdata/fixtures/repo/helm_testchart",
   300  			},
   301  			golden: "testdata/helm_testchart.json.golden",
   302  		},
   303  		{
   304  			name: "helm chart directory scanning with value overrides using set",
   305  			args: args{
   306  				scanner: types.MisconfigScanner,
   307  				input:   "testdata/fixtures/repo/helm_testchart",
   308  				helmSet: []string{"securityContext.runAsUser=0"},
   309  			},
   310  			golden: "testdata/helm_testchart.overridden.json.golden",
   311  		},
   312  		{
   313  			name: "helm chart directory scanning with value overrides using value file",
   314  			args: args{
   315  				scanner:        types.MisconfigScanner,
   316  				input:          "testdata/fixtures/repo/helm_testchart",
   317  				helmValuesFile: []string{"testdata/fixtures/repo/helm_values/values.yaml"},
   318  			},
   319  			golden: "testdata/helm_testchart.overridden.json.golden",
   320  		},
   321  		{
   322  			name: "helm chart directory scanning with builtin policies and non string Chart name",
   323  			args: args{
   324  				scanner: types.MisconfigScanner,
   325  				input:   "testdata/fixtures/repo/helm_badname",
   326  			},
   327  			golden: "testdata/helm_badname.json.golden",
   328  		},
   329  		{
   330  			name: "secrets",
   331  			args: args{
   332  				scanner:      "vuln,secret",
   333  				input:        "testdata/fixtures/repo/secrets",
   334  				secretConfig: "testdata/fixtures/repo/secrets/trivy-secret.yaml",
   335  			},
   336  			golden: "testdata/secrets.json.golden",
   337  		},
   338  		{
   339  			name: "conda generating CycloneDX SBOM",
   340  			args: args{
   341  				command: "rootfs",
   342  				format:  "cyclonedx",
   343  				input:   "testdata/fixtures/repo/conda",
   344  			},
   345  			golden: "testdata/conda-cyclonedx.json.golden",
   346  		},
   347  		{
   348  			name: "pom.xml generating CycloneDX SBOM (with vulnerabilities)",
   349  			args: args{
   350  				command: "fs",
   351  				scanner: types.VulnerabilityScanner,
   352  				format:  "cyclonedx",
   353  				input:   "testdata/fixtures/repo/pom",
   354  			},
   355  			golden: "testdata/pom-cyclonedx.json.golden",
   356  		},
   357  		{
   358  			name: "conda generating SPDX SBOM",
   359  			args: args{
   360  				command: "rootfs",
   361  				format:  "spdx-json",
   362  				input:   "testdata/fixtures/repo/conda",
   363  			},
   364  			golden: "testdata/conda-spdx.json.golden",
   365  		},
   366  		{
   367  			name: "gomod with fs subcommand",
   368  			args: args{
   369  				command:   "fs",
   370  				scanner:   types.VulnerabilityScanner,
   371  				input:     "testdata/fixtures/repo/gomod",
   372  				skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"},
   373  			},
   374  			golden: "testdata/gomod-skip.json.golden",
   375  			override: func(report *types.Report) {
   376  				report.ArtifactType = ftypes.ArtifactFilesystem
   377  			},
   378  		},
   379  		{
   380  			name: "dockerfile with fs subcommand and an alias scanner",
   381  			args: args{
   382  				command:     "fs",
   383  				scanner:     "config", // for backward compatibility
   384  				policyPaths: []string{"testdata/fixtures/repo/custom-policy/policy"},
   385  				namespaces:  []string{"user"},
   386  				input:       "testdata/fixtures/repo/custom-policy",
   387  			},
   388  			golden: "testdata/dockerfile-custom-policies.json.golden",
   389  			override: func(report *types.Report) {
   390  				report.ArtifactType = ftypes.ArtifactFilesystem
   391  			},
   392  		},
   393  	}
   394  
   395  	// Set up testing DB
   396  	cacheDir := initDB(t)
   397  
   398  	// Set a temp dir so that modules will not be loaded
   399  	t.Setenv("XDG_DATA_HOME", cacheDir)
   400  
   401  	for _, tt := range tests {
   402  		t.Run(tt.name, func(t *testing.T) {
   403  
   404  			command := "repo"
   405  			if tt.args.command != "" {
   406  				command = tt.args.command
   407  			}
   408  
   409  			format := types.FormatJSON
   410  			if tt.args.format != "" {
   411  				format = tt.args.format
   412  			}
   413  
   414  			osArgs := []string{
   415  				"-q",
   416  				"--cache-dir",
   417  				cacheDir,
   418  				command,
   419  				"--skip-db-update",
   420  				"--skip-policy-update",
   421  				"--format",
   422  				string(format),
   423  				"--parallel",
   424  				fmt.Sprint(tt.args.parallel),
   425  				"--offline-scan",
   426  			}
   427  
   428  			if tt.args.scanner != "" {
   429  				osArgs = append(osArgs, "--scanners", string(tt.args.scanner))
   430  			}
   431  
   432  			if len(tt.args.policyPaths) != 0 {
   433  				for _, policyPath := range tt.args.policyPaths {
   434  					osArgs = append(osArgs, "--config-policy", policyPath)
   435  				}
   436  			}
   437  
   438  			if len(tt.args.namespaces) != 0 {
   439  				for _, namespace := range tt.args.namespaces {
   440  					osArgs = append(osArgs, "--policy-namespaces", namespace)
   441  				}
   442  			}
   443  
   444  			if len(tt.args.ignoreIDs) != 0 {
   445  				trivyIgnore := ".trivyignore"
   446  				err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.args.ignoreIDs, "\n")), 0444)
   447  				assert.NoError(t, err, "failed to write .trivyignore")
   448  				defer os.Remove(trivyIgnore)
   449  			}
   450  
   451  			if len(tt.args.filePatterns) != 0 {
   452  				for _, filePattern := range tt.args.filePatterns {
   453  					osArgs = append(osArgs, "--file-patterns", filePattern)
   454  				}
   455  			}
   456  
   457  			if len(tt.args.helmSet) != 0 {
   458  				for _, helmSet := range tt.args.helmSet {
   459  					osArgs = append(osArgs, "--helm-set", helmSet)
   460  				}
   461  			}
   462  
   463  			if len(tt.args.helmValuesFile) != 0 {
   464  				for _, helmValuesFile := range tt.args.helmValuesFile {
   465  					osArgs = append(osArgs, "--helm-values", helmValuesFile)
   466  				}
   467  			}
   468  
   469  			if len(tt.args.skipFiles) != 0 {
   470  				for _, skipFile := range tt.args.skipFiles {
   471  					osArgs = append(osArgs, "--skip-files", skipFile)
   472  				}
   473  			}
   474  
   475  			if len(tt.args.skipDirs) != 0 {
   476  				for _, skipDir := range tt.args.skipDirs {
   477  					osArgs = append(osArgs, "--skip-dirs", skipDir)
   478  				}
   479  			}
   480  
   481  			// Setup the output file
   482  			outputFile := filepath.Join(t.TempDir(), "output.json")
   483  			if *update && tt.override == nil {
   484  				outputFile = tt.golden
   485  			}
   486  
   487  			if tt.args.listAllPkgs {
   488  				osArgs = append(osArgs, "--list-all-pkgs")
   489  			}
   490  
   491  			if tt.args.includeDevDeps {
   492  				osArgs = append(osArgs, "--include-dev-deps")
   493  			}
   494  
   495  			if tt.args.secretConfig != "" {
   496  				osArgs = append(osArgs, "--secret-config", tt.args.secretConfig)
   497  			}
   498  
   499  			osArgs = append(osArgs, "--output", outputFile)
   500  			osArgs = append(osArgs, tt.args.input)
   501  
   502  			uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d")
   503  
   504  			// Run "trivy repo"
   505  			err := execute(osArgs)
   506  			require.NoError(t, err)
   507  
   508  			// Compare want and got
   509  			switch format {
   510  			case types.FormatCycloneDX:
   511  				compareCycloneDX(t, tt.golden, outputFile)
   512  			case types.FormatSPDXJSON:
   513  				compareSPDXJson(t, tt.golden, outputFile)
   514  			case types.FormatJSON:
   515  				compareReports(t, tt.golden, outputFile, tt.override)
   516  			default:
   517  				require.Fail(t, "invalid format", "format: %s", format)
   518  			}
   519  		})
   520  	}
   521  }