github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/integration/repo_test.go (about)

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