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 }